You were asked for a settings page, a module's parameters, a wizard step that configures a run, a "form for the X module". This is what that request brings along.
Audience: an agent (or person) asked to build the settings surface of a module, a run configuration, or any form that a schema could describe.
Read the bundle first. It is the completeness contract: every row is something
the request implies whether or not anyone said it. The rule that decides the
whole recipe is row 1: if a JSON Schema in the kit's subset can describe the
form, the form is SchemaForm over that schema and the rest of the bundle is
already handled; if it cannot, the schema is the thing to fix (add the field
shape to the subset through the spec), never a hand-built second anatomy.
Traits involved: 13 forms and fields, 05 cards and surfaces, 04 loading
and states, 09 page composition, 02 buttons, 10 overlays, 11 motion.
The bundle#
| # | Comes along | Default | Law | Deviate when |
|---|---|---|---|---|
| 1 | The form itself | SchemaForm over the module's params schema (packages/ui/docs/schema-form.md); the schema is the source of the fields, the widths, the sections and the messages | 13 §A1, 13 §A11 | a one-off field with no schema anywhere composes the same anatomy by hand from Input and Select; never a second anatomy |
| 2 | Schema conformance | lintSchema(schema) returns ok in a test; the manifest lint runs it in CI | 13 §B | no; a schema outside the subset is refused by name at mount, never rendered with a fallback |
| 3 | Field widths | derived from the type (numerics and short enums a third, segmented of three or four a half, text full); x-width for the exception | 13 §A2 | never full-width numerics |
| 4 | Sections | Card per x-section, declared order, advanced collapsed with the changed count | 13 §A3 | a form of five or fewer fields has no sections |
| 5 | Branching choice | x-widget: mode-chooser cards over a discriminated oneOf; branch defaults on switch, the other branch pruned at submit | 13 §A4 | a flat three-way choice with no field consequences is segmented |
| 6 | Repeating rows | x-widget: table for short rows, list for rich rows; add at the bottom, duplicate and remove trailing, cards under sm | 13 §A5 | rows seeded from the file (x-seed-from) hide add and remove |
| 7 | Viewer tier | tier prop from the session; x-tier fields hidden below their tier and chipped admin or team above customer | 13 §A1 | never a second gate in the page around a field the schema already tiers |
| 8 | Dependent fields | x-when on a sibling or $settings.<key>; hidden values dropped from submit()'s return | 13 §A8 | no |
| 9 | Options from data | x-options-from and x-suggestions-from through one resolveOptions(path, params); a loading line, a Retry fallback, pending while a required option list loads | 13 §A7, 04 §A | static enums stay in the schema |
| 10 | Context from the file | context map for x-seed-from, x-hint-from, x-variables-from (columns, samples, distinct values) | 13 §C | no |
| 11 | Validation timing | blur once touched, live after an error, all on submit, first error focused | 13 §A7 | no |
| 12 | Message voice | plain sentences from the keyword table; never the schema description as an error | 13 §A7 | no |
| 13 | Server checks | validate(value) returning path-keyed issues rendered in place; the form is pending while it runs | 13 §A7 | a form with no server semantics passes none |
| 14 | Warnings | x-warn, suggestion misses and the app's status line warn and never block | 13 §A7 | no |
| 15 | The action bar | the host's: a primary button reading isValid, pending, isDirty, disabled only when it can explain itself; submit() returns the pruned value or null | 13 §A8, 02 §A4 | no; the form never renders Save |
| 16 | Presets | the app's picker (a template row, a Select) plus presets and activePreset; a Reset to template calling resetToPreset(); dirty measured against the active preset | 13 §A10 | a module with no templates passes none |
| 17 | The aside | the module's preview or summary through aside, beside the form at 1280 and below it at 375; it runs against unsaved state and says so | 13 §C | a module with nothing to preview has no aside |
| 18 | Review step | a wizard's last step is mode="review" with onEditSection returning to the field; the job page shows mode="snapshot" over the frozen value | 13 §A6 | no |
| 19 | Loading | loading for the shape-matched skeleton while the schema or the value loads | 13 §A9, 04 §A2 | never a spinner |
| 20 | Both themes | exercised in versable-dark and versable-light at 1280 and 375 before done | 14 | no |
Worked example: the four V1 console modules#
Each module is a committed schema under docs/plan/65-schema-form-fixtures/
and a pattern page that renders it with the console's slots stubbed:
/patterns/module-content-generation (rows 4, 5, 6, 16, 17, 18),
/patterns/module-image-generation (rows 5, 14, 17),
/patterns/module-attribute-normalization (rows 6, 9, 10),
/patterns/module-part-type-matching (rows 3, 7, 10). The component page
/components/schema-form shows every widget alone.
Do-nots#
The forms trait's list (docs/design-language/13-forms-and-fields.md), in the
recipe's voice, plus the one that belongs to the recipe alone.
Do not put a native select where a mode has behavioural weight. A choice that changes which fields exist is a row of cards.
Do not mark a required field with a dot or a bare asterisk. The mark is the kit's RequiredMark, a red asterisk with a "Required" tooltip, centred on the label row; optional fields say nothing.
Do not stretch a numeric field to full width. Numbers and short enums are a third; the widths are the rhythm.
Do not show a spinner as the form's loading state. The skeleton is the form's own shape, labels as themselves and controls as bars.
Do not reuse the schema's description as an error message. Errors are plain sentences from the keyword table.
Do not fall back silently when a schema is outside the subset. The form refuses by name; the schema is what gets fixed.
Do not put layout keys in a schema. x-width and x-section are the whole allowance; pixels, columns and colours belong to the kit.
Do not build a second field anatomy beside the kit's. A hand-composed field copies the shape, or the shape is added to the canon first.
Do not render a Save button inside the form. The action bar is the host's, and its button reads the form's validity.