Metadata form
Posting a contract to the board — grouped Cards, repeatable and nested fields, live serialisation, and a rule engine that only ever hands the library a boolean and a node.
A realistic contract-posting screen at full viewport, the form a quartermaster fills before work
and, like the discovery showcase, built as a Workspace on the Shell regions. ShellHeader
carries ONE row: the title, the Contract switcher that says which posting this is, and then
Share / Source / Output / validation / Preferences. The standing-orders switcher is
not up there — it lives in the Source panel's own header, against the document it replaces.
Below it a three-column Resizable workspace reads left→right exactly like the breadcrumb:
the hall's standing orders (a leading ShellAside) → the editable form (ShellMain, the
single <main>, groups The work · The posting · The terms · The party) →
generated Output (a trailing ShellAside). Each side column toggles independently from its
header button and is drag-resizable.
It exists to answer the question a single field never can: does a whole form hold together — required markers, repeatable rows, nested objects, dates, AI assistance and validation — without the library growing a domain. Switch the Data selector from Empty to Q-1041 to watch the header badge flip from 3 issues to Valid and the Output column reserialise live; open Source and Output together and drag the splitters to rebalance the three columns.
What it composes
Almost nothing here is new — it is existing parts, arranged:
ShellRoot/ShellHeader/ShellBody/ShellMain/ShellAside+Resizable— the Workspace frame. Exactly one<main>; the Source and Output columns are<aside>landmarks (ShellAside side="start"/side="end", logical and RTL-safe) rendered only when open, sized by a drag-resizableResizablesplitter keyed on the open-set.Field·FieldLabel·FieldError— every row. A required field shows a red*(FieldRequiredIndicator); an invalid one getsField's own error channel.FieldArray— the repeatable fields (what to expect, who to ask, the party), each row keyed by a stable id so editing never steals focus.DatePicker— the posting and due dates. The ISO-string adapter the form wants is written out locally in the showcase, which is where that seam belongs.SuggestRoot/SuggestMark/SuggestList— the ✨ beside Tags, streaming deduped candidates into a strip under the field while it has focus.CompleteRoot+CompleteGhost— the title and the notice, with Tab-to-accept inline completion over a plainInputandTextarea.CodeEditorhas no completion prop and never had one; the ghost is a compound composed over a pure primitive.Steps/Tabs— the form re-lays-out live as sequential Cards, Tabs, or a Steps wizard.Preferences(extended) — the library's ownPreferencesRoot+PreferencesPanel(its "Preferences [P]" drawer) is reused as-is and given customchildren: a LayoutSegmentGroupand displaySwitches that drive theShellMainform live, above the library's own theme axes. Flat exports, per the RSC boundary.HoverCard+Badge— the header validation summary, listing every failing field. The Output column is the generated writ and record, kept distinct from validation.
Where the domain lives
The library is domain-free, so the entire rule engine lives in data.tsx — a plain
validate(values) that walks a posting and returns Issue[] with resolved,
tri-state (violation / warning / info) messages. That is the forms decision made visible: the
library models validation as a boolean + a ReactNode and nothing more, so a violation
flows through Field's invalid + FieldError, while the non-blocking severities the library
deliberately does not model are product-rendered nodes. Where the boolean came from — here a fake
engine, in the product a real one over the whole archive — is never the library's business.
Source
docs/showcases/metadata-form/
├── default.tsx — the Workspace: Shell chrome, the resizable Source/form/Output columns, the
│ four groups, and the three live layouts driven by the Preferences drawer
└── data.tsx — the rule engine, three seeded postings, the world's vocabularies,
the writ / record serialisers, and the faked AI streamsImports come from two entry points: @kanzo-tech/ui for the form and chrome, and
@kanzo-tech/ui/editor for CodeEditor — a subpath, so the base bundle never carries
CodeMirror for consumers who do not use it.