docs: form-hook reference pages — IA + props-type-driven Fields rendering#2304
docs: form-hook reference pages — IA + props-type-driven Fields rendering#2304mariechatfield wants to merge 40 commits into
Conversation
Three changes to the TypeDoc custom plugin that apply automatically to all hook pages: 1. Section ordering (router.ts, typedoc.config.ts): hook function section now comes before field components — SYNTHETIC_GROUP_ORDER and groupOrder both updated so Form Hooks precedes Components. 2. Hook props inline (theme.ts, router.ts): UseXxxFormSharedProps is now inlined under the hook function (like component XxxProps already are), rather than appearing as a standalone Interfaces entry. Detection is by naming convention; TypeAlias and Interface kinds both handled since TypeDoc may assign either depending on how the type is declared. 3. Fields summary table (theme.ts, utils.ts): each hook page now auto- generates a ## Fields quick-reference table from the Components group, listing each field with its availability/required notes extracted from @remarks (boilerplate "Available on the hook result as..." sentence stripped). Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Five improvements to the auto-generated hook pages: 1. Remove redundant headings: ## Form Hooks group heading and ### hookName() member heading are stripped; inner H4 sections (SharedProps, Returns, Remarks, Example) shift up to H2 so they read as top-level sections of the page. 2. Example code fences get title="Example" so Docusaurus renders them as titled code blocks rather than plain fences. 3. UseXxxFormReady ready-state interface is inlined under ## Returns with its full properties table; removed from the standalone Interfaces section. 4. EmployeeDetailsFields-style interfaces now drive the fields table: field keys come from the interface's own property names (source of truth for form.Fields shape), Component Type is extracted from the HookFieldProps generic chain and linked to the upstream hook-field type in utilities, and Notes come from each field component's @remarks. The table heading uses the interface's actual name (e.g. ## EmployeeDetailsFields) with its anchor preserved. The interface is removed from the standalone Interfaces section. 5. Restored Form Hooks before Components in SYNTHETIC_GROUP_ORDER (accidentally reverted during implementation, now corrected). Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
…ail anchors Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
…EmployeeDetailsFields - Example section now appears first (before props and returns) so a reader sees a working example immediately after the hook signature - ## Components heading removed; ### XxxField entries nest directly under ## EmployeeDetailsFields via heading hierarchy Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
… FieldProps under field entries Section order is now: Example → Remarks → SharedProps → Returns (with UseXxxFormReady nested at H3) → EmployeeDetailsFields (with XxxFieldProps and XxxValidation nested at H4 under each field) → Variables → Interfaces → Type Aliases (only generic/shared types remain). Key implementation detail: field key collection for FieldProps nesting is scoped to lines before ## Type Aliases to avoid misclassifying type aliases like EmployeeDetailsField as field component names. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
…ypes Moves EmployeeDetailsErrorCode, EmployeeDetailsField, EmployeeDetailsFormData (+ @interface), EmployeeDetailsFormOutputs, EmployeeDetailsOptionalFieldsToRequire, EmployeeDetailsSubmitCallbacks, EmployeeDetailsFieldsMetadata, EmployeeDetailsFormFields, UseEmployeeDetailsFormProps, UseEmployeeDetailsFormResult, EmployeeDetailsRequiredValidation, and NameValidation out of Type Aliases/Interfaces into a ## Utility Types section. Also adds Utility Types to SYNTHETIC_GROUP_ORDER in router.ts so it renders on synthetic hook pages. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Set excludeNotDocumented: false so symbols without explicit TSDoc appear in generated reference pages rather than being silently omitted. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Regenerate reference docs with expanded parameter objects. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
…ation - Elevate `UseXxxFormProps` (direct interface) to a named top-level section before Returns, matching the existing SharedProps elevation pattern - Remove all SharedProps-specific overrides from the plugin; hooks with a direct interface props type now use the same inlining path as components - Fix `buildFieldsTable` to find the Fields interface via type traversal (UseXxxFormReady → form.Fields → referenced type) instead of name heuristics - Fix router's inlinedNames to strip the props interface by following the hook's parameter type, and to try both <HookPascal>Fields name candidates so BankFormFields and EmployeeDetailsFields both resolve correctly - Remove duplicate entries: props interface and Fields interface no longer appear in both the top-level section and the Interfaces group Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Replace name-convention matching for hook props/Ready/Fields with a type-driven model (form-hook-model.ts). A hook is recognized as a form hook when its return union has a branch extending BaseFormHookReady; the props, Ready interface, Fields interface, and FormData are then read off the type arguments. Malformed form hooks hard-fail the build via FormHookModelError rather than degrading silently. - Props rendered first-class as a discriminated union: shared-options table plus a table per variant (e.g. useEmployeeDetailsForm) - Fields quick-reference table, props elevation, the Parameters->props heading rename, section reorder, and Ready inlining all model-driven - getHookReadyInterface resolves the Ready branch for any hook (form or data), so non-form hooks like useEmployeeList no longer duplicate it - Handles legitimate shape variants without failing: array Fields (StateTaxFieldsGroup[]) and derived-alias props (Omit<...>) on secondary hooks Fixes latent bugs from name matching: useSignEmployeeForm's Fields interface (SignEmployeeFormFieldComponents) now resolves and renders a table; XxxFormFields utility aliases are no longer wrongly stripped. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Extract a base `HookModel { hookFn, props, readyInterface }` from
`FormHookModel` (renaming form-hook-model.ts → hook-model.ts) so data
hooks like `useEmployeeList` get the same TypeDoc treatment form hooks
already had — props elevation, page flattening, and Returns inlining —
all derived from the type graph rather than name conventions.
Restructure every hook page to hard-coded `Props` / `Returns` sections
with the type names nested as sub-entries:
## Props
### UseXxxProps
## Returns
### UseXxxResult
### UseXxxReady
The `UseXxxResult` return-union alias is folded into Returns (removed
from the Type Aliases group, its anchor relocated so links resolve), and
the hook group heading (`## Hooks` / `## Form Hooks`) is dropped since each
page documents exactly one hook.
Only generated hook reference pages change; verified via plugin tests
(114), docs:lint, and the docs:build broken-anchor gate.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Demote the generated Fields-interface heading (e.g. BankFormFields) from H2 to H3 under a new hard-coded `## Fields` parent, so the per-field components render as siblings rather than nesting under the interface. The interface keeps its symbol name and anchor, so cross-references from the Returns table stay valid. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The fields quick-reference table matched each field key to its component by stripping the "Field" suffix from the component name. That breaks when a component is re-exported under an alias to avoid a public-API collision (e.g. AmountField → DeductionAmountField), leaving the row's link, type, and notes blank. Map components by the reflection id of their props parameter instead: a field property (typeof XxxField, expanded to its call signature) and its component both reference the same XxxFieldProps type, so identity pairs them regardless of export name. Fixes the Amount rows on useDeductionForm and useChildSupportGarnishmentForm. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the bare `props: XxxFieldProps` parameter row on field components with an expanded table resolved from the underlying field-props interface (NumberInputHookFieldProps, SelectHookFieldProps, etc.) reached through the `HookFieldProps<…>` alias. Required props (label) and props that reference a type parameter (validationMessages, getOptionLabel) get table rows, with the field's actual type arguments substituted in — so validationMessages shows the field's real error codes and getOptionLabel resolves its entry type. Remaining props are forwarded unchanged from the base interface and summarized in a footer that links it. The hook-bound `name` is omitted (HookFieldProps drops it). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sort the form-hook Fields quick-reference table with always-defined fields first, then conditional ones (`typeof XxxField | undefined`), alphabetized within each group — mirroring the null-check boundary. Conditional fields now show `| undefined` in the Component Type column. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
When a `#### XxxFieldProps` type alias is folded under its field component, the injected block ends with its own `***`; the original divider TypeDoc emitted after the field was left in place, producing a double horizontal rule. Skip the original when injecting. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Each flat field component already documents its props via the expanded Parameters table (plus the "Also accepts … from XxxHookFieldProps" line), so the standalone `XxxFieldProps` type-alias block was pure duplication. Drop those aliases from the reference: they're removed from the `## Type Aliases` section and no longer nested under their component. Scoped precisely by type identity — only aliases that are a flat field component's props parameter are dropped. Group sub-field props (PreparerTextFieldProps, SplitFieldProps), discriminated variant props (*StateTaxFieldProps), `*Validation` types, and the shared `*HookFieldProps` base types are all retained, since they have no per-field Parameters table and are referenced as link targets. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Two reference-IA changes for hook pages, plus the source cleanup they enable. Grouping: collapse the leftover kind-based buckets (Variables, Interfaces, Type Aliases) — everything not already sorted into a named section — into a single dynamically-derived `## Utility Types` group, alphabetized across the merged set. `Utility Hooks` stays its own section. Because the grouping is now derived from the type graph, the hand-written `@group Utility Types` (and `@interface`) annotations on the employee-details types are redundant and removed. Slots: hooks now use a single `advanced` slot instead of overview/appendix. A hook GUIDE.md is collected wholesale (slot tags ignored) and nested under one `## Advanced` section at the page end, its headings bumped one level. Flows keep the overview/appendix model unchanged. Cleaned the now-redundant slot tags from the one hook guide (useEmployeeStateTaxesForm). Regenerated docs/reference. Router tests updated for the collapsed group. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Make the hook documentation model fail the build on incomplete hooks rather than degrading silently. A function is recognized as a hook when its return union references HookLoadingResult; once recognized it must resolve all three pillars — props, returns (the ready-state interface), and an @example — or extraction throws HookModelError. Form hooks must additionally resolve fields, to either a flat interface map or an array of per-group bundles (inline or via a named alias). Supporting changes: - Add @example to the four hooks that lacked one (useHomeAddressForm, useWorkAddressForm, useContractorAddressForm, useContractorDetailsForm) so every hook page now documents usage. - The fields validation distinguishes a flat interface (drives the quick-reference table) from an array alias like `StateTaxFields = StateTaxFieldsGroup[]`, which is documented in its own right rather than inlined — so useEmployeeStateTaxesForm's named Fields alias resolves on the page instead of dangling. Regenerated docs/reference. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Form hooks whose Fields type arg is a named array alias (StateTaxFields = StateTaxFieldsGroup[]) now get a ## Fields section with the alias at H3, matching the flat-interface hooks, instead of the alias falling into ## Utility Types. Enforce the loud-fail contract end to end: a form hook always gets a Fields section. The model requires Fields to resolve to a flat interface or a named array alias (an inline unnamed array now throws), and the theme throws rather than silently skipping when neither shape renders. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Extract the inline arms of StateTaxQuestionFieldEntry into named, exported
per-variant types (SelectStateTaxQuestion … DateStateTaxQuestion) and export
SharedQuestionMetadata, all re-exported through the barrels. The Fields
section's source-of-truth spine (StateTaxFields → StateTaxFieldsGroup →
StateTaxQuestionFieldEntry) is followed by the six question variants.
Add a @groupWith {@link X} block tag: a layout directive that renders a type
immediately after sibling X in its group (chained). Registered in tsdoc.json
and typedoc blockTags; consumed by the theme's orderByGroupWith and stripped
from output after consumption (mirrors @components). 'Fields' added as a valid
@group.
The *StateTaxFieldProps live in Utility Types (override detail, not core to
rendering) and are now interfaces extending BaseStateTaxFieldProps, so their
full prop surface renders in one table.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Separate the manually-rendered ## Fields entries with the same `***` divider the standard members partial uses between siblings. Render each Fields interface that extends a base (the *StateTaxQuestion variants extending SharedQuestionMetadata) with the common-props treatment: drop the Extends block, table only the interface's own props (type/Field), and summarize inherited props in an "Also includes … from Base" footer. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Drop the Hooks sidebar category and its three orphaned per-hook entries (now superseded by the autogenerated reference pages). Keep the hooks/hooks overview as a top-level doc link beside Workflows Overview. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Carve the form-shape types out of Utility Types into a dedicated `## Form` section, ordered before Fields since the field entries are referenced from the form types. Membership is derived structurally from the type graph (getFormSectionTypes), never by name: the form data and fields-metadata types are the BaseFormHookReady type arguments, the form-fields alias is an indexed access into the ready interface, and the form-outputs alias is a direct re-alias of the form data type. Only the hook page's own exports are grouped, so a hook taking the default metadata type doesn't leak the section onto the shared utilities page. reorderHookSections now ranks Form and Fields explicitly and runs again after the Fields section is injected, so the order holds for both the flat-interface and array-alias Fields shapes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
dropFieldPropsAliases searched for a `## Type Aliases` section, but hook pages collapse Variables/Interfaces/Type Aliases into `## Utility Types`, so the pass silently no-op'd and every flat field's `*FieldProps` alias lingered despite being fully inlined into that field's Parameters table. Point the pass at the `## Utility Types` heading. Only flat field components (props inlined into the field's Parameters) are affected. Array-shaped fields (e.g. state-tax question variants) reference their `*FieldProps` as `ComponentType<…>` link targets and document them nowhere else, so those aliases are correctly left in place. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…nMessages Both interfaces are already tagged @public and back the state-tax field override props, but neither was re-exported through src/index.ts — api-extractor flagged them as forgotten exports and TypeDoc rendered the variants' `Extends` reference (and the validationMessages type) as dead, unlinkable names. Surface them through the hook barrel so the override surface is documented and linkable: partners overriding a field's validation messages can now see the shape and error-code keys of StateTaxValidationMessages. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…face Add a `tsdoc-coverage/require-form-data-interface` ESLint rule (auto-fixable) that requires every `*FormData` object type alias whose body references `z.infer` to carry the `@interface` TSDoc modifier, and apply it across the form-hook schemas. These aliases are computed shapes (`{ [K in keyof typeof fieldValidators]: z.infer<…> }`), so without `@interface` TypeDoc renders them as an opaque mapped-type expression; with it, TypeDoc resolves the shape via the checker and renders a property table — matching what the expand-DTS build already produces in the published types. Enforcing at lint time keeps it from being forgotten on a new form hook instead of relying on a manual tag (which several hooks already carried ad hoc). FormData stays in the `## Form` section because it's detected via the BaseFormHookReady type argument, which resolves whether it's an alias or an interface. The other form-shape aliases are unaffected — FormOutputs/FormFields/ FieldsMetadata stay as reference links. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Remove the model-driven `## Form` group (getFormSectionTypes, the @group Form stamping, the SYNTHETIC_GROUP_ORDER entry, and the Form/Fields reorder ranks). The form-shape types (FormData, FieldsMetadata, FormFields, FormOutputs) return to Utility Types. The `@interface` lint rule stays, so FormData still renders as a property table in place rather than an opaque mapped-type expression — that was the part worth keeping. Section order returns to Example → Remarks → Props → Returns → Fields → Utility Types. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Lift field validation types — the error-code types passed as a field's TErrorCode / TOptionalErrorCode generic — out of Utility Types into a dedicated Validations section, led by the full code maps (the XxxErrorCodes const and its derived XxxErrorCode union) they are built from. Membership is derived from the type graph (transitive closure over field generics, never name conventions). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A hook's ready state declared as `type X = BaseHookReady<…>` is a TypeAlias reflection, not an Interface, so the docs model can't resolve it and the hook page fails to generate (hit when main's useContractorDocumentsList merged in). Add `tsdoc-coverage/require-hook-ready-interface`: a type alias built on BaseHookReady/BaseFormHookReady must either be an `interface … extends …` (when it intersects extra members) or carry `@interface` (when it's a pure reference; auto-fixable). Apply the @interface fix to useContractorDocumentsList and regenerate the affected reference pages. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Rework the typedoc-custom Fields rendering so every form hook documents its
fields from their `*FieldProps` types rather than exported component functions.
Each field gets its description, a generated JSX usage example pre-filled with
the field's own `validationMessages` codes, and an expanded props table; field
groups (`PreparerFieldGroup`) and arrays-of-entries (`splits`) are handled, and
the index table sorts required-first then optional/can-be-undefined.
- theme: props-type-driven `## Fields` section; normalize `typeof XxxField` and
`ComponentType<XxxFieldProps>` to a single display; resolve validation codes
across all four code-definition forms (`typeof X.CODE`, indexed-access,
`keyof Pick<…>`, literal unions); drop `validationMessages` when a field has
no codes; hard-fail on unrenderable field shapes
- router: inline exported field-component functions (now documented via Fields)
so they don't render duplicate subsections
- source: annotate useSplitPaymentsForm's return as its result alias; drop
obsolete `{@link XxxField}` references from Fields member comments
Published SDK surface is unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Regenerated by `npm run derive` for the props-type-driven Fields rendering. The API report reflects useSplitPaymentsForm's return type now naming its `UseSplitPaymentsFormResult` alias (structurally identical union). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The first Fields key to use a group (e.g. Preparer1) now renders every sub-field, each with its own validationMessages; later keys reusing the same group (Preparer2–4) point back to it with an inline reference. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
I made some changes to the API surface to export a few more underlying symbols / give full names to some inlined union types and change types to interfaces for better docs / more accurate descriptions
There was a problem hiding this comment.
Side effect: I changed excludeNotDocumented to false, so any undocumented props that were hidden before now are showing up.
There was a problem hiding this comment.
This helps us parse the parts of a hook so we can more easily render a highly custom page based on the shape of hooks. If we can't find something that we expect a hook to have (e.g. fields for a form hook) this should throw a big error so that your build fails and you know what's missing
There was a problem hiding this comment.
Since I'm requiring some form hooks to follow a super specific pattern, I added lint rules where I could to auto-fix it and apply the changes
SYNTHETIC_GROUP_ORDER lacked the renamed 'Component Props' group title, so the custom router appended it after 'Utility Types' instead of following the native groupOrder. Add it ahead of 'Utility Types' to restore intended order. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
What
Overhauls the auto-generated hook reference pages (
docs/reference/**/hooks/*) and the TypeDoc-custom plugin that builds them, so every form hook documents its fields richly and consistently from the type graph — no hand-maintenance.How to review
The diff is large (~9k lines / 72 files) but splits cleanly:
Read closely — the actual logic (~9 files):
docs-site/plugins/typedoc-custom/hook-model.ts— derives the hook doc model from the type graphdocs-site/plugins/typedoc-custom/router.ts+theme.ts+utils.ts— rendering (field example generation,validationMessagesresolution, groups / arrays-of-entries)docs-site/plugins/typedoc-custom/sdk-router.test.ts— covers the aboveeslint-rules/tsdoc-require-{form-data,hook-ready}-interface.ts(+ tests) — the new hard-fail enforcementSpot-check, don't line-read — generated output (27 files):
docs/reference/**are emitted bynpm run derive. Trust the screenshots + a local preview over reading the markdown line-by-line.Why
src/componentsshows 28 files: almost entirely additive TSDoc + type/interface exports so hooks satisfy the new lint rules. The one file with substantive restructuring isuseEmployeeStateTaxesForm(fieldProps.ts/fields.tsx) — worth a closer read; the rest are doc/type-only, no runtime behavior changes.Note: one commit (
58f24dd8) is a pure prettier 3.9.4 reformat — no content changes, safe to skip in review.Preview locally
Highlights
Page structure
UseXxxResultshown as its named alias## Fieldssection (the bulk of the work)*FieldPropstype —typeof XxxFieldandComponentType<XxxFieldProps>are normalized to the same display, so no component-function value exports are required (the router inlines any that exist to avoid duplicate subsections)validationMessagesdynamically pre-filled with that field's actual error codes (resolved acrosstypeof X.CODE, indexed-access,keyof Pick<…>, and literal-union code definitions; required + optional generics; wraps when >2 codes);validationMessagesis omitted when a field has no codesPreparerFieldGroup) render a complete worked example on first use, with later reuses cross-referencing it; arrays-of-entries (splits) render the entry table + bound-field propsFieldComponentalways shown*FieldProps/entry types out of "Utility Types" into Fields (anchors preserved)Tooling / lint
expandParameters, include undocumented symbolsVerification
npm run deriveclean (0 errors)useSplitPaymentsFormnaming itsUseSplitPaymentsFormResultreturn alias — a structurally identical union)🤖 Generated with Claude Code