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·FieldHelper·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. -
AssistProvider+Assist— one provider around the form, andAssistaround three controls: the title'sInput(whole titles as chips under it), the notice'sTextarea(a continuation at the caret, Tab to take it) and the TagsTagsInput(tags to add, never one already there). The control decides how the proposal is drawn; the showcase writes nothing per field but the value. The provider'scontextis the writ as it stands, read when a field asks, so a title is proposed against the notice the poster has written since.The model is told which field it is filling by the field itself — its label, and its description through
aria-describedby. That is why every row's description is aFieldHelperrather than aFieldDescription: Ark wires the helper to the control and leaves a plain paragraph out, so aFieldDescriptionwould be text the reader sees and the model does not. The model is@kanzo-tech/llm/testing's double answering by field name, so the ✨ works with no key and no network;localModelin its place changes nothing else. -
Steps/Tabs— the form re-lays-out live as sequential Cards, Tabs, or a Steps wizard. -
The preferences panel (extended) — the library's own
PreferencesRoot+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 modelImports come from two packages: @kanzo-tech/ui for the form and chrome, and @kanzo-tech/ai
for Assist — a sibling package, so a form with no ✨ installs no model SDK.