# Offers — Agent Guidance Offers-specific rules. Read alongside `../CLAUDE.md`. ## Form isolation invariant Every form section owns its own `useForm` instance, its own `FormProvider`, and its own Zod resolver. Sections **never** share form state. If you find yourself reading a field from a sibling section's form, that is a bug — expose the value through a prop or a shared hook return instead. ## Section hook contract All section hooks must return `{ form, onSave, isLoading, ...domainData }`. The `onSave` signature is always `(cb?: (showAlert: boolean) => void) => (e?: BaseSyntheticEvent) => void`. This is the standard used by `useJobForm` to compose the wizard — do not deviate. ## `useJobForm` is CREATE-only `useJobForm` orchestrates the four-step wizard. It is not used on edit pages. Edit pages instantiate section hooks directly (e.g. `JobOfferEdit` calls `useGeneralConfigSection` directly). Do not try to reuse `useJobForm` for edit flows. ## Application Form visibility is not an RHF form Field-visibility state is managed as a delta map (`Record`) via `useApplicationFormChanges`. It is **not** a React Hook Form instance. The delta only tracks fields whose visibility differs from the backend value; missing keys inherit the BE value. Call `resetApplicationFormChanges()` after a successful save. ## `ApplicationFormTab` ↔ `profileDisplayRules` coupling `ApplicationFormTab` (inside `PublishStep`) imports `HIDDEN_SECTION_CODES` from `JobApplicationDetail/constants/profileDisplayRules.ts`. This cross-directory import is intentional — both views must agree on which sections are unsupported. If you add a new hidden section code, update `profileDisplayRules.ts` in one place. ## `summaryTypes.ts` invariant `Section` is a discriminated union: it holds either `items` or `subsections`, never both. This invariant is enforced by the TypeScript type. Do not add an optional second field — use the union correctly. ## `ProfileTab` resume override In `ProfileTab`, `application.resume` overrides the form-answer value for the `resume` field. This is intentional: the resume can be updated via the edit drawer without re-fetching form answers. Do not remove this override. ## `useOffersFilters` — URL as state Filter state (search, departments, brands) lives in URL search params, not in component state. All setters use `replace: true` to avoid polluting history. List params are encoded as `|`-separated strings. When adding a new filter, follow this pattern — do not introduce useState for filter state. ## `useJobPosting` constructs the public URL client-side The public posting URL is assembled from `VITE_CAREERS_SITE_URL` + `jobBoard.siteSlug` + `data.jobId`. The backend does not return the full URL. Do not expect a `url` field from the API response. ## `useApplicantUpdateDrawerContent` biome-ignore This hook returns a factory function (a hook that returns a function). Biome incorrectly flags the inner hooks as top-level violations. The `biome-ignore-all lint/correctness/useHookAtTopLevel` comment at the top is a documented false positive — do not remove it. ## Query invalidation After mutating application form field visibility, always invalidate `jobKeys.applicationForm(jobId)`. After updating a candidate, invalidate `jobKeys.detail(jobId)`, `applicantsKeys.applicationDetail(jobId, applicationId)`, and `applicantsKeys.formAnswers(jobId, applicationId)`. ## `usePublicDescriptionWarningModal` vs `useJobOpenFromPublishModal` - `usePublicDescriptionWarningModal` — shown when the user edits a **live** posting (not just any save) - `useJobOpenFromPublishModal` — shown when the job is still in Draft and the user tries to publish Do not conflate these two modal triggers.