feat!: split useCompensationForm into useJobForm + useCompensationForm hooks#1735
Open
serikjensen wants to merge 5 commits intomainfrom
Open
feat!: split useCompensationForm into useJobForm + useCompensationForm hooks#1735serikjensen wants to merge 5 commits intomainfrom
serikjensen wants to merge 5 commits intomainfrom
Conversation
…m hooks Decompose the monolithic onboarding-only useCompensationForm into focused, steady-state hooks so partners can drive job and compensation lifecycles independently or together. - Add useJobForm / useCurrentJobForm: create | update routing, error handling, and POST/PUT/DELETE wiring for the Jobs API. - Refactor useCompensationForm + add useCurrentCompensationForm: single create | update path covering both the onboarding stub-update case and steady-state effective-dated compensations, with optimistic-locking via version. - Drop CompensationSubmitCallbacks (and the never-shipped JobSubmitCallbacks scaffolding); per the SDK convention, only hooks chaining multiple internal API calls expose submit callbacks. Partners read the saved entity from the awaited HookSubmitResult instead. - Document the title field's effective-dating semantics across the two hooks (useJobForm.Fields.Title for job-creation / immediate active-role rename vs useCompensationForm.Fields.Title for changes scoped to a specific compensation effectiveDate). - Add compose.test.tsx covering the onboarding chain (POST job -> PUT stub compensation) and steady-state composition through composeSubmitHandler. - Migrate Compensation.test.tsx + new hook tests off the bespoke apiSpy utility onto vi.fn<HttpResponseResolver> resolvers and mock.invocationCallOrder; delete the spy. Document the pattern in CLAUDE.md under "Asserting on HTTP requests". - Add src/test/factories/jobsAndCompensations.ts with fixtures parsed through Job\$inboundSchema / Compensation\$inboundSchema at construction time so wire shape drift fails fast. - Update docs/hooks/hooks.md (Composing Job + Compensation), useJobForm.md, and useCompensationForm.md; refresh hooks-implementation.md and the migrate-sdk-component-to-hooks SKILL to reflect the no-callbacks rule. BREAKING CHANGE: useCompensationForm.actions.onSubmit changed from (callbacks?, options?) to (options?). The CompensationSubmitCallbacks export is removed. Read the saved compensation from the awaited HookSubmitResult's data field instead of wiring onCompensationCreated / onCompensationUpdated. Co-authored-by: Cursor <cursoragent@cursor.com>
The hook documented (in useJobForm.md) that Fields.StateWcClassCode is hidden until the user marks the role as covered, but the implementation was only gating on showStateWc (state === 'WA'), leaving the class-code field visible in WA whether or not coverage was elected. Watch stateWcCovered in the hook and gate the field accordingly so the hook owns visibility (matching the migrate-sdk-component-to-hooks rule "the hook controls field visibility"). The schema's conditional required-ness was already correct and is unchanged. Co-authored-by: Cursor <cursoragent@cursor.com>
…h screen UX - Drop the eager `flsaStatus: NONEXEMPT` fallback so the field renders the empty "Select an item" placeholder when nothing is provided. Adds a `primaryFlsaStatus` derivation that inherits from the employee's primary job's current compensation FLSA when adding a secondary job. Schema still enforces requiredness on submit in create mode; partners can seed via `defaultValues.flsaStatus` for create flows that have a known classification up-front. `flsaStatus` is now `.optional()` at the validator level so the type honestly reflects the runtime contract. - Reorder the FLSA rate validations: surface RATE_EXEMPT_THRESHOLD before RATE_MINIMUM so a partner setting rate=0 with EXEMPT sees the more actionable "must meet salary threshold" message rather than the generic "amount must be at least \$1.00". Updates two unit tests that relied on the eager NONEXEMPT default to set `flsaStatus: 'Nonexempt'` in their `defaultValues`. Surfaced while migrating EditCompensation onto the hooks; the existing integration test asserts both the placeholder rendering and the EXEMPT threshold message ordering. Co-authored-by: Cursor <cursoragent@cursor.com>
4 tasks
jeffredodd
approved these changes
May 8, 2026
… and split job+comp doc The carve-out flag previously lived under `data.canTriggerCarveOut`, but `data.*` was otherwise reserved for fetched entities and static entity-derived facts; this one boolean was the only `useWatch`-driven reactive value mixed in. Move it to `status.*` alongside `isPending` and `mode`, and rename it to describe the real server-side effect (the carve-out path deletes the employee's secondary jobs, per the shipped i18n string and the gws-flows reference) so partners don't need Gusto-internal vocabulary to interpret the flag. Lift the lengthy "Composing Job + Compensation" section out of hooks.md into a dedicated docs/hooks/jobs-and-compensations.md, with see-also links from useJobForm.md and useCompensationForm.md and a one-line pointer left at the original anchor in hooks.md (preserves inbound deep links). Steady-state example switched from a window.confirm at submit time to a render-time inline alert keyed off the renamed flag, which mirrors the existing EditCompensation UX and exercises the now-correctly-placed reactive value. Co-authored-by: Cursor <cursoragent@cursor.com>
…rtners" The hooks docs are partner-facing, so third-person framings like "partners typically render…", "partners pass this as `min`…", or "no extra `useWatch` on the partner side" read as if the reader is a bystander. Flip those to second-person/imperative voice so the docs speak directly to the developer reading them. Also rename the "Partner-configurable?" column header in the requiredness tables to "Configurable?" for the same reason. Co-authored-by: Cursor <cursoragent@cursor.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Decomposes the onboarding-only
useCompensationForminto focused, steady-state hooks so partners can drive job and compensation lifecycles independently or together.useJobForm/useCurrentJobForm—create | updaterouting,Title,HireDate,TwoPercentShareholder,StateWcCovered,StateWcClassCodefields, S-Corp + WA workers' comp visibility helpers.useCompensationForm+ newuseCurrentCompensationForm— singlecreate | updatepath covering both the onboarding stub-update case (POST /v1/employees/:id/jobsreturns a stub compensation that getsPUT-updated) and steady-state effective-dated compensations (POST /v1/jobs/:jobId/compensations), with optimistic-locking viaversion. AddsEffectiveDatefield and derivedcanTriggerCarveOut/minimumEffectiveDate/maximumEffectiveDate/hasPendingFutureCompensationhelpers.composeSubmitHandlerexamples for the onboarding chain (POST job → PUT stub compensation in one submit) and steady-state edits.titlesemantics clarified —useJobForm.Fields.Titlefor job creation / immediate active-role rename;useCompensationForm.Fields.Titlefor changes scoped to a specific compensationeffectiveDate. Documented across both hook docs and the schema.Breaking changes
useCompensationForm.actions.onSubmitsignature changed from(callbacks?, options?)to(options?). TheCompensationSubmitCallbackstype is removed from the public exports. Read the saved compensation from the awaitedHookSubmitResult<Compensation>(result.data) instead of wiringonCompensationCreated/onCompensationUpdated.This aligns
useCompensationFormwith the rest of the SDK convention: hooks expose submit callbacks only when they chain multiple internal API calls (e.g.useEmployeeDetailsForm's onboarding-status update). Single-mutation hooks return their result directly. Updated docs and themigrate-sdk-component-to-hooksskill to reflect this rule.Testing infrastructure
compose.test.tsxcovering the onboarding chain throughcomposeSubmitHandlerand steady-state composition.Compensation.test.tsxand the new hook test suites off the bespokeapiSpyutility ontovi.fn<HttpResponseResolver>(...)resolvers +mock.invocationCallOrderfor sequencing assertions. Deletedsrc/test/spies/apiSpy.ts.CLAUDE.mdunder "Asserting on HTTP requests" so future agents converge on the same approach.src/test/factories/jobsAndCompensations.tswith fixtures parsed throughJob\$inboundSchema/Compensation\$inboundSchema(jobFromJSON/compensationFromJSON) at construction time so wire-shape drift fails fast.EMF-CO3,EMF-M03) toEditCompensationPresentation.test.tsx.Docs touched
docs/hooks/useJobForm.mddocs/hooks/useCompensationForm.md,docs/hooks/hooks.md(Composing Job + Compensation section, updated table)CLAUDE.md,.claude/hooks-implementation.md,.claude/skills/migrate-sdk-component-to-hooks/SKILL.mdTest plan
Made with Cursor