@lotics/ui 11.8.10 → 12.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/AGENTS.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # @lotics/ui — the UI reference index
2
2
 
3
+ > **Upgrading to v12 from 11.x.** Two exports were removed: **`CommentComposer`** (compose
4
+ > comments with THE kit `Composer` — attach via `actionsButton`/`files`; `CommentList` is
5
+ > unchanged) and **`DetailRow.info`** (field annotations are explicit — use the new
6
+ > **`DetailRow.description`** under the value and **`DetailRow.error`**, the `FormField`
7
+ > vocabulary; heading/card `info` ⓘ props are untouched). Visual behavior changes:
8
+ > `SubsectionStack` is SPACE-ONLY (32px beat, no hairlines — titles carry the grouping),
9
+ > `Section` heading gap 12→16, `DetailTable` row gap 6→8, `DetailRow` rows top-align with
10
+ > label/control/trailing centered on the first control line, `Timeline` grew to 32px
11
+ > discs / 16px icons / 40px minimum rows, `TextInputField` paints a WHITE surface on any
12
+ > background (it was transparent — identical on the white canvas, visible on tinted
13
+ > panels), and the inline editors' resting chip gained 6px vertical padding (identical for
14
+ > one-line values; multi-line resting content now breathes).
15
+
3
16
  > **Upgrading to v11 from ≤10.x — the type scale was re-slotted.** The display sizes gained the
4
17
  > missing `##` step: `xl` is now 22/24 (section title), `xxl` is 28/32 (page/record title — the
5
18
  > OLD `xl` look), and the new `xxxl` is 32/48 (hero numbers — the OLD `xxl` look). Migrate an
package/docs/catalog.md CHANGED
@@ -170,8 +170,8 @@ gap-spaced region + `SectionHeading`/`SectionHeadingTitle`/`SectionHeadingMeta`;
170
170
  component — children are the body), `Subsection` (+ `SubsectionHeading`/
171
171
  `SubsectionHeadingTitle` — the named group INSIDE a section, `###` lg-semibold title),
172
172
  `SectionStack` (the flat page's content column — owns the fixed 56px beat + hairline between
173
- top-level blocks), `SubsectionStack` (the same law one step tighter — fixed 24px beat +
174
- hairline between a section's `Subsection` groups), `SectionCard`, `PageHeader` /
173
+ top-level blocks), `SubsectionStack` (a section's `Subsection` groups — fixed 32px beat,
174
+ SPACE-ONLY: titles carry the grouping, hairlines stay at the section level), `SectionCard`, `PageHeader` /
175
175
  `PageContent`, `Stack`, `Spacer`, `Divider`, `Accordion`, `Tabs`, `SegmentedControl`,
176
176
  `Stepper`, `DangerZone` (the destructive section — delete/archive — set apart at the bottom
177
177
  of a record/settings surface).
@@ -396,8 +396,9 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
396
396
  `arrow-left` `MenuButton` reads as another nav ITEM, not an exit). `onPress` +
397
397
  `accessibilityLabel` (defaults to `label`, else the locale's `nav.back` — "Back" /
398
398
  "Quay lại"). Bare, it's the chevron-left `IconButton` (lg, secondary) heading a
399
- screen/panel; pass **`label`** to name where back GOES ("Danh sách") and it becomes ONE
400
- pressable over glyph + words never an icon button beside unclickable prose.
399
+ screen/panel; pass **`label`** to name where back GOES ("Danh sách") the SAME
400
+ circular glyph with the destination beside it, ONE pressable over disc + words —
401
+ never an icon button beside unclickable prose.
401
402
  - **`link`** — `Link`: the EXTERNAL hyperlink — fixed underline+blue + `role="link"`;
402
403
  `onPress` only (the consumer wires the opener).
403
404
  - **`text_link`** — `TextLink`: underlined text that's optionally an `onPress` action or an
@@ -489,7 +490,9 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
489
490
  ### Form & text inputs
490
491
 
491
492
  - **`text_input_field`** — `TextInputField`: the standard text input (multiline grows via
492
- the auto-grow engine; `shortcut` renders a `ShortcutBadge`).
493
+ the auto-grow engine; `shortcut` renders a `ShortcutBadge`). Its surface is WHITE on any
494
+ background — on a tinted panel the input still reads as an open well, never blending into
495
+ the tint; override via `style` only when a surface genuinely wants otherwise.
493
496
  - **`number_input`** — `NumberInput`: numeric input; `format` for currency/units.
494
497
  - **`search_input`** — `SearchInput`: the search box for toolbars/filters.
495
498
  - **`form_field`** — `FormField` + `useFormField`: label / description / error / `optional`
@@ -567,10 +570,15 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
567
570
  label+value row for drawer/peek detail; in FORM mode (`labelWidth` set) the value column
568
571
  FILLS the row so a stack of inline editors all span the same width + none jumps wider on
569
572
  edit; optional `trailing` slot renders a right-side action/badge after the value (units
570
- belong IN the value via `InlineNumberInput format`); optional `info` renders an ⓘ popover
571
- hugging the labelthe row-label member of the one `info` vocabulary (`CardHeaderTitle` /
572
- `SectionHeadingTitle` / `SubsectionHeadingTitle`); pass `undefined` to hide it (callers may
573
- gate it on their own state). `DetailTable`: the compound parent of
573
+ belong IN the value via `InlineNumberInput format`). The FIELD-ANNOTATION vocabulary (same
574
+ names + meanings as `FormField`), always EXPLICIT a row never hides guidance behind an ⓘ:
575
+ **`description`** = persistent guidance, under the VALUE (stacked mode mirrors the form
576
+ order label · description · control); **`error`** = field-level failure under the value,
577
+ danger + alert semantics (the `Inline*` editors already render their own transient save
578
+ errors — don't wire both); invalid/cross-field STATE = a co-located `Callout`; **`flat`**
579
+ on a flat-text value row (`InlineStatic`, plain `Text`) tucks the annotation up by the
580
+ control band's slack so its gap matches a chip row's.
581
+ `DetailTable`: the compound parent of
574
582
  a row STACK — `labelWidth` (default 130) / `trailingWidth` / `minHeight` (default 40, the
575
583
  inline-control grid) declared ONCE + the 6px row gap; with `trailingWidth` every row
576
584
  reserves the trailing column so value cells share one width and trailing items align at
@@ -645,17 +653,23 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
645
653
  filled check when done, distinct from the square checkbox; the task/to-do/checklist
646
654
  toggle.
647
655
  - **`checklist`** — `Checklist` + `ChecklistRow` — the record-scoped checklist COMPOUND: it
648
- owns GEOMETRY only (row minHeight 32, gap 12, ring/title alignment, ONE `trailingWidth` so
649
- assignee cells column-align) while content stays composed — `control` takes the
650
- `CheckCircle` (omit onChange = read-only ring), children the struck transparent
651
- `InlineTextInput`, `trailing` an `InlineMemberSelect`, `menu` the row's ⋯ options
656
+ owns GEOMETRY only (row minHeight 32, gap 12, control/title alignment, the `meta`/
657
+ `expansion` indent, ONE `trailingWidth` so assignee cells column-align) while content
658
+ stays composed — `control` takes the `CheckCircle` (omit onChange = read-only ring; a
659
+ PICKER list puts a `CheckboxInput` here and sets `controlWidth={24}` so the indent
660
+ tracks), children the struck transparent `InlineTextInput` or plain `Text`, `trailing` an
661
+ `InlineMemberSelect`, `menu` the row's ⋯ options
652
662
  (`{items: ActionMenuItem[], accessibilityLabel}`) — Delete lives BEHIND the menu,
653
663
  danger-styled and last, never a bare ✕ (omit on read-only rows). NARROW surfaces (a
654
664
  drawer/peek checklist) put the editors on the `meta` line instead of `trailing` — the
655
- second line indents past the ring so the TITLE keeps the full width; wide surfaces use
656
- `trailing`; never both. SUGGESTIONS are never rows: offer the commons as `SuggestionChip`s
657
- under the list. Close the list with `CaptureRow`. There is deliberately NO monolithic Task
658
- component richer task-management rows compose their own anatomy directly.
665
+ second line indents to the title's text edge so the TITLE keeps the full width; wide
666
+ surfaces use `trailing`; never both. `expansion` is a BLOCK slot under the row on the
667
+ same indent a transient inline fill editor, a drill-down rendered only while open
668
+ (`tpl_record`'s Document set rows are the worked example). SUGGESTIONS are never rows:
669
+ offer the commons as `SuggestionChip`s under the list. Close the list with `CaptureRow`.
670
+ There is deliberately NO monolithic Task component — richer task-management rows compose
671
+ their own anatomy directly. Hand-rolling this checkbox-row anatomy in a template is how
672
+ alignment drifts — the compound exists so it can't.
659
673
  - **`suggestion_chip`** — `SuggestionChip`: the dismissible SUGGESTION pill — an item the
660
674
  record could have but doesn't yet (a common task, an expected line) as a `Chip` whose
661
675
  press MATERIALIZES it (plus glyph + label, one tuned anatomy) and whose ✕ refuses it;
@@ -892,7 +906,8 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
892
906
  expands for long text + attachments (`pills` + attachment slots, controlled or
893
907
  uncontrolled `value`, `onSend`/`onStop`, `sendDisabled` override).
894
908
  - **`agent_run`** — `AgentRun` + `AgentRunStep`/`AgentRunItem` + `resolveToolMeta`: the live
895
- streaming work feed.
909
+ streaming work feed. AI work ONLY — deterministic work (template fill, an export) shows a
910
+ `loading` state on its trigger and delivers at once; dressing it as an agent run is theater.
896
911
  - **`agent_progress`** — `AgentProgress`: its compact, floating, expandable form — a
897
912
  composer's working state.
898
913
  - **`confidence`** — `Confidence` + `ConfidenceLevel` + `levelFromScore`: calibrated
@@ -916,8 +931,11 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
916
931
  - **`finding`** — `Finding` + `FindingComparison` + `FindingSeverity`/`FindingLabels`: one
917
932
  ranked AI-check insight — severity word · title · detail · `Sources` · children slot; the
918
933
  expected-vs-actual body with the emphasized delta; the `finding` locale slice.
919
- - **`comments_thread`** — `CommentList` + `CommentComposer` + the
934
+ - **`comments_thread`** — `CommentList` + the
920
935
  `ThreadComment`/`ThreadMember`/`ThreadFile` types: the record comments thread.
936
+ Composing is THE kit `Composer` (never a bespoke comment box): attach via its
937
+ `actionsButton`, staged files in its `files` slot; render attachments with
938
+ `renderFiles` (a pressable `FileGrid` → gallery preview — see tpl_record).
921
939
  **Limitation:** labels are not provider-wired — pass `CommentListLabels`.
922
940
 
923
941
  ### Utility hooks & plumbing
@@ -63,8 +63,9 @@ restyle a heading level per-page.
63
63
  `SubsectionHeading` › `SubsectionHeadingTitle` (`###` — lg semibold, with the same `info` ⓘ
64
64
  affordance as `SectionHeadingTitle`; siblings — a `Badge`, a
65
65
  `SectionHeadingMeta`, an action — ride the heading row's right edge). Sibling subsections stack
66
- in a **`SubsectionStack`** (the same hairline law one step tighter: a fixed 24px beat + hairline
67
- between groups no margins, no hand-rolled dividers); a headingless `Subsection` is the
66
+ in a **`SubsectionStack`** (a fixed 32px beat, SPACE-ONLY subsection titles carry the
67
+ grouping; hairlines belong to the SECTION level, one rule per altitude; no margins, no
68
+ hand-rolled dividers); a headingless `Subsection` is the
68
69
  section's lead group. Group leads INSIDE a subsection's rows are `md` medium at most. Do NOT
69
70
  hand-roll `Text weight="semibold"` group leads inside a section, and never promote a subsection
70
71
  to its own section-level heading just to separate it.
@@ -377,6 +378,58 @@ All view controls are 40px tall (`CONTROL_HEIGHT`), `sm` labels, in ONE wrapping
377
378
  `danger` (and its quieter `danger-secondary`) marks destructive — that's the whole axis. No
378
379
  "success"/green button. Decision UIs put positive/negative color on the STATUS (dot) and verdict
379
380
  (colored `Text`), not the buttons.
381
+ - **Create buttons carry NO `+` icon** — "Add fee", not "+ Add fee". Section-heading creates are
382
+ `secondary` (one primary per scope, reserved for the scope's main verb); a create promotes to
383
+ `primary` only where creating IS the surface's job — the register page's New, an empty state's
384
+ action. (The icon-only `+` `IconButton` — a composer's attach — is a different thing and keeps
385
+ its glyph.)
386
+
387
+ ## The action-gating law — disabled + the reason as a co-located Callout
388
+
389
+ A not-ready CTA is **DISABLED**, and the reason is a **persistent co-located `Callout` at the
390
+ gate's SCOPE, once** — never prose beside the button, never revealed only on press:
391
+
392
+ - A **local inconsistency** (a charged line without a payment method) → a callout in ITS group,
393
+ beside its own disabled CTA.
394
+ - A **broken record-level premise** (no customer attached, invalid tax ID) → ONE callout at the
395
+ section top — not repeated beside every affected button.
396
+ - **Self-evident empties stay silent**: an empty band's disabled Issue, a composer's Send on
397
+ empty input, a dialog's confirm until its one required adjacent field is filled — nothing to
398
+ explain, so nothing is said.
399
+ - An **expected not-yet** (a stage that simply hasn't arrived) is not a problem — it gets no
400
+ callout; if that leaves a disabled button undiscoverable, remove the gate rather than narrate
401
+ it (a filled surface whose CTA stays silently dead is a trap).
402
+ - A **DEGRADED-but-valid action is not a gate.** When the action can still produce a legitimate
403
+ outcome (generating forms whose optional fields print BLANK and get filled by hand), the CTA
404
+ stays **ENABLED**: the co-located callout states the CONSEQUENCE (not a block), and the press
405
+ CONFIRMS via `Alert` — name what's missing, offer "Cancel" (back to the inline fixes) and the
406
+ proceed verb. Reserve DISABLING for actions that cannot produce their outcome at all.
407
+
408
+ ## Field annotations & linked records
409
+
410
+ - **The field-annotation vocabulary** (same names + meanings on `FormField` and `DetailRow`,
411
+ always EXPLICIT — never a hidden ⓘ on rows): **`description`** = persistent guidance (under
412
+ the VALUE on a horizontal row — the label column has no room for prose; stacked mode wears
413
+ the form order label · description · control); **`error`** = field-level failure under the
414
+ value, danger + alert semantics (the `Inline*` editors already render their own transient
415
+ save errors); cross-field / record-level STATE = a co-located `Callout`. Rows share ONE
416
+ alignment law: the row top-aligns and label · control · trailing each center within the
417
+ first control line — a tall value block never drags the label. A FLAT value row
418
+ (`InlineStatic`, plain `Text`) sets **`DetailRow flat`**: the annotation tucks up by the
419
+ control band's slack so the perceived gap under the text equals the gap under a chip —
420
+ the band's invisible bottom half must never read as a hole above the description.
421
+ - **A LINKED record never renders as field rows** — another record's data must not read as
422
+ fields on THIS one. The reference shape (`tpl_record`'s Customer/Handoff, inline — no
423
+ wrapper component): a BORDERED, fully pressable box (glyph anchor · identity · facts
424
+ stacked vertically) whose press opens the record's detail drawer (read-only rows + the
425
+ navigate verb); the action row sits INSIDE the box — destructive LEFT in danger, go-to
426
+ RIGHT. Editing the linked record happens on ITS page, never here. The press ANATOMY:
427
+ the box is a `PressableRow` (role-less, non-focusable surface — hover wash spans the
428
+ nested verbs) with the keyboard DOOR — an EMPTY absolutely-positioned `role="button"`
429
+ sibling carrying the tab stop, the accessible name and the focus ring — beneath the
430
+ content, and the interior verbs lifted above it via `zIndex: 1`. NEVER `role="button"`
431
+ on a container with interactive descendants: a button must not contain a button
432
+ (invalid HTML, a hydration error, and AT double-announcement).
380
433
 
381
434
  ## Color discipline — solid / tint / ramp + ONE accent
382
435
 
@@ -454,11 +507,15 @@ that praises it is marketing.
454
507
  - **Instructions live where action is needed** — empty states, callouts, form-field
455
508
  descriptions: imperative, one concrete step ("Drop the CI + PL here."). A heading never
456
509
  instructs.
510
+ - **NEVER prose beside a CTA.** No consequence narration next to a button ("Handing off moves
511
+ the record to Operations — fields stay editable after") and no label chips beside headings
512
+ ("Collected separately") — the CTA's label names where it goes, and STATE speaks through
513
+ the gating law below, not through standing text.
457
514
  - **Restatement is the second noise mode.** A description that repeats what the screen already
458
515
  shows — the title, the subsection names below it, the table's columns — carries zero
459
516
  information and fails even though it "states content". Useful copy states an **invisible
460
517
  truth**: an invariant of the data, a boundary ("P&L lives in the accounting app, not here"),
461
- a linkage ("the HS code here feeds the customs declaration"), or a consequence ("past the
518
+ a linkage ("the commodity code here feeds the customs declaration"), or a consequence ("past the
462
519
  cut-off, the box misses the sailing").
463
520
  - **The deletion test**: if removing the line changes nothing for the reader, remove the line —
464
521
  `description` is optional for a reason; when no invisible truth is worth stating, omit it.
@@ -19,11 +19,27 @@ in [the templates](./templates.md) (`examples/tpl_*.tsx`).
19
19
  | CHARGES that bill onto documents | [**billing**](#billing--the-invoice-document-is-the-unit) (`tpl_record` Billing section) | the invoice document is the unit |
20
20
  | a record's FEE/charge SUMMARY | [**`Ledger`**](#fee-summary--ledger) (worked example: `tpl_item_list` drawer) | grouped money lines, one emphasized total — no bars/charts |
21
21
  | a multi-value TAG field | [**`Select multi`**](#tag--multi-value-field--select-multi) (`renderSelected` → `Chip`) | chips composed, not a separate control |
22
- | ONE choice from a small visible set | **`ChipGroup` pills** (or `RadioPicker`) | required single-select, one tap, every option visible |
22
+ | ONE choice from a small visible set | **`ChipGroup` pills** (or `RadioPicker` — column when options carry descriptions, row when short) | required single-select, one tap, every option visible |
23
+ | ONE pick from a BIG registry (ports, projects…) | on a RECORD surface: **`InlineSelect searchable`** (search lives in the popover); on a form/attach surface: **`Combobox`** with `value` | an inline row stays an inline editor — a persistent search input breaks the surface's grammar |
24
+ | a YES/NO on a record | **`CheckboxInput`** as the `DetailRow` value | a boolean needs no editor mode |
23
25
  | a STATUS with terminal outcomes | [**disposition**](#disposition--lifecycle-status-is-asymmetric-by-phase) (open → resolve → revise) | guides the decision |
24
26
  | FILES | [**attachment field**](#attachments--a-full-add--preview--delete-field) (dropzone + grid + gallery) | add / preview / delete |
25
27
  | a state TRANSITION mid-flow | [**stage gate**](#stage-gates--tiered-by-weight) (popover / dialog by weight) | right-sized friction |
26
28
 
29
+ **The right-input-per-field law.** A field gets the control its SHAPE wants, never a default
30
+ text box — and on a record surface radio/checkbox render as PERSISTENT controls (the control
31
+ is the best display of the value; prose-shaped values and registry picks stay inline
32
+ editors). Rich `InlineSelect` options carry a description line (`renderOptionContent` — it
33
+ shows in the resting row too; the `data` generic types option payloads). A DEPENDENT field
34
+ renders only while its parent value makes it real — never a disabled ghost row. Worked rows:
35
+ `tpl_record`'s Classification group.
36
+
37
+ **The form-action alignment law.** On an open page, a label-left form's action row rides the
38
+ form's OWN GRID — an empty-label `DetailRow` puts the CTA (and what it produces) exactly on
39
+ the CONTROL COLUMN, the same left edge the user just filled, and inherits stacked mode on
40
+ narrow containers. A right-floated button aligns to nothing; only OVERLAY footers
41
+ (`DialogFooter`/`DrawerFooter`) right-align. Worked: `tpl_record`'s Delivery receipt.
42
+
27
43
  ## Inline edit — the preferred way to edit an existing record
28
44
 
29
45
  When the whole record is editable (a detail/record screen, dense settings), don't wrap it in a
@@ -107,10 +123,14 @@ null). `InlineTagSelect` (multi) needs no `onClear` — an empty set is already
107
123
  ### The row stack — `DetailTable` + `DetailRow`
108
124
 
109
125
  A STACK of rows lives in a `DetailTable` (label · value · trailing laid out like a TABLE:
110
- `labelWidth` / `trailingWidth` / `minHeight` set ONCE on the parent, plus the 6px row gap the
126
+ `labelWidth` / `trailingWidth` / `minHeight` set ONCE on the parent, plus the 8px row gap the
111
127
  zinc-50 chips need) holding `DetailRow`s — set `trailingWidth` when ANY row carries a trailing
112
128
  action/badge, so EVERY row reserves the column and one row's `Copy` button never makes its editor
113
- narrower than its neighbours'.
129
+ narrower than its neighbours'. Rows share ONE alignment law: the row top-aligns and label ·
130
+ control · trailing each center within the first control line — annotations (`description` under
131
+ the value, `error` in danger with alert semantics; both indented to the chip's 8px text inset)
132
+ grow the row downward without dragging the label. The full annotation vocabulary lives in
133
+ composition.md §Field annotations.
114
134
 
115
135
  ### The editability affordance
116
136
 
@@ -127,10 +147,9 @@ border) but stays FLAT and non-interactive, so editable (chip) vs read-only (fla
127
147
  glance and the static value never reads as a disabled input.
128
148
 
129
149
  To hang a right-side action/badge off a row, use `DetailRow`'s `trailing` slot (NOT for units —
130
- "kg"/"$" belong IN the value via `InlineNumberInput format`) — see the "Details" `DetailTable` of
131
- `tpl_record`, which also reads top→bottom as a full record surface (header fields
132
- `DangerZone`). Not every field is a same-height swap — a tag field, a status, or an attachment
133
- grid edit in place too (below).
150
+ "kg"/"$" belong IN the value via `InlineNumberInput format`) — see the "General" section of
151
+ `tpl_record`, which also reads top→bottom as a full record surface. Not every field is a
152
+ same-height swap — a tag field, a status, or an attachment grid edit in place too (below).
134
153
 
135
154
  ## Fieldset form — fields lay out on a RESPONSIVE two-column grid
136
155
 
package/docs/templates.md CHANGED
@@ -20,6 +20,11 @@ the package index is [../AGENTS.md](../AGENTS.md).
20
20
  - **Pick by the job, not the domain.** Templates are scenario-flavoured (a pick run, cash
21
21
  application, a delivery week) but generic-purpose — they teach the shape. Your orders /
22
22
  items / customers screen starts from the template whose *job* matches, whatever the domain.
23
+ **Flavor lives in VALUES, never in STRUCTURE**: section identities, teaching comments, and
24
+ registry keys name the generic PATTERN; mock nouns stay at the common-denominator level any
25
+ goods/services business uses (a service level, a destination, a delivery receipt) — never
26
+ one vertical's jargon (an FCL, an EIR, an HS code). Adapt a template by swapping the
27
+ values; the patterns are what you keep.
23
28
  - The templates encode the composition grammar (band order, toolbar law, color discipline,
24
29
  status weight) as working code — when a template and your instinct disagree, the template
25
30
  wins.
@@ -60,12 +65,16 @@ Laws for any screen where a record's data is edited and a primary action produce
60
65
  2. **Static shape.** The page never changes shape from output-selection state. Conditional
61
66
  rendering is DATA-driven only (a control appears because two stored values differ), never
62
67
  selection-driven. Relevance is an advisory `Callout` with a jump-to-section action — warn,
63
- never hide, never hard-block.
68
+ never hide, never hard-block. One sanctioned disclosure: a TRANSIENT fill-what's-missing
69
+ editor expanding under a just-checked item is progressive disclosure of the gate, not
70
+ shape change — it edits the record field in place and collapses once satisfied.
64
71
  3. **Colocation & ownership.** Every output option (selections, print/export switches) lives in
65
72
  the output section beside its action; a per-entity selection carries its owner's name in its
66
73
  heading. Persisted record DATA never lives there — data belongs in data sections. Misfiled
67
74
  data announces itself as callout special-cases; the exception disappearing is the sign the
68
- model is right.
75
+ model is right. (HOMING is what's forbidden, not repair: the output section may host a
76
+ transient fill editor that saves onto the record field — same storage, the field's home row
77
+ shows the value — rendered only while the field blocks a checked output.)
69
78
  4. **Resolved state.** Controls show the EFFECTIVE state, never raw storage: a derived/suggested
70
79
  selection renders checked; the first manual change persists the full explicit set; a
71
80
  reset-to-auto affordance appears only in explicit mode. What's shown = what happens.
@@ -207,43 +216,133 @@ Oldest-first auto-allocates; each `AllocationRow` can be filled or typed.
207
216
 
208
217
  ### `tpl_record` — THE record surface
209
218
 
210
- Create-then-refine taken all the way: **"New" is ONE CLICK** (no dialog, no form) it
211
- creates a fresh Draft and the surface IS the editor; everything refines in place. It absorbed
212
- the old order-form, inline-record, intake, settings, billing, and quick-capture templates.
213
- Top bottom:
214
-
215
- - A `RecordSummary` header (identity · status · the one metric) over its key-fact
216
- `DetailTable`, then a Details `DetailTable` — one shared column grid, auto-stacking when
217
- narrow.
218
- - **The customer section TWO states, no swap mode.** Attached = the read-only-first card:
219
- Edit/Done live in the SECTION HEADING and swap the fields to inline chips, where a trailing
220
- Fetch fills contact + city from a registry lookup keyed by tax ID, un-gating billing;
221
- Remove (danger) sits low and detaches the link (the customer stays in the book) → the
222
- find-or-create search, whose custom row opens the create `Dialog` rendering the SAME
223
- inline-chip table + Fetch.
224
- - **Files** `FileDropzone` add, gallery preview, delete.
225
- - **Billing, owned by a later desk** (gated until the record reaches that stage), in the
226
- inline vocabulary: each invoice is a hairline-set band; charge amounts are chips with the
227
- list price as ghost placeholder + a one-tap "Standard …" suggestion pill while unset;
228
- "How paid…" select chips; NO per-band totals the collect band owns the number. The band's
229
- action row sits BOTTOM-RIGHT as *hint status dot Issue*, the hint naming exactly what
230
- blocks (fees method the customer gate). An ISSUED invoice keeps its lines editable and
231
- offers Re-issue a new lookup code replaces the old, confirmed in the same `Dialog`. Plus
232
- a validated receipt and a separate refundable deposit.
233
- - **The lifecycle is a HANDOFF CHAIN** (e.g. Sales Operations AccountingClosed): a
234
- Tasks section speaking the full task-list grammar a clearable Group-by `FilterChip`
235
- (Desk/Assignee/Status) plus assignee/status filter chips derive the groups (empty groups
236
- drop, EXCEPT desk groups — the journey stays visible; desk heads keep their owner dots,
237
- assignee heads are `MemberChip`s), the add-a-task `CaptureRow` at the TOP landing on the
238
- current desk, rows riding the `Checklist`/`ChecklistRow` compound with per-task
239
- `InlineMemberSelect` assignees and a per-row `menu` (move to next desk · danger Delete),
240
- and suggested tasks as `SuggestionChip`s. EVERY desk's rows edit the stage gates the
241
- handoff and Billing, never task editing (planning ahead on a later desk is normal work).
242
- **Per-stage handoff CTAs are NEVER blocked** open tasks warn and carry over.
243
- - Also the settings shape: Preferences switch rows + a `DangerZone`.
244
- - A LEFT OUTLINE RAIL (`MenuButton` + `useSectionNav`) jumps between the sections and
245
- scroll-spies the active one; on narrow containers it becomes the pinned current-section
246
- bar opening a section-picker `Modal`.
219
+ The record page — **the surface IS the editor**; every field refines in place. No breadcrumb
220
+ and no create CTA (creation belongs to the REGISTER the list owns "new"; back lives in the
221
+ left panel as the bare circular `BackButton`). First paint is a `Skeleton` MIRRORING the
222
+ layout, never a spinner. It absorbed the old order-form, inline-record, intake, settings,
223
+ billing, and quick-capture templates. Top → bottom:
224
+
225
+ - A MINIMAL header: `RecordSummary` (title · subtitle ONLY no stage chip, no metric; Tasks
226
+ carries progress, Handoff carries the desk, money lives in Billing), then the record's
227
+ ATTENTION state as a co-located `Callout` (e.g. overdue record-scoped state reads here;
228
+ field-scoped state stays on its field), then a quiet `SummaryLine` (tasks done · documents
229
+ · to collect).
230
+ - **GENERAL the MAIN details, FIRST in the rail**; every later section is supplementary.
231
+ A `SubsectionStack` (space-only beat) of a headingless LEAD group (the key facts) plus
232
+ named groups (e.g. Order / Fulfilment / System — system identifiers get a HOME, not a
233
+ per-row badge). Every group's `DetailTable` repeats the same `labelWidth`/`trailingWidth`
234
+ so the grid aligns through the groups. The FIELD-ANNOTATION vocabulary is the worked
235
+ example here: `description` (persistent, under the value, incl. a deliberately LONG one)
236
+ and `error` (derived, e.g. overdue Due) always explicit, never an ⓘ; the inline editors
237
+ demo their own transient save error (a throwing `onSave`). The Classification group works
238
+ **the right-input-per-field law**: a field gets the control its SHAPE wants, never a
239
+ default text box ≤5 exclusive options the user should see `RadioPicker` (column when
240
+ options carry descriptions, row when short), a longer closed list `InlineSelect` (rich
241
+ options carry a description line, shown in the resting row too), a pick from a big
242
+ REGISTRY `Combobox` (search is the input), a boolean`CheckboxInput`; a DEPENDENT
243
+ field renders only while its parent value makes it real (no disabled ghost rows).
244
+ Radio/checkbox/search are PERSISTENT controls the control is the best display;
245
+ prose-shaped values stay inline editors.
246
+ - **Comments** a coworker's note is the first thing read on a shared record: `CommentList`
247
+ (author-only edit/delete; attachments as a pressable `FileGrid` gallery preview; a
248
+ file-capable edit form injected via `renderEditForm`) + THE kit `Composer` (attach via
249
+ `actionsButton`, staged files in its `files` slot) never a bespoke comment box.
250
+ - **Tasks** the full task-list grammar: clearable Group-by `FilterChip` (+ assignee/status
251
+ chips), the `CaptureRow` on top, `Checklist`/`ChecklistRow` rows with `InlineMemberSelect`
252
+ assignees and per-row menus, suggestions as `SuggestionChip`s; groups divide via the
253
+ `SubsectionStack` beat with plain text heads (no dot badges).
254
+ - **Documents** — the Agents "Document desk" pattern (shared with `tpl_documents` change
255
+ it in BOTH): the register `Table` (search · Add files) whose selection feeds the
256
+ `FloatingActionBar` → ONE "Use AI" fork (extract / cross-check / edit-with-AI). Every
257
+ row's leading visual is a `FileThumbnail` in ONE square 32px slot — an image file fills
258
+ it as a real thumbnail, a document centers its badge in it — never a bare `FileBadge`
259
+ (mixed footprints misalign the identity column). ONE
260
+ sanctioned divergence: GENERATION — on the record page this desk holds what ARRIVES and
261
+ Create moves to the Document set OUTPUT section below; `tpl_documents` (no output
262
+ pipeline on its page) keeps the toolbar "Create documents" dialog.
263
+ - **Customer — the LINKED-RECORD reference implementation** (inline, no wrapper component):
264
+ another record's data NEVER renders as field rows on this one — a BORDERED, fully
265
+ pressable box (glyph anchor · identity · facts stacked VERTICALLY, legible at any width)
266
+ whose press opens the detail drawer (read-only rows + the navigate verb); the action row
267
+ sits INSIDE the box — destructive (Remove, `danger`) LEFT, go-to (Open record) RIGHT.
268
+ Built on `PressableRow` + the keyboard door, never `role="button"` on the box — the
269
+ press anatomy is [composition.md's linked-record law](./composition.md).
270
+ Empty state = the find-or-create search, custom row → the create `Dialog` (tax ID + Fetch;
271
+ its field errors ride `DetailRow error`, not a loose line).
272
+ - **Fees — the DETAILED money ledger** (both directions), distinct from Billing's invoice
273
+ documents: a `SummaryLine` (collected · to collect · to pay, warning-toned when overdue) →
274
+ a `priority`-annotated register `Table` (Fee · Type · Party · Amount · Status — status in
275
+ plain ink, danger only when overdue) → EVERY row opens a right-docked entity `Drawer`
276
+ (◀ ▶ + position stepping) with all fields inline-editable and a confirmed Remove in the
277
+ `DrawerFooter`. "Add fee" is create-then-refine: a blank fee opens straight in the drawer.
278
+ - **Billing** — every group a `Subsection` on ONE beat: per-invoice bands (charge chips with
279
+ ghost list prices + one-tap "Standard …" fill, "How paid…" selects), the collect band
280
+ (owns the total + the receipt), the deposit. THE ACTION-GATING LAW is the worked example:
281
+ a not-ready Issue is DISABLED and the reason is a co-located `Callout` at the gate's
282
+ scope, once — a band-local problem (charged line missing its method) in the band, a broken
283
+ record premise (no customer / invalid tax ID) at the section top; self-evident empties
284
+ stay silent, and NEVER prose beside a CTA. Issuing gates on record premises only — never
285
+ silently on stage.
286
+ - **Document set — the OUTPUT desk, the last WORK section** (the composition rules' output
287
+ law worked on the record surface: the top is intake, the bottom produces on demand).
288
+ Forms group PER PARTY on the `SubsectionStack` beat — each party a `SubsectionHeading` +
289
+ plain `CheckboxInput` rows, the common ones PRE-CHECKED, the long tail folded behind a
290
+ muted "Show all forms (N hidden)" toggle. Between the pickers and the CTA sit the OUTPUT
291
+ CONFIG fields (values printed on the forms — e.g. issuing office, prefill-signing-date
292
+ boolean), each with the input its shape wants. "Create document set" (primary — create IS
293
+ this scope's job; an empty pick self-evidently disables it, no callout) is DETERMINISTIC
294
+ template fill, NOT AI — the CTA carries a brief `loading` state and the whole set appears
295
+ at once (`AgentRun` is for AI work only; dressing deterministic work as an agent run is
296
+ theater). The produced files list under the action row as `FileRow`s (press → preview;
297
+ ⋯ → Download / Remove) with "Download all" BELOW the files it acts on. Regenerating
298
+ REPLACES the set — derived paperwork, never intake; the desk register above keeps what
299
+ arrived. **READINESS**: a form declares the RECORD FIELDS it reads (`needs`); the fields'
300
+ HOME stays their DATA section (the colocation law) with descriptions naming their
301
+ consumers ("printed on the delivery note"). The picker rows ride `Checklist`/
302
+ `ChecklistRow` (`controlWidth={24}` for the `CheckboxInput`) — the compound owns the row
303
+ geometry and the `meta`/`expansion` indent; hand-rolling the anatomy is how alignment
304
+ drifts. Every not-ready row shows BOTH the mark and the explicit trigger — the mark says
305
+ WHAT (the `meta` line: 12px `circle-alert` + "Needs: …", muted ink until checked,
306
+ warning once it blocks), the trigger says HOW: a colored `TextLink` ("Add missing
307
+ fields", link-blue) BESIDE the label — an inline row action; a `Button` here outweighs
308
+ the row. It toggles the TRANSIENT fill editor in the row's `expansion` ("These values
309
+ save onto the order and unlock the form.") — `FormTextInput`s (label + the consumer
310
+ hint as `description`), Cancel / "Save fields" (secondary — the section keeps ONE
311
+ primary, disabled while a draft is empty). Checking NEVER expands anything by itself —
312
+ an expansion that only a side effect reveals is no affordance. Save writes the record
313
+ field (its home row shows the value) and the panel collapses; mark + trigger vanish =
314
+ ready, and a ready form stays silent. Drafts are keyed BY FIELD, so two forms needing
315
+ the same field share one draft and one Save resolves both. The trigger HIDES while its
316
+ panel is open — the panel's Cancel is the closer. Missing fields are the gating law's
317
+ DEGRADED-BUT-VALID case (those fields print blank, filled by hand later): Create stays
318
+ ENABLED, the co-located warning `Callout` states the consequence ("… print blank unless
319
+ filled above"), and the press CONFIRMS via `Alert` — per-form "form — fields" lines,
320
+ Cancel back to the inline fixes / "Generate anyway". Only an empty pick disables, and
321
+ silently. A form can also be DATA-conditional (the hazardous-goods note renders only
322
+ while the record's Hazardous goods boolean is set — record data may reshape the list,
323
+ output selection never does).
324
+ - **Delivery receipt — the QUICK-ISSUE form, its OWN section** (in the rail): one document
325
+ issued from a handful of facts at a known moment (a handover, an inspection, a visit) —
326
+ a label-left capture form (auto-stamped date/receipt-no, record-prefilled fields, the
327
+ right input each) whose CTA and produced `FileRow` ride an empty-label `DetailRow` — the
328
+ form-action alignment law (data_entry.md), worked.
329
+ - **Activity — the audit trail**: a `Timeline` (32px discs, 40px rows) of every change —
330
+ who and when — with DRILL-DOWN `details` (a field change carries its From → To diff, a
331
+ document entry a pressable `FileRow` → preview) and LIVE appends from the surface's real
332
+ actions (issue, fees, handoff).
333
+ - **Handoff — ONCE.** Handing off CREATES the next desk's record on ITS table and links it
334
+ here: the dialog collects the receiver (`MemberSelect`, confirm disabled until chosen) +
335
+ an optional note; confirm writes the trail mark and the SIBLING record, shown as the same
336
+ linked-record box (its drawer holds the reference info + Open record + **Recall**). After
337
+ the handoff there is NO further CTA. Recall is the undo: confirmed, it withdraws the
338
+ sibling and returns the desk — history is NEVER erased (the trail keeps both marks) and
339
+ the CTA returns, so redo is possible.
340
+ - **Danger zone** — with the full section anatomy (heading + description) like every other
341
+ section; the fenced red `DangerZone` card is the section's BODY.
342
+ - A LEFT OUTLINE RAIL (`MenuButton` + `useSectionNav`) — **the rail is the page's COMPLETE
343
+ map** (every section including Danger zone; subsections never join it) — headed by the
344
+ bare circular `BackButton`; it scroll-spies the active section, and on narrow containers
345
+ becomes the pinned current-section bar opening a section-picker `Modal`.
247
346
  - **The page layout is the DOCS layout — the reading column is CENTRED IN THE VIEWPORT, and
248
347
  the rail sits in its left gutter.** The `ScrollView` is FULL-WIDTH — **never cap it at
249
348
  `CONTENT_MAX`, or the scroll container becomes the column and the gutters go wheel-dead**
@@ -253,17 +352,16 @@ Top → bottom:
253
352
  three parts: a gutter RESERVED for the rail (`width: GUTTER` — the rail's own `RAIL_W`
254
353
  PLUS `RAIL_GAP`, the docs' 3rem of breathing room, so the rail never butts against the
255
354
  column), the reading column (`flex: 1, minWidth: 0`), and an EMPTY right gutter of the
256
- same `GUTTER` that BALANCES the rail — without it the column sits half a rail right of centre. (The public docs pages get
257
- this balance for free: their right-hand TOC is the counterweight. A record surface has no
258
- TOC, so the gutter is simply reserved.) The rail is NOT window chrome: no full-height
259
- border, not glued to the window edge — it FLOATS over its reserved gutter
260
- (`position: "absolute"`, `left` mirroring the row's centring maths), so it stays put while
261
- the record scrolls **without** web-only `position: sticky` (which RN does not type —
262
- `position` is `"absolute" | "relative" | "static"`) and without stealing the page's
263
- scrollable width. The rail earns its place only while the centred column still has room
264
- beside it (`pageWidth >= GUTTER * 2 + 560`); below that it collapses to the pinned bar.
265
- Sizing the column is the SHELL's job — inner blocks are plain `width: "100%"`, never a
266
- second `maxWidth` + `alignSelf: "center"`.
355
+ same `GUTTER` that BALANCES the rail — without it the column sits half a rail right of
356
+ centre. (The public docs pages get this balance for free: their right-hand TOC is the
357
+ counterweight. A record surface has no TOC, so the gutter is simply reserved.) The rail is
358
+ NOT window chrome: no full-height border, not glued to the window edge — it FLOATS over
359
+ its reserved gutter (`position: "absolute"`, `left` mirroring the row's centring maths),
360
+ so it stays put while the record scrolls **without** web-only `position: sticky` (which RN
361
+ does not type) and without stealing the page's scrollable width. The rail earns its place
362
+ only while the centred column still has room beside it (`pageWidth >= GUTTER * 2 + 560`);
363
+ below that it collapses to the pinned bar. Sizing the column is the SHELL's job inner
364
+ blocks are plain `width: "100%"`, never a second `maxWidth` + `alignSelf: "center"`.
267
365
 
268
366
  ### `tpl_tasks` — the quick list
269
367