@lotics/ui 45.9.0 → 45.10.1

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/docs/templates.md CHANGED
@@ -2,11 +2,10 @@
2
2
 
3
3
  The package ships full worked-example screens in `examples/tpl_*.tsx` — each a complete,
4
4
  runnable recipe for one screen **job**, built purely from `@lotics/ui` components plus mock
5
- data. This doc is the map: what shape each template solves, what it owns versus what its
6
- primitives own, and which one to start from. Read it when starting any new screen, then read
7
- the chosen template's source before writing code. Component contracts live in
8
- [the catalog](./catalog.md), the layout/color laws in [the composition grammar](./composition.md);
9
- the package index is [../AGENTS.md](../AGENTS.md).
5
+ data. This doc is the map: what shape each template solves and which one to start from. Read it
6
+ when starting any new screen, then read the chosen template's source before writing code.
7
+ Component contracts live in [the catalog](./catalog.md), the layout/color laws in [the
8
+ composition grammar](./composition.md); the package index is [../AGENTS.md](../AGENTS.md).
10
9
 
11
10
  ## How to use an example
12
11
 
@@ -18,13 +17,12 @@ the package index is [../AGENTS.md](../AGENTS.md).
18
17
  resolution by design. Copy the bands you need into your app, keep the component imports,
19
18
  and replace the mock constants with real reads.
20
19
  - **Pick by the job, not the domain.** Templates are scenario-flavoured (a pick run, cash
21
- application, a delivery week) but generic-purpose — they teach the shape. Your orders /
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.
20
+ application, a delivery week) but generic-purpose — they teach the shape. Your orders / items
21
+ / customers screen starts from the template whose *job* matches, whatever the domain. **Flavor
22
+ lives in VALUES, never in STRUCTURE**: section identities, teaching comments, and registry
23
+ keys name the generic PATTERN; mock nouns stay at the common-denominator level any
24
+ goods/services business uses (a service level, a destination, a delivery receipt) — never one
25
+ vertical's jargon (an FCL, an EIR, an HS code).
28
26
  - The templates encode the composition grammar (band order, toolbar law, color discipline,
29
27
  status weight) as working code — when a template and your instinct disagree, the template
30
28
  wins.
@@ -64,10 +62,150 @@ its own navigated route only when one of these holds:
64
62
  - **The work needs the canvas.** A docked comments gutter, a document read beside the fields,
65
63
  an outline rail worth navigating — width the drawer's single column cannot give.
66
64
 
67
- The volume of DATA is not the test. A record with forty fields still closes in the drawer if
68
- the operator opens it from the list, works it, and goes back to the list. What an app without
69
- a destination skips is the ROUTE, never the template: the drawer renders `tpl_record` either
70
- way, so "no record page" must never become a hand-built detail surface.
65
+ The volume of DATA is not the test. What an app without a destination skips is the ROUTE, never
66
+ the template: the drawer renders `tpl_record` either way, so "no record page" must never become
67
+ a hand-built detail surface.
68
+
69
+ ### The shape taxonomy — two families
70
+
71
+ Everything above picks a TEMPLATE. This picks a SHAPE, which is the decision that comes first —
72
+ and the one an agent otherwise re-derives, badly and differently each time, because the register
73
+ has ~13 templates and the record surface has one.
74
+
75
+ **An app screen is `[tabs] + list → record`, and that is nearly every app in every industry.**
76
+ The LIST is the only constant: glancing, filtering, narrowing, summarising. It is where the reader
77
+ spends the day and where the app is judged. The RECORD is the detail layer behind it, with two
78
+ destinations decided by who arrives and from where (§ Register + drawer, or a navigated page?).
79
+ TABS are optional, and reaching for them by reflex is how one set becomes piles nobody asked for.
80
+
81
+ **A dashboard is the other family and shares none of it** — no list, no drawer, no work.
82
+ Read-only, visual-first, immediate: it answers "how is the operation doing", never "which of these
83
+ do I act on".
84
+
85
+ **This is a map of known territory, not a fence.** Building a shape that is not on it is the
86
+ intended behaviour when the job calls for one — the line between a choice and a defect is drawn in
87
+ [composition.md](./composition.md) §"The form comes before the treatment", and it is drawn there
88
+ for this too.
89
+
90
+ ### App-screen shapes
91
+
92
+ **What earns a tab.** A band is a TAB when it changes what the screen IS — its columns, the act on
93
+ its row, what its summary states. A band showing the same columns and the same act over fewer rows
94
+ is a LENS, and a lens is a `ChipGroup` / `Select` / `FilterChip` in the toolbar (→
95
+ [composition.md](./composition.md) §"One view-control vocabulary"). Tabs are the COARSE cut above
96
+ the filters, never a second copy of them; each states its own size and trouble as props (→
97
+ [composition.md](./composition.md) §"A partitioning control states its band's size as a PROP").
98
+
99
+ **The lifecycle desk** — one record type walking ordered stages: an order, a claim, an
100
+ application, a shipment, a repair. *"What is stuck, and what do I move next?"*
101
+
102
+ - **Tabs: the stages**, and here they earn it — each band carries its own act (confirm, dispatch,
103
+ close), its own summary, often its own columns. Roughly five; past that the tail is a lens.
104
+ - **Record: a `Drawer`.** The reader came from the list and returns to it, and the drawer's ◀ ▶
105
+ sequences the band they were reading.
106
+ - **Row vs record:** the row carries what the NEXT act needs — the goods, the money, the clock —
107
+ so the decision is made without opening anything. The record carries the history, the documents,
108
+ and the way back (§ Anything that ADVANCES a record must be able to walk it back).
109
+ - **Adapt:** rename the stages and the shape survives intact. An approval queue is its two-stage
110
+ degenerate case, not a shape of its own.
111
+
112
+ **The party register** — a person or an organisation you have a relationship with: a customer, a
113
+ supplier, a member, a tenant, a patient. *"Who is this, what is our history, is there a risk?"*
114
+
115
+ - **Tabs: usually NONE.** A party has no stages; its segments (region, source, risk) are filters,
116
+ and splitting people into permanent piles buries the ones the reader did not pick.
117
+ - **Record: a PAGE.** A party is arrived at COLD — from an order, a call, a notification — so it
118
+ must stand up without the list that would have opened it.
119
+ - **Row vs record:** identity (`Avatar` initials), ONE reachable contact, what the relationship is
120
+ WORTH (spend, count, last contact), and the single flag that costs money when missed. The record
121
+ holds the transaction history and the interaction log the user writes into.
122
+ - **Adapt:** the worth metrics and the risk flag are the industry-specific half; identity and
123
+ reach are not.
124
+
125
+ **The offering register** — what you sell: products, services, plans, a rate card. *"What do we
126
+ offer, at what price, can I sell it today?"*
127
+
128
+ - **Tabs: none, or the sales categories** the business already thinks in.
129
+ - **Record: a PAGE.** Media, a long description and variants need canvas, and an offering is
130
+ arrived at cold from every order line that names it.
131
+ - **Row vs record:** the row is IMAGE-led — a `FileThumbnail` in one square slot, then the name
132
+ with its code on the supporting line, the price, and the one availability figure. The record
133
+ holds the media, the description, and the price/cost history.
134
+ - **Adapt:** a services business swaps availability for a rate basis. The image column stays: it
135
+ is the row's own data doing the work
136
+ ([composition.md](./composition.md) §"Character comes from the DATA").
137
+
138
+ **The transaction ledger** — dated movements in a book: bank lines, journal entries, stock
139
+ movements, metered usage. *"Does this period reconcile, and what is still unexplained?"*
140
+
141
+ - **Tabs: the BOOKS** — one per source or account. Not the periods: a period is a
142
+ `DateRangeFilterField` in the header band
143
+ ([composition.md](./composition.md) §"Period filters for time-constrained data"), and tabbing it
144
+ strands every other period behind a tab bar that grows monthly.
145
+ - **Record: a `Drawer`.** A line has little of its own; what it needs is the document that proves
146
+ it and whatever it was matched against.
147
+ - **Row vs record:** date, counterparty, ONE signed amount (never paired in/out columns), its
148
+ classification, and whether its document is attached — absence is the information. Where the
149
+ BALANCE is the point the list is a `RunningLedger`, which derives every running figure itself
150
+ rather than trusting a supplied one.
151
+ - **Adapt:** the classification vocabulary. **The period's default decides whether the screen
152
+ opens rich or empty** — open on a settled period and surface the incomplete one as a call to
153
+ action; a screen that opens on its own unfinished work reads as broken.
154
+
155
+ **The monitored-asset set** — a population with a live state and a history: stock, machines,
156
+ vehicles, containers, sensors. *"What needs attention now, and is that number normal?"*
157
+
158
+ - **Tabs: by RESPONSIBILITY, not by state** — holding, receiving and counting are different jobs
159
+ with different acts. State is what the reader sorts and filters by, so it is a lens.
160
+ - **Record: a `Drawer`** onto the unit's recent movements: a `RunningLedger` where they are
161
+ arithmetic, a `Timeline` where they are events.
162
+ - **Row vs record:** the row carries the level, the THRESHOLD it is measured against and the
163
+ derived shortfall — a level with no threshold beside it cannot be acted on — plus its trajectory
164
+ as a `Sparkline`. Where the population runs to hundreds and the job is scanning for red, the
165
+ list becomes a `StatusGrid` (`tpl_tower`); where it runs to thousands, `tpl_stock`.
166
+ - **Adapt:** name the two levels the way the trade does. A gross level and an available level are
167
+ never synonyms, and the available one is what a person checks before committing.
168
+
169
+ **The reconciliation desk** — two sources that should agree: a statement against the ledger, a
170
+ count against the system, a remittance against the orders it covers. *"What does not match, and by
171
+ how much?"* The kit ships no example for this one.
172
+
173
+ - **Tabs: the RUNS** — each a dated session — or none where only one run is ever open.
174
+ - **Record: a `Drawer` on the PAIR**, not on either side: the two figures and everything that
175
+ explains the gap between them.
176
+ - **Row vs record:** a row is a PAIRING, so it carries both figures and the signed difference. A
177
+ committed difference takes a REASON — an unexplained variance cannot be audited, and that is
178
+ where breakage hides. The unmatched remainder is the `SummaryLine`, and the desk is not finished
179
+ until it is zero (`RemainderMeter` + `tpl_allocate` where one source splits across many
180
+ targets).
181
+ - **Adapt:** the two sources and the reason vocabulary; the arithmetic does not change.
182
+
183
+ ### Dashboard shapes
184
+
185
+ Both are read-only and visual-first, and neither runs AI (→ `tpl_dashboard`). They differ by the
186
+ reader's CLOCK, and a screen that mixes them answers neither: a wallboard filtered by quarter is
187
+ not glanceable, and a quarterly review showing only now explains nothing.
188
+
189
+ **The live-status board** — the operation's units, right now. *"Is everything OK?"* Glanced at
190
+ repeatedly, often on a wall.
191
+
192
+ - **No period control** — "now" has no range.
193
+ - Leads with STATE (`KPIStrip` + `StatusGrid`), then an exceptions rail ordered by severity and
194
+ then by how long each has gone unresolved, then where it clusters (`Heatmap`). `tpl_tower` is
195
+ the worked example.
196
+ - Every cell and every exception row is a door into that unit's `Drawer`.
197
+ - **Adapt:** the state vocabulary — one `ColorName` per state, at every weight.
198
+
199
+ **The trend deep-dive** — a period of the business. *"How did it go, and why?"* Opened to decide
200
+ something.
201
+
202
+ - **The period control governs every figure on the screen** (`DateRangeFilterField`).
203
+ - Leads with the shape of CHANGE (`LineChart`, `BarChart`), then composition (`StackedBarChart`,
204
+ `Breakdown`, `PieChart`), then the BRIDGE that explains a movement (`WaterfallChart`, where the
205
+ step that costs the money is the red one rather than a footnote). `tpl_dashboard` for the shape;
206
+ `tpl_pivot` / `tpl_rollup` when the answer is chased down a dimension.
207
+ - **Adapt:** the metrics — and every one traces to a stored or derived value. A chart licenses no
208
+ number the data cannot produce.
71
209
 
72
210
  ## Composition rules — how a record surface is assembled
73
211
 
@@ -81,16 +219,16 @@ Laws for any screen where a record's data is edited and a primary action produce
81
219
  2. **Static shape.** The page never changes shape from output-selection state. Conditional
82
220
  rendering is DATA-driven only (a control appears because two stored values differ), never
83
221
  selection-driven. Relevance is an advisory `Callout` with a jump-to-section action — warn,
84
- never hide, never hard-block. One sanctioned disclosure: a fill-what's-missing `Inset`
85
- editor CO-LOCATED under a just-checked item is progressive disclosure of the gate, not
86
- shape change — it edits the record field in place and clears once satisfied.
222
+ never hide, never hard-block. One sanctioned disclosure: a fill-what's-missing `Inset` editor
223
+ CO-LOCATED under a just-checked item edits the record field in place and clears once
224
+ satisfied.
87
225
  3. **Colocation & ownership.** Every output option (selections, print/export switches) lives in
88
226
  the output section beside its action; a per-entity selection carries its owner's name in its
89
227
  heading. Persisted record DATA never lives there — data belongs in data sections. Misfiled
90
- data announces itself as callout special-cases; the exception disappearing is the sign the
91
- model is right. (HOMING is what's forbidden, not repair: the output section may host a
92
- co-located fill editor that saves onto the record field same storage, the field's home
93
- row shows the value — rendered only while a checked output still lacks the field.)
228
+ data announces itself as callout special-cases. (HOMING is what's forbidden, not repair: the
229
+ output section may host a co-located fill editor that saves onto the record field same
230
+ storage, the field's home row shows the valuerendered only while a checked output still
231
+ lacks the field.)
94
232
  4. **Resolved state.** Controls show the EFFECTIVE state, never raw storage: a derived/suggested
95
233
  selection renders checked; the first manual change persists the full explicit set; a
96
234
  reset-to-auto affordance appears only in explicit mode. What's shown = what happens.
@@ -146,6 +284,23 @@ name `Peek`s the dossier). Also exercises the wider chart family (`LineChart`, `
146
284
  filter actually filters the KPI datasets. Start here for any "state of the operation"
147
285
  landing screen.
148
286
 
287
+ **A dashboard is VISUAL first.** Lead with the charts, let the figures support them. `KPIStrip`
288
+ earns its place as the headline row above the charts, never as the whole screen.
289
+
290
+ The chart family is `BarChart`, `LineChart`, `PieChart`, `Funnel`, `Heatmap`, `Sparkline`,
291
+ `ProgressRing`, `ProgressBar`, `StackedProgressBar`, `StackedBarChart`, `WaterfallChart`,
292
+ `StepProgress` — and **that list is a starting inventory, not the boundary.** When the truth
293
+ needs a shape the kit has no component for — a dual-axis pairing spend against return, a bullet
294
+ against target — **build it into `@lotics/ui`** rather than hand-rolling it inside one app (→
295
+ AGENTS.md, "components graduate into `src/` + an `examples/tpl_*` + a catalog entry").
296
+
297
+ Read the `dataviz` guidance before choosing a chart type or a palette.
298
+
299
+ **No AI run belongs on a dashboard.** If a figure is worth showing, compute it and show it; if
300
+ the question is open-ended enough to need a model, it belongs in chat. The same test
301
+ disqualifies anything else that makes a dashboard *ask before it answers*: a required mode
302
+ chooser, an empty state awaiting a selection, a panel that loads only on click.
303
+
149
304
  ### `tpl_stock` — the drill-down overview (large populations)
150
305
 
151
306
  Monitoring a population too large to browse (10,000+ units). The funnel: `KPIStrip`
@@ -194,51 +349,63 @@ master-detail panel.
194
349
  ### `tpl_item_list` — THE canonical register
195
350
 
196
351
  The one work-execution list shape; it subsumes approvals, dispatch, batch-building, and run
197
- screens — register, per-row action, gated selection, and act-on-many in one. The page:
352
+ screens — register, per-row action, gated selection, and act-on-many in one.
353
+
354
+ **A register is a WORK surface, and that fixes its altitude.** The aggregate belongs elsewhere:
355
+ **no `KPIStrip`, no chart panel, no report band above the rows.** The register's whole summary
356
+ is the one `SummaryLine`, and what it states is *this filtered view* — money at stake, how many
357
+ need a decision, the worst case in view — never a count of rows the reader can already see.
358
+ Aggregates are a DASHBOARD's job: a dashboard says how the operation is doing, a register is
359
+ where the work gets done. (A per-row micro-visual — a `Sparkline`, a bar in a cell — is row
360
+ DATA, not a panel, and is fine.)
361
+
362
+ The corollaries, each of which a register is routinely missing:
363
+
364
+ - **Filter on the axis its reader actually thinks in**, and make anything transactional
365
+ filterable by DATE.
366
+ - **Sortable columns wherever the reader ranks** — dates and amounts above all.
367
+ - **Put intake ON the register**, and one CTA per tab covers the tab's own shape. Where a file
368
+ is the real-world input, the CTA pair is manual entry beside a file-drop that proposes rows
369
+ for review.
370
+ - **If a row can carry a document, the register states whether it has one.** Absence is the
371
+ information — a badge that shows nothing for a row missing its invoice is exactly the column
372
+ that earns its width.
198
373
 
199
374
  - **One toolbar row** — search + a status `Select` + facet `FilterChip`s LEFT, the New CTA
200
- RIGHT; then a light `SummaryLine` of the filtered view BELOW it, above the rows — the counts are what the toolbar just produced, so they read after it, not before.
375
+ RIGHT; then a light `SummaryLine` of the filtered view BELOW it, above the rows.
201
376
  - **A sortable `Table`** — rows separated by their own height and the hover wash, with the one
202
- hairline under the column band. It sets no row height: the register's rhythm is the kit's,
203
- and a template that overrode it would teach the override. → [composition.md](./composition.md)
204
- §"The register's rhythm". The record's SUBJECT is the first column
205
- who or what the record is FOR, because that is what a reader scans for and what they say out
206
- loud ("the Harbor Freight one", never "RC-2026-0041"). The KEY is how the SYSTEM refers to the
207
- record, so it rides the identity cell's supporting line, muted, where the other supporting
208
- values live; it does NOT take a column of its own, and it is never the row's name. It also does
209
- not SHRINK there — a key is quoted whole or it is not a key, so a long phone number gives way
210
- first (it has a `CopyButton` handing over the full value regardless). The record surface names
211
- itself the same way, or the two disagree about what a record is called. Every row carries a
212
- leading checkbox; a row that can't take the bulk action gets a
213
- **disabled** checkbox (the same gating as any blocked line). A row's status is
214
- `Badge variant="dot"` never the tonal `Badge` (that's the drawer/header twin; [composition.md](./composition.md)
215
- §"`Badge` is for STATUS only") and the SAME dot vocabulary
216
- drives the status facet. A cell that carries supporting detail is a HIERARCHY, not two peers:
217
- the identity on top (`size="sm"`, `weight="medium"` when it IS the row's name), its supporting
218
- value beneath as `size="xs" color="muted"`, `gap: 2` and a category / type / attribute goes
219
- on THAT line, never a second chip beside the name (which reads as its peer, or louder when
220
- colored, inverting what the row is scanned by; [composition.md](./composition.md)
221
- §"`Badge` is for STATUS only"). Every value in a row is SELECTABLE, so a reader copies any of them
222
- without chrome; the supporting phone additionally carries a **`CopyButton`** as the worked
223
- example of a verb riding its value — the one value copied often enough to earn a control, and
224
- in its own cell rather than the trailing gutter, which two values in a cell give nothing to
225
- name. A per-row action `Button` (here Print) is the row's primary action in
226
- the trailing column, `⋯` its overflow. A select-all band, footer totals + `Pagination`, and a
227
- `FloatingActionBar` carrying the bulk action while rows are ticked.
377
+ hairline under the column band. It sets no row height: the register's rhythm is the kit's. →
378
+ [composition.md](./composition.md) §"The register's rhythm". The record's SUBJECT is the first
379
+ column who or what the record is FOR. The KEY is how the SYSTEM refers to the record, so it
380
+ rides the identity cell's supporting line, muted, where the other supporting values live; it
381
+ does NOT take a column of its own, and it is never the row's name. It also does not SHRINK
382
+ there a long phone number gives way first (it has a `CopyButton` handing over the full value
383
+ regardless). The record surface names itself the same way. Every row carries a leading
384
+ checkbox; a row that can't take the bulk action gets a **disabled** checkbox (the same gating
385
+ as any blocked line). A row's status is `Badge variant="dot"` never the tonal `Badge`
386
+ (that's the drawer/header twin; [composition.md](./composition.md) §"`Badge` is for STATUS
387
+ only") and the SAME dot vocabulary drives the status facet. A cell that carries supporting
388
+ detail is a HIERARCHY, not two peers: the identity on top (`size="sm"`, `weight="medium"` when
389
+ it IS the row's name), its supporting value beneath as `size="xs" color="muted"`, `gap: 2` —
390
+ and a category / type / attribute goes on THAT line, never a second chip beside the name (→
391
+ [composition.md](./composition.md) §"`Badge` is for STATUS only"). Every value in a row is
392
+ SELECTABLE, so a reader copies any of them without chrome; the supporting phone additionally
393
+ carries a **`CopyButton`** as the worked example of a verb riding its value, in its own cell
394
+ rather than the trailing gutter. A per-row action `Button` (here Print) is the row's primary
395
+ action in the trailing column, `⋯` its overflow. A select-all band, footer totals +
396
+ `Pagination`, and a `FloatingActionBar` carrying the bulk action while rows are ticked.
228
397
  - **A row press opens the PRODUCTION workspace `Drawer`** — which RENDERS
229
398
  [`tpl_record`](../examples/tpl_record.tsx) in `chrome="drawer"`, not a copy of it. Same
230
399
  sections, same order, same rules, because it is the same component: `<TplRecord
231
400
  chrome="drawer" code={row.id} />`. A hand-built second record surface is exactly how the two
232
- drifted apart the progress ladder grouped one way here and flat there, a reference as a
233
- section here and a field there — so the register owns the REGISTER and nothing else.
401
+ drift apart, so the register owns the REGISTER and nothing else.
234
402
  - What the chrome flag changes is only what a nested surface must not do: the back button is
235
- left to the Drawer's header, since two exits is two exits. Everything else follows from
236
- width — the layout measures its CONTAINER via `onLayout`, never the window, so the outline
237
- rail and the mirrored gutters collapse on their own, and the section picker is a popover
238
- ANCHORED to its bar button rather than a full-page `Modal` that would escape the drawer.
239
- **The discussion follows the same measure**: docked beside the record where there is room for
240
- its column, and a SECTION under General where there is not — which a drawer always is. It is
241
- never behind a control.
403
+ left to the Drawer's header. Everything else follows from width the layout measures its
404
+ CONTAINER via `onLayout`, never the window, so the outline rail and the mirrored gutters
405
+ collapse on their own, and the section picker is a popover ANCHORED to its bar button rather
406
+ than a full-page `Modal` that would escape the drawer. **The discussion follows the same
407
+ measure**: docked beside the record where there is room for its column, and a SECTION under
408
+ General where there is not — which a drawer always is. It is never behind a control.
242
409
  - **Linked records** still push an editable screen for that record inside the drawer via the
243
410
  hosted `ScreenRouter` — while `canGoBack` the drawer header swaps to a back button + the
244
411
  pushed id and the ◀ ▶ sequencer hides; back pops with scroll preserved.
@@ -270,12 +437,9 @@ screens — register, per-row action, gated selection, and act-on-many in one. T
270
437
  they will be stored, each editable in place, a `DiffMark` per card, Keep/Drop via
271
438
  `useChangeSet` (`initial: "accepted"` — the operator drops the exceptions rather than
272
439
  approving six identical records) — and ONE commit named for its outcome creates them. The
273
- manual form beside it skips all of that and saves direct: the operator typed those three
274
- values and read them on the way in. Only the DOCUMENT path needs the gate, because a
275
- receipt saying "Created 3 records" is the first moment a misread order reference could
276
- have been caught, and by then it is in the data. The receipt still follows the write — it
277
- states the outcome, ROUTES, and carries the deterministic checks (a checksum, a count
278
- reconciliation) that a person cannot eyeball anyway.
440
+ manual form beside it skips all of that and saves direct. Only the DOCUMENT path needs the
441
+ gate. The receipt still follows the write it states the outcome, ROUTES, and carries the
442
+ deterministic checks (a checksum, a count reconciliation).
279
443
 
280
444
  ### `tpl_pick` — the guided queue
281
445
 
@@ -297,17 +461,17 @@ Oldest-first auto-allocates; each `AllocationRow` can be filled or typed.
297
461
 
298
462
  ### `tpl_record` — THE record surface
299
463
 
300
- The record page — **the surface IS the editor**; every field refines in place. No breadcrumb
301
- and no create CTA (creation belongs to the REGISTER — the list owns "new"; back lives in the
302
- left panel as the bare circular `BackButton`). First paint is a `Skeleton` MIRRORING the
303
- layout, never a spinner. It absorbed the old order-form, inline-record, intake, settings,
304
- billing, and quick-capture templates. Top → bottom:
464
+ The record page — **the surface IS the editor**; every field refines in place. No breadcrumb and
465
+ no create CTA (creation belongs to the REGISTER — the list owns "new"; back lives in the left
466
+ panel as the bare circular `BackButton`). First paint is a `Skeleton` MIRRORING the layout,
467
+ never a spinner. It absorbs the order-form, inline-record, intake, settings, billing, and
468
+ quick-capture templates. Top → bottom:
305
469
 
306
470
  - A MINIMAL header: `RecordSummary` (title, subtitle ONLY — no stage chip, no metric; Progress
307
471
  carries the desk, money lives in Billing), then the record's
308
472
  ATTENTION state as a co-located `Callout` (e.g. overdue — record-scoped state reads here;
309
- field-scoped state stays on its field), then a quiet `SummaryLine` (desk, documents
310
- , to collect).
473
+ field-scoped state stays on its field), then a quiet `SummaryLine` (desk, documents, to
474
+ collect).
311
475
  - **GENERAL — the MAIN details, first in THIS record's rail** because its work starts at the
312
476
  details; rule 1 orders sections by work flow, so a surface whose work starts at a DOCUMENT
313
477
  (an invoice desk, a claims desk) leads with Documents and General follows. Every later
@@ -327,23 +491,20 @@ billing, and quick-capture templates. Top → bottom:
327
491
  field renders only while its parent value makes it real (no disabled ghost rows).
328
492
  Radio/checkbox/search are PERSISTENT controls — the control is the best display;
329
493
  prose-shaped values stay inline editors.
330
- - **Comments — a docked PANEL, not a section.** It is about the whole record, so it has no place
331
- in a top-to-bottom read and no rail entry: it pins in the page's RIGHT gutter (the column the
332
- outline rail's balance already reserved), scrolling inside itself so a long thread never
333
- lengthens the record, and collapses onto the pinned bar's right on narrow. `CommentList`
334
- (author-only edit/delete; attachments as a pressable `FileGrid` → gallery preview; a
335
- file-capable edit form injected via `renderEditForm`) + THE kit `Composer` (attach via
336
- `actionsButton`, staged files in its `files` slot) — never a bespoke comment box.
337
- - **Progress** — directly UNDER General, because where the record SITS is the second question a
338
- reader has after what it IS, ahead of its files, its route and its money, which are all
339
- reference. The desk handoff as the ordered positions it is: a `Checklist connected` where the
340
- desks are `ChecklistGroup`s (Sales Operations Accounting) and the milestones they produce
341
- are the rows. The row that OWES the work
342
- carries all three: how long it has been owed (`meta`), its condition (`ChecklistNote`, e.g.
343
- past the due date with an `action` jumping to the section that owns a missing field rather
344
- than editing it here) and the single act that leaves the desk (`ChecklistActions` → the handoff
345
- dialog). `meta` is prose
346
- the reader can't set, never the date that the stage's own field already shows. See § The
494
+ - **Comments — a docked PANEL, not a section.** It is about the whole record, so it has no rail
495
+ entry: it pins in the page's RIGHT gutter (the column the outline rail's balance already
496
+ reserved), scrolling inside itself so a long thread never lengthens the record, and collapses
497
+ onto the pinned bar's right on narrow. `CommentList` (author-only edit/delete; attachments as
498
+ a pressable `FileGrid` → gallery preview; a file-capable edit form injected via
499
+ `renderEditForm`) + THE kit `Composer` (attach via `actionsButton`, staged files in its
500
+ `files` slot) — never a bespoke comment box.
501
+ - **Progress** — directly UNDER General. The desk handoff as the ordered positions it is: a
502
+ `Checklist connected` where the desks are `ChecklistGroup`s (Sales Operations Accounting)
503
+ and the milestones they produce are the rows. The row that OWES the work carries all three:
504
+ how long it has been owed (`meta`), its condition (`ChecklistNote`, e.g. past the due date
505
+ with an `action` jumping to the section that owns a missing field rather than editing it here)
506
+ and the single act that leaves the desk (`ChecklistActions` the handoff dialog). `meta` is
507
+ prose the reader can't set, never the date that the stage's own field already shows. See § The
347
508
  handoff shape for the rules; for a record that genuinely needs a CHECKLIST, the task-list
348
509
  grammar is worked in `tpl_item_list`.
349
510
  - **Documents** — the Agents "Document desk" pattern (this template is its worked example —
@@ -365,82 +526,69 @@ billing, and quick-capture templates. Top → bottom:
365
526
  own (see [composition.md's reference law](./composition.md)). It wears the inline editor's
366
527
  resting surface with an `InlineButton` **Open** inside it; pressing the surface PEEKS the
367
528
  facts, and the peek's footer carries detach LEFT / Open RIGHT — so the row itself shows no
368
- destructive verb at all. The tax-ID problem that gates invoicing is the row's `warning`, not
369
- a floating `Callout`. Transport's three party rows are the same shape repeated, which is where
370
- it earns itself: N references as field rows scan as one list, where N cards would be a wall.
371
- Empty state = the find-or-create `Combobox` in the same value slot, custom row → the create
372
- `Dialog` (tax ID + Fetch). That dialog is built from **`FormField` / `FormTextInput`, never the
373
- record surface's `Inline*` editors** — see [data_entry.md](./data_entry.md) §"A dialog is a
374
- draft".
375
- **Reference (the buyer's own) is the `actions` worked example**: its `Copy` is an
376
- `InlineButton` ON the field, passed unconditionally and `disabled` while empty not in the
377
- row's `trailing`, and not conditional, which would resize the field as you type.
529
+ destructive verb at all. The tax-ID problem that gates invoicing is the row's `warning`, not a
530
+ floating `Callout`. Transport's three party rows are the same shape repeated: N references as
531
+ field rows scan as one list, where N cards would be a wall. Empty state = the find-or-create
532
+ `Combobox` in the same value slot, custom row → the create `Dialog` (tax ID + Fetch). That
533
+ dialog is built from **`FormField` / `FormTextInput`, never the record surface's `Inline*`
534
+ editors** — see [data_entry.md](./data_entry.md) §"A dialog is a draft". **Reference (the
535
+ buyer's own) is the `actions` worked example**: its `Copy` is an `InlineButton` ON the field,
536
+ passed unconditionally and `disabled` while empty not in the row's `trailing`, and not
537
+ conditional, which would resize the field as you type.
378
538
  - **Fees — the DETAILED money ledger** (both directions), distinct from Billing's invoice
379
- documents: a `SummaryLine` (collected, to collect, to pay, warning-toned when overdue) →
380
- a `priority`-annotated register `Table` (Fee, Type, Party, Amount, Status — status in
381
- plain ink, danger only when overdue) → EVERY row opens a right-docked entity `Drawer`
382
- (◀ ▶ + position stepping) with all fields inline-editable and a confirmed Remove in the
383
- `DrawerFooter`. "Add fee" rides the section heading's right edge the one place it does not
384
- move as rows arrive and is create-then-refine: a blank fee opens straight in the drawer. The
385
- empty state carries no button of its own, because that one is already on screen.
539
+ documents: a `SummaryLine` (collected, to collect, to pay, warning-toned when overdue) → a
540
+ `priority`-annotated register `Table` (Fee, Type, Party, Amount, Status — status in plain ink,
541
+ danger only when overdue) → EVERY row opens a right-docked entity `Drawer` (◀ ▶ + position
542
+ stepping) with all fields inline-editable and a confirmed Remove in the `DrawerFooter`. "Add
543
+ fee" rides the section heading's right edge and is create-then-refine: a blank fee opens
544
+ straight in the drawer. The empty state carries no button of its own, because that one is
545
+ already on screen.
386
546
  - **Billing — INLINE bands, closed by a STATEMENT.** Each invoice is a `Subsection` costing ONE
387
547
  line of chrome (its name, its total, its issued ref) over its charge rows in a `DetailTable`;
388
548
  the charge editors carry ghost list prices with a one-tap "Standard …" fill and a "How paid…"
389
- select. Three shapes were tried. Inline bands with a heading rung, a band callout and an
390
- action row each made the CHROME three times the content; a `Table` + right-docked `Drawer`
391
- then hid four charge lines behind three expansions, which trades a real fault for a worse one
392
- — you can no longer see what you are paying for. So the lines all stay and the chrome
393
- shrank, and a per-invoice problem rides the CHARGE ROW that has it (`warning` on the line
394
- missing its method), which is smaller than a callout and more precise: it names the offender
395
- instead of counting offenders.
549
+ select. The lines all stay, and a per-invoice problem rides the CHARGE ROW that has it
550
+ (`warning` on the line missing its method), which is smaller than a callout and more precise.
396
551
  The section then CLOSES with a `Ledger` — the money grammar's worked example, and the answer
397
- the bands cannot give: three invoice totals down the page are three facts the reader has to
398
- add up, and "is anything still owed" appeared nowhere. THREE sides `Invoiced`, a one-row
399
- `Adjustments` credit, and `Received` with its rows negative and `success`-toned — so the total
400
- states arithmetic rather than a difference, over a `LedgerTotal` carrying `zeroLabel="Paid in
401
- full"`. Every row state is on screen: a `peek` door on the multi-line invoice (its lookup link
402
- INSIDE the popover, since a button cannot hold a button), a flat `reference` on the single-line
403
- one, `meta` qualifying how money came back. `Received` is DERIVED from the charges that carry a
404
- payment method not a second flag to keep in step and the refundable deposit stays OUT,
405
- because a ledger earns trust by summing exactly what its label claims. The fixture is
406
- deliberately uneven (an unpriced charge, one issued invoice, a one-row group) because a tidy
407
- one verifies nothing.
408
- THE ACTION-GATING LAW is still the worked example: a not-ready Issue is DISABLED and the
552
+ the bands cannot give. THREE sides `Invoiced`, a one-row `Adjustments` credit, and
553
+ `Received` with its rows negative and `success`-toned so the total states arithmetic rather
554
+ than a difference, over a `LedgerTotal` carrying `zeroLabel="Paid in full"`. Every row state
555
+ is on screen: a `peek` door on the multi-line invoice (its lookup link INSIDE the popover,
556
+ since a button cannot hold a button), a flat `reference` on the single-line one, `meta`
557
+ qualifying how money came back. `Received` is DERIVED from the charges that carry a payment
558
+ method not a second flag to keep in step and the refundable deposit stays OUT, because a
559
+ ledger earns trust by summing exactly what its label claims. The fixture is deliberately
560
+ uneven (an unpriced charge, one issued invoice, a one-row group) because a tidy one verifies
561
+ nothing. THE ACTION-GATING LAW is the worked example: a not-ready Issue is DISABLED and the
409
562
  reason is a co-located `Callout` at the gate's SCOPE, once — an invoice's own inconsistency (a
410
563
  charged line with no method) on that line, a broken record premise (no customer / invalid tax
411
564
  ID) at the section top; self-evident empties stay silent, and NEVER prose beside a CTA.
412
565
  Issuing gates on record premises only — never silently on stage. It is also the worked example
413
- of **an irreversible action taking an ID, not a captured object** — the confirm re-resolves the
414
- invoice from its key, so the quoted total is the one that will be billed even when the press
415
- was held for a charge cell's write (data_entry.md § Inline edit).
416
- - **Document set — the OUTPUT desk, the last WORK section** (the composition rules' output
417
- law worked on the record surface: the top is intake, the bottom produces on demand).
418
- Forms group PER PARTY on the `SubsectionStack` beat — each party a `SubsectionHeading` +
419
- plain `CheckboxInput` rows, the common ones PRE-CHECKED, the long tail folded behind a
420
- muted "Show all forms (N hidden)" toggle. Between the pickers and the CTA sit the OUTPUT
421
- CONFIG fields (values printed on the forms — e.g. issuing office, prefill-signing-date
422
- boolean), each with the input its shape wants. "Create document set" (primary — create IS
423
- this scope's job; an empty pick self-evidently disables it, no callout) is DETERMINISTIC
424
- template fill, NOT AI — the CTA carries a brief `loading` state and the whole set appears
425
- at once (`AgentRun` is for AI work only; dressing deterministic work as an agent run is
426
- theater). The produced files list under the action row as `FileRow`s (press → preview;
427
- ⋯ → Download / Remove) with "Download all" BELOW the files it acts on. Regenerating
428
- REPLACES the set derived paperwork, never intake; the desk register above keeps what
429
- arrived. **READINESS**: a form declares the RECORD FIELDS it reads (`needs`); the fields'
430
- HOME stays their DATA section (the colocation law) with descriptions naming their
431
- consumers ("printed on the delivery note"). The picker rows ride a
566
+ of **an irreversible action taking an ID, not a captured object** — the confirm re-resolves
567
+ the invoice from its key, so the quoted total is the one that will be billed even when the
568
+ press was held for a charge cell's write (data_entry.md § Inline edit).
569
+ - **Document set — the OUTPUT desk, the last WORK section** (the composition rules' output law
570
+ worked on the record surface: the top is intake, the bottom produces on demand). Forms group
571
+ PER PARTY on the `SubsectionStack` beat — each party a `SubsectionHeading` + plain
572
+ `CheckboxInput` rows, the common ones PRE-CHECKED, the long tail folded behind a muted "Show
573
+ all forms (N hidden)" toggle. Between the pickers and the CTA sit the OUTPUT CONFIG fields
574
+ (values printed on the forms — e.g. issuing office, prefill-signing-date boolean), each with
575
+ the input its shape wants. "Create document set" (primary — create IS this scope's job; an
576
+ empty pick self-evidently disables it, no callout) is DETERMINISTIC template fill, NOT AI —
577
+ the CTA carries a brief `loading` state and the whole set appears at once (`AgentRun` is for
578
+ AI work only; dressing deterministic work as an agent run is theater). The produced files list
579
+ under the action row as `FileRow`s (press → preview; ⋯ → Download / Remove) with "Download
580
+ all" BELOW the files it acts on. Regenerating REPLACES the set — derived paperwork, never
581
+ intake; the desk register above keeps what arrived. **READINESS**: a form declares the RECORD
582
+ FIELDS it reads (`needs`); the fields' HOME stays their DATA section (the colocation law) with
583
+ descriptions naming their consumers ("printed on the delivery note"). The picker rows ride a
432
584
  `Checklist connected={false} mark="select"` — the SAME compound as Progress, TWO axes apart.
433
- Unconnected because these forms are a SET the user picks from rather than a sequence, and a
434
- line between them would claim an order the work does not have; `select` because picking a form
435
- is a CHOICE, and a filled ring there would read as work already carried out to a reader who
436
- learned what a ring means one section above. The row NAMES what is missing in a `ChecklistNote` — "Needs: …"
437
- the actual field labels, muted at rest so gaps show WITHOUT checking, warning once checked
438
- because it now blocks and points at the section that owns those fields.
439
-
440
- **It does not collect them.** The row briefly opened a form of its own here, which made every
441
- row two things at once (a status and a form) with nothing to say which one the reader was in,
442
- and put an editor beside a field's real home where its description and its neighbours live. A
443
- checklist REPORTS; the section that owns a value COLLECTS it. A ready form stays SILENT.
585
+ Unconnected because these forms are a SET the user picks from rather than a sequence; `select`
586
+ because picking a form is a CHOICE. The row NAMES what is missing in a `ChecklistNote` —
587
+ "Needs: …" the actual field labels, muted at rest so gaps show WITHOUT checking, warning once
588
+ checked because it now blocks and points at the section that owns those fields.
589
+
590
+ **It does not collect them.** A checklist REPORTS; the section that owns a value COLLECTS it.
591
+ A ready form stays SILENT.
444
592
 
445
593
  Missing fields are the gating law's DEGRADED-BUT-VALID case (they print blank, filled by hand later): Create stays ENABLED, a
446
594
  co-located warning `Callout` states the consequence, and the press CONFIRMS via `Alert`.
@@ -452,115 +600,91 @@ billing, and quick-capture templates. Top → bottom:
452
600
  a label-left capture form (auto-stamped date/receipt-no, record-prefilled fields, the
453
601
  right input each) whose CTA and produced `FileRow` ride an empty-label `DetailRow` — the
454
602
  form-action alignment law (data_entry.md), worked.
455
- - **Activity — what was SAID with the other party**, newest first, directly under Progress: the
456
- two answer a reader's first two questions in order, where the record stands and what passed
457
- between us to get it there. It is NOT an audit trail and NOT the handoff — see the next bullet
458
- for the line, which is what keeps this section from becoming the change log Progress refuses.
603
+ - **Activity — what was SAID with the other party**, newest first, directly under Progress. It
604
+ is NOT an audit trail and NOT the handoff see the next bullet for the line.
459
605
 
460
606
  **The GIST is what the feed is scanned by** — what came of the exchange, in the reader's own
461
- words. Building it the other way round is the natural mistake and it makes the feed unreadable:
462
- every entry renders as a pair of enum values ("Outbound Meeting"), the substance sits behind a
463
- chevron, and a column of near-identical labels defeats the scan the feed exists for.
607
+ words. Building it the other way round makes the feed unreadable: every entry renders as a
608
+ pair of enum values ("Outbound Meeting") and the substance sits behind a chevron.
464
609
 
465
610
  **An entry is a COMMENT, not a `Timeline` row.** `Timeline` renders its label inside the row's
466
611
  press target, which is right for derived read-only text ("Stage changed to Won") and wrong for
467
- the one thing on an entry a person WROTE — text inside a button cannot be edited where it sits.
468
- So the gist is a RESIDENT field and the derived
469
- facts sit above it on a muted `xs` **byline** — which way it went, over what, when. Order does
470
- not decide the scan; weight does. The byline is small and grey, the prose is `sm` on the page's
471
- own ground, and the eye lands on the substance even though it reads second.
612
+ the one thing on an entry a person WROTE — text inside a button cannot be edited where it
613
+ sits. So the gist is a RESIDENT field and the derived facts sit above it on a muted `xs`
614
+ **byline** — which way it went, over what, when. Order does not decide the scan; weight does:
615
+ the byline is small and grey, the prose is `sm` on the page's own ground.
472
616
 
473
617
  **ONE anatomy, a body that VARIES.** An entry can be a call, a recorded demo, an email, a
474
618
  message or a note typed between meetings — and those differ in what they CARRY, not in what
475
619
  they are: all of them answer what-came-of-it, which way, over what, and when. So the four
476
- invariants are the row, and the body is a SET OF BLOCKS rather than a shape per kind. A shape
477
- per kind writes the same block five times and lets the copies drift, and the sixth kind — the
478
- one nobody has thought of needs a sixth. **Every block is optional and any combination is
479
- legal**, because what an entry carries is independent of what it is: a call may arrive as a
480
- recording alone, gain a transcript minutes later and a summary after that.
620
+ invariants are the row, and the body is a SET OF BLOCKS rather than a shape per kind. **Every
621
+ block is optional and any combination is legal**: a call may arrive as a recording alone, gain
622
+ a transcript minutes later and a summary after that.
481
623
 
482
624
  The blocks, in the order they render (`ActivityBody` in `tpl_record` is the worked one):
483
625
 
484
626
  | Block | What it is | How it renders |
485
627
  |---|---|---|
486
- | Participants | ONLY what cannot be derived — a third party who joined, an email's From/To. The record is already the counterparty, so naming the obvious attendees is padding | muted line |
628
+ | Participants | ONLY what cannot be derived — a third party who joined, an email's From/To | muted line |
487
629
  | Subject | the one thing an email has that nothing else does | `sm` medium |
488
630
  | Media | the exchange itself, recorded | `MediaPlayer` — **give it a box**, it fills its parent and collapses to nothing without one |
489
631
  | Transcript | VERBATIM and long; the source a summary was made from | a muted underlined `TextLink` in your own `Pressable`, revealing PLAIN text — see the disclosure exception in composition.md §"Commit & feedback surfaces" |
490
- | Prose | what the COUNTERPARTY sent — an email body. Not a second field of ours: an entry offering both a gist and a "note" gives one event two free-text boxes and nothing to tell a writer which to use, and the gist auto-grows, so it already carries whatever length someone wants | `Markdown variant="embedded"` on the page's own ground |
491
- | AI prose | what a MODEL wrote | ONE short label INSIDE the `Inset` ("AI summary") floating above it, a 12px muted fragment over a tinted box reads as an orphan. Name the evidence only where it is NOT on screen: over a player the reader can see, "from the recording" is four words restating a video. Let the label replace the block's own `## Heading` rather than sit above it |
632
+ | Prose | what the COUNTERPARTY sent — an email body. Not a second field of ours: the gist auto-grows, so it already carries whatever length someone wants | `Markdown variant="embedded"` on the page's own ground |
633
+ | AI prose | what a MODEL wrote | ONE short label INSIDE the `Inset` ("AI summary"), never floating above it. Name the evidence only where it is NOT on screen. Let the label replace the block's own `## Heading` rather than sit above it |
492
634
  | Files | what came with it — EVIDENCE, so it is writable | one `InlineFiles` (`onRemove`, no `onAdd`), mixed pdf/image, previewing in place |
493
635
  | Source | where it happened, if openable | `Link` |
494
636
 
495
- **An entry's files are WRITABLE, on both paths.** A touchpoint is very often about a document —
496
- you sent the quote, they sent the PO back so a feed that displays `files` but offers no way to
497
- file one is a read-only surface over a writable model, which reads as a missing feature every
498
- time. Two places, because there are two moments: the section's `Composer` stages them on the
499
- entry being logged (`actionsButton` + the `files` slot + `sendDisabled`, the three kit slots the
500
- comment thread already used), and the entry's own menu attaches to one that already exists.
501
-
502
- **`sendDisabled` is not optional once a composer can attach.** The default blocks send on empty
503
- text, which is right for a message and wrong the moment an attachment is the whole point — "here
504
- is the signed PO" needs no sentence. Pass `false` while files are staged, `undefined` otherwise
505
- so the default returns.
506
-
507
- **Attaching lives on the MENU, not on the file list.** The list's own CTA cannot work on an entry
508
- with nothing attached — there is no list to hang it off so a menu verb that works in every
509
- state is the single path, and the list takes `onRemove` alone. Removing CONFIRMS: that file is
510
- already in the customer's history, unlike a staged attachment on the composer, which is not yet
511
- anything.
512
-
513
- **An entry is its own `FileDropTarget`**, nested inside the record-level one. A file dropped on a
514
- conversation belongs to that conversation; routing it to the record's Documents intake instead
515
- leaves the reader to relate the two by hand. Nesting is sound rather than lucky — the component
516
- stops propagation on every drag/drop event, so the innermost region wins, the outer one neither
517
- fires nor lights, and the file is added once. Pass a plain child so the target paints its own
518
- drag affordance: with two live regions the drag has to say WHICH one it is about to land in.
519
- Both paths — the menu verb and the drop — call ONE append function, because two implementations
520
- of "add to this list" is how one of them quietly becomes a replace. A section earns its own
521
- target when the drop MEANS something different there; "the same thing, closer" does not.
637
+ **An entry's files are WRITABLE, on both paths.** Two places, because there are two moments:
638
+ the section's `Composer` stages them on the entry being logged (`actionsButton` + the `files`
639
+ slot + `sendDisabled`, the three kit slots the comment thread already used), and the entry's
640
+ own menu attaches to one that already exists.
641
+
642
+ **`sendDisabled` is not optional once a composer can attach.** The default blocks send on
643
+ empty text, which is wrong the moment an attachment is the whole point. Pass `false` while
644
+ files are staged, `undefined` otherwise so the default returns.
645
+
646
+ **Attaching lives on the MENU, not on the file list.** The list's own CTA cannot work on an
647
+ entry with nothing attached, so a menu verb that works in every state is the single path, and
648
+ the list takes `onRemove` alone. Removing CONFIRMS.
649
+
650
+ **An entry is its own `FileDropTarget`**, nested inside the record-level one. Nesting is sound
651
+ rather than lucky the component stops propagation on every drag/drop event, so the innermost
652
+ region wins, the outer one neither fires nor lights, and the file is added once. Pass a plain
653
+ child so the target paints its own drag affordance. Both paths — the menu verb and the drop —
654
+ call ONE append function. A section earns its own target when the drop MEANS something
655
+ different there; "the same thing, closer" does not.
522
656
 
523
657
  **A MESSAGE's attachments are tiles — staged or filed.** The composer's `files` slot takes
524
658
  `<FileGrid files={…} itemSize={THUMBNAIL_SIZE} onDisplayRemove={…}>` — the exact component and
525
- size the product's own chat composer passes because a staged file is not yet a document in a
526
- list: it is a thing you are about to send, checked by LOOKING at it, and the control that
527
- unstages it belongs on the tile. Getting this backwards is easy and invisible: a row of staged
528
- attachments looks tidy and quietly makes the composer feel like a file manager.
529
-
530
- Filing it does not turn it into a row it turns it into a `FilesEditor`. Tiles either way, the
531
- same as `chat_user_message` renders a posted message's attachments and the same as this record's
532
- Files section, because evidence is recognised by LOOKING (a scanned PO, a photo of a seal). The
533
- rows belong to a different object: `InlineFiles` is a labelled FIELD in a `DetailRow` ("Signed
534
- contract", "Supplier original"), where the document set has a name, a cardinality and a
535
- `blockedReason`. Reasoning from that field to a message's attachments is the mistake to avoid
536
- it is not "the filed version of the same thing".
537
-
538
- **A FILED attachment must be hard to lose; a STAGED one is not yet anything.** That is the whole
539
- difference between the two components. A bare `FileGrid` draws a ✕ on every tile, one click from
540
- gone — right in a composer, wrong on a record's history. `FilesEditor`'s default view never draws
541
- one at all: removal means opening the file and doing it from the gallery, where you are looking
542
- at the thing you are about to delete and it still asks, naming the file. Reach for the editor and
543
- three things you would otherwise hand-roll come with it — the confirmation, the full-screen
544
- gallery, and press-to-open.
545
-
546
- **Give it no children unless a toolbar earns its place.** `FilesEditor` renders its bar from its
547
- children and nothing without them, and select mode is reachable ONLY from that bar — so omitting
548
- it removes the mode rather than stranding it. A feed of eight entries does not want eight
549
- toolbars.
659
+ size the product's own chat composer passes. A staged file is not yet a document in a list:
660
+ the control that unstages it belongs on the tile.
661
+
662
+ Filing it does not turn it into a row it turns it into a `FilesEditor`. Tiles either way,
663
+ the same as `chat_user_message` renders a posted message's attachments and the same as this
664
+ record's Files section, because evidence is recognised by LOOKING. The rows belong to a
665
+ different object: `InlineFiles` is a labelled FIELD in a `DetailRow` ("Signed contract",
666
+ "Supplier original"), where the document set has a name, a cardinality and a `blockedReason`.
667
+
668
+ **A FILED attachment must be hard to lose; a STAGED one is not yet anything.** A bare
669
+ `FileGrid` draws a on every tile, one click from gone right in a composer, wrong on a
670
+ record's history. `FilesEditor`'s default view never draws one at all: removal means opening
671
+ the file and doing it from the gallery, and it still asks, naming the file. Reach for the
672
+ editor and three things you would otherwise hand-roll come with it the confirmation, the
673
+ full-screen gallery, and press-to-open.
674
+
675
+ **Give it no children unless a toolbar earns its place.** `FilesEditor` renders its bar from
676
+ its children and nothing without them, and select mode is reachable ONLY from that bar so
677
+ omitting it removes the mode rather than stranding it.
550
678
 
551
679
  **Never write a tile size as a number.** The kit exports the two that exist — `THUMBNAIL_SIZE`
552
- (a composer attachment, a file tile you are meant to recognise) and `COMPACT_THUMBNAIL_SIZE` (the
553
- badge on a list row) — and the product uses nothing else. A literal is how a surface ends up at
554
- 56 or 84 for no reason anyone can reconstruct, looking subtly unlike the same surface elsewhere;
555
- the complaint arrives as "why is it smaller here", which is not a question a constant can raise.
556
- For a grid that should FILL its container instead, omit `itemSize` and let `minItemWidth` derive
557
- the columns — that is a different decision from the tile's size, not a smaller version of it.
680
+ (a composer attachment, a file tile you are meant to recognise) and `COMPACT_THUMBNAIL_SIZE`
681
+ (the badge on a list row) — and the product uses nothing else. For a grid that should FILL its
682
+ container instead, omit `itemSize` and let `minItemWidth` derive the columns.
558
683
 
559
684
  **Copy the component, not the layout mode.** Chat's POSTED attachments use `columns={3}
560
- partialRowAlign="end"`; the `partialRowAlign` is there because a chat bubble is right-aligned, so
561
- a short last row hugging the end is what lines up. A comment on a record is a left-aligned block
562
- and inherits neither. Matching chat means the same component and the same size constant, not
563
- transplanting a decision that belongs to a bubble.
685
+ partialRowAlign="end"`; the `partialRowAlign` is there because a chat bubble is right-aligned,
686
+ so a short last row hugging the end is what lines up. A comment on a record is a left-aligned
687
+ block and inherits neither.
564
688
 
565
689
  **One list, not the documents/images split.** That split — documents as rows because a name
566
690
  identifies them, images as tiles because content does — holds where files are the SUBJECT (a
@@ -568,53 +692,41 @@ billing, and quick-capture templates. Top → bottom:
568
692
  identified by what they are, which is the shape the fee ledger's "Supplier original" already
569
693
  uses: one `InlineFiles` taking `application/pdf,image/*`.
570
694
 
571
- **Delete is the only verb on the entry itself, and it sits in an `ActionMenu` at the TOP RIGHT.** Edit
572
- stopped existing the moment the gist became a resident field the fix that removes a control
573
- beats the one that relocates it and what remains goes where every comment surface puts it,
574
- beside the byline rather than in a footer a scroll below the sentence it acts on. The menu is a
575
- SIBLING of the content, so nothing nests a button inside another.
695
+ **Delete is the only verb on the entry itself, and it sits in an `ActionMenu` at the TOP
696
+ RIGHT** where every comment surface puts it, beside the byline rather than in a footer a
697
+ scroll below the sentence it acts on. The menu is a SIBLING of the content, so nothing nests a
698
+ button inside another.
576
699
 
577
- Delete belongs there DESPITE the feed being a record because of what a multi-writer feed
578
- actually produces. An extraction filing against the wrong contact and an automation
579
- double-filing a recording are not history, and a feed with no way to remove them accumulates
580
- garbage it also asks you to trust. Name what leaves with the entry in the confirmation: a
581
- count somewhere else is the part nobody expects.
700
+ Delete belongs there DESPITE the feed being a record: an extraction filing against the wrong
701
+ contact and an automation double-filing a recording are not history. Name what leaves with the
702
+ entry in the confirmation.
582
703
 
583
704
  Only the GIST is editable — everything else is derived, verbatim, an artifact or a model's
584
- output, and a counterparty's own email body is a record of what THEY said for the same reason a
585
- transcript is not editable. It is resident, with no mode and no verb, and it follows the value:
586
- `bare` once written up so it reads as prose, `framed` while empty so it is recognisable as an
587
- input. Both halves are in data_entry.md §"A value rendered TWICE is the bug".
588
-
589
- **NO AUTHOR on an entry.** A recording is made by the people in the meeting and merely
590
- transcribed by a service, so a byline reading "Recording bot" names the plumbing and fabricates
591
- an author for somebody else's conversation. Once that shape is refused, showing an author on the
592
- entries that happen to have one is worse than showing none the reader learns only that we
593
- attribute when we can, which is a fact about the pipeline. The record has an Owner; a customer's
594
- history does not restate it once per entry. What a machine actually TOUCHED stays visible where
595
- it changes how you read something: the `Inset` names an "AI summary", a transcript is labelled
596
- verbatim. That is attribution of the CONTENT, which is the only kind that was load-bearing.
597
-
598
- **The blocks SHOW; they are not folded behind a disclosure.** They are what the entry IS, and a
599
- control that hides them makes the reader click to discover what the entry already says is there
600
- on every entry, forever. A video is the
601
- honest counter-case at 358px and it still shows it is the most informative thing on a recorded
602
- call, the feed folds its own tail, and the byline and gist stay at the top of every entry, so the
603
- scan survives a tall one.
604
-
605
- **The TRANSCRIPT keeps its disclosure, and it is the only one.** It is the one block that is
606
- unbounded by nature — a machine's verbatim record of half an hour — and the one nobody reads
607
- straight through: you open it to check a claim the summary above made. That is what a disclosure
608
- is for. An email BODY is prose of unknown length too and still shows, because it is human-written,
609
- ordinarily short, and it IS the message rather than the source behind one.
705
+ output, and a counterparty's own email body is a record of what THEY said. It is resident,
706
+ with no mode and no verb, and it follows the value: `bare` once written up, `framed` while
707
+ empty. Both halves are in data_entry.md §"A value rendered TWICE is the bug".
708
+
709
+ **NO AUTHOR on an entry.** A byline reading "Recording bot" names the plumbing and fabricates
710
+ an author for somebody else's conversation. Showing an author on the entries that happen to
711
+ have one is worse than showing none. The record has an Owner; a customer's history does not
712
+ restate it once per entry. What a machine actually TOUCHED stays visible where it changes how
713
+ you read something: the `Inset` names an "AI summary", a transcript is labelled verbatim.
714
+
715
+ **The blocks SHOW; they are not folded behind a disclosure.** A video is the honest
716
+ counter-case at 358px and it still shows — the feed folds its own tail, and the byline and
717
+ gist stay at the top of every entry, so the scan survives a tall one.
718
+
719
+ **The TRANSCRIPT keeps its disclosure, and it is the only one.** It is the one block unbounded
720
+ by nature a machine's verbatim record of half an hour — and the one nobody reads straight
721
+ through: you open it to check a claim the summary above made. An email BODY is prose of
722
+ unknown length too and still shows: it IS the message rather than the source behind one.
610
723
 
611
724
  **A block earns its place by carrying what nothing else on the row does.** The failure is not
612
725
  a missing block, it is three blocks saying one thing: a participants line, a rep's gist and a
613
- machine summary all reporting that the spreadsheet owner was absent. When the summary is good
614
- the rep usually adds nothing, and an entry with no note is the normal case rather than an
615
- incomplete one. Cut on sight: a label above a block that already labels itself, a fact the
616
- record or the row already states, and any block whose content the reader can see rendered
617
- directly above it.
726
+ machine summary all reporting the same absence. An entry with no note is the normal case
727
+ rather than an incomplete one. Cut on sight: a label above a block that already labels itself,
728
+ a fact the record or the row already states, and any block whose content the reader can see
729
+ rendered directly above it.
618
730
 
619
731
  **Two of those rows are the whole point of the section on a modern record.** A feed fills from
620
732
  more than one direction — a person, a chat agent, an extraction off an upload, an automation
@@ -624,50 +736,43 @@ billing, and quick-capture templates. Top → bottom:
624
736
  plus a recessed ground — never by a weight nudge. The reasoning and the failure modes are in
625
737
  ai_patterns.md §After the run.
626
738
 
627
- **The transcript folds, and it reveals in place.** It is the source a summary was made from,
628
- read rarely and in full when at all and the reason anyone opens it is to check a claim the
629
- summary made, so a dialog is wrong: it takes the claim off screen at the moment they want to
630
- compare. Reveal it whole rather than into a scroll box; a scroller inside a scrolling drawer
631
- traps the wheel, and a reader who pressed it asked for the length.
739
+ **The transcript folds, and it reveals in place** a dialog is wrong: it takes the claim off
740
+ screen at the moment they want to compare. Reveal it whole rather than into a scroll box; a
741
+ scroller inside a scrolling drawer traps the wheel.
632
742
 
633
743
  **The toggle is underlined text, and that is a deliberate exception** (the in-prose disclosure,
634
744
  composition.md §"Commit & feedback surfaces"): a muted underlined `TextLink` in your own
635
745
  `Pressable` — visible at rest, on the column's own left edge, and carrying no more weight than
636
746
  the words. The ink keeps underline honest: blue leaves the surface, muted stays on it.
637
747
 
638
- **Reveal it PLAIN, with no tinted well.** The toggle directly above already says what the text
639
- is; a panel would put two identical recessed boxes on one row meaning two different things — a
640
- verbatim record and a machine's reading of it. Keep the `Inset` for the machine's.
748
+ **Reveal it PLAIN, with no tinted well** the toggle directly above already says what the
749
+ text is. Keep the `Inset` for the machine's.
641
750
 
642
751
  **The capture sits AT the feed**, as a `Composer` — compact until typed into. Logging an entry
643
752
  is this section's input, not a distinct AREA of the record, and a section of its own costs a
644
- rail entry, which is a claim that there is somewhere else to go.
753
+ rail entry.
645
754
 
646
- **The tail FOLDS, it does not page.** A record's feed is read newest-first and the rest is
647
- reference; an unbounded one makes every section below it unreachable by scroll. Show the recent
648
- few with a toggle naming how many are behind it — a register pages, a record's feed folds.
755
+ **The tail FOLDS, it does not page.** An unbounded feed makes every section below it
756
+ unreachable by scroll. Show the recent few with a toggle naming how many are behind it.
649
757
 
650
- The toggle is a `TextDisclosure`, the same control as the transcript's, because it is the same
651
- act: it reveals more of what is already here. It sits on the
652
- SECTION's left edge, not the rows': the rows are inset by the disc rail, and at the fold's line
653
- the rail has ended, so nothing visible occupies that indent. Same for any other fold on the
654
- surface — a document set's "show the rest" is this control too.
758
+ The toggle is a `TextDisclosure`, the same control as the transcript's. It sits on the
759
+ SECTION's left edge, not the rows'. Same for any other fold on the surface — a document set's
760
+ "show the rest" is this control too.
655
761
  - **No Handoff section.** Progress owns it: it says where the record SITS and carries the act
656
- that moves it, so a second section repeating the handoff was one more place to look for one
657
- answer. What that costs is explicit: the sibling record a handoff creates has no home on this
658
- surface. It belongs on the Progress stage that produced it if an app needs it back.
762
+ that moves it. The sibling record a handoff creates has no home on this surface; it belongs on
763
+ the Progress stage that produced it if an app needs it back.
659
764
 
660
765
  **Activity is not that trail, and the line is the SUBJECT.** Activity carries what a person
661
766
  said or sent to a counterparty; the handoff trail carries where the record moved and which
662
767
  field changed. Put desk transitions in Activity and it becomes the audit log this template
663
- declined to build — a feed of everything, scanned for nothing.
664
-
665
- Recall is the undo: confirmed, it withdraws the sibling, clears the custody stamp that
666
- handoff wrote, and returns the record to the desk BEFORE it — one step, never a jump to the
667
- first desk, which would silently drop the desks between. History is NEVER erased (the trail
668
- keeps both marks) and the CTA returns, so redo is possible. **Only the LATEST handoff offers
669
- Recall.** Undoing a superseded one would leave the record behind a sibling that still exists
670
- — the stage and the linked records would disagree — so a walk-back is one handoff at a time.
768
+ declined to build.
769
+
770
+ Recall is the undo: confirmed, it withdraws the sibling, clears the custody stamp that handoff
771
+ wrote, and returns the record to the desk BEFORE it — one step, never a jump to the first
772
+ desk, which would silently drop the desks between. History is NEVER erased (the trail keeps
773
+ both marks) and the CTA returns, so redo is possible. **Only the LATEST handoff offers
774
+ Recall.** Undoing a superseded one would leave the record behind a sibling that still exists,
775
+ so a walk-back is one handoff at a time.
671
776
  - **Danger zone** — with the full section anatomy (heading + description) like every other
672
777
  section; the fenced red `DangerZone` card is the section's BODY.
673
778
  - A LEFT OUTLINE RAIL (`MenuButton` + `useSectionNav`) — **the rail is the page's COMPLETE
@@ -677,25 +782,42 @@ billing, and quick-capture templates. Top → bottom:
677
782
  current-section bar opening a section-picker POPOVER, anchored to that bar's own button, whose
678
783
  items carry the same `current`. Anchored, not a `Modal`: a modal escapes its container, so the
679
784
  same surface hosted in a drawer would take the whole window over the register behind it.
680
- - **The page layout is the DOCS layout — the reading column is CENTRED IN THE VIEWPORT, and
681
- the rail sits in its left gutter.** The `ScrollView` is FULL-WIDTH — **never cap it at
682
- `CONTENT_MAX`, or the scroll container becomes the column and the gutters go wheel-dead**
683
- (a capped scroller leaves the page either side of it ignoring the wheel).
684
- Inside it sits one centred row (`width: "100%"`,
685
- `maxWidth: GUTTER * 2 + CONTENT_MAX`, `alignSelf: "center"`, `flexDirection: "row"`) of
686
- three parts: a gutter RESERVED for the rail (`width: GUTTER` — the rail's own `RAIL_W`
687
- PLUS `RAIL_GAP`, the docs' 3rem of breathing room, so the rail never butts against the
688
- column), the reading column (`flex: 1, minWidth: 0`), and an EMPTY right gutter of the
689
- same `GUTTER` that BALANCES the rail — without it the column sits half a rail right of
690
- centre. (The public docs pages get this balance for free: their right-hand TOC is the
691
- counterweight. A record surface has no TOC, so the gutter is simply reserved.) The rail is
692
- NOT window chrome: no full-height border, not glued to the window edge it FLOATS over
693
- its reserved gutter (`position: "absolute"`, `left` mirroring the row's centring maths),
694
- so it stays put while the record scrolls **without** web-only `position: sticky` (which RN
695
- does not type) and without stealing the page's scrollable width. The rail earns its place
696
- only while the centred column still has room beside it (`pageWidth >= GUTTER * 2 + 560`);
697
- below that it collapses to the pinned bar. Sizing the column is the SHELL's job — inner
698
- blocks are plain `width: "100%"`, never a second `maxWidth` + `alignSelf: "center"`.
785
+ - **The page layout is the DOCS layout — the reading column is CENTRED IN THE VIEWPORT, and the
786
+ rail sits in its left gutter.** The `ScrollView` is FULL-WIDTH — **never cap it at
787
+ `CONTENT_MAX`, or the scroll container becomes the column and the gutters go wheel-dead** (a
788
+ capped scroller leaves the page either side of it ignoring the wheel). Inside it sits one
789
+ centred row (`width: "100%"`, `maxWidth: GUTTER + CONTENT_MAX + PANEL_GUTTER`, `alignSelf:
790
+ "center"`, `flexDirection: "row"`) of three parts: a gutter RESERVED for the rail (`width:
791
+ GUTTER` — the rail's own `RAIL_W` PLUS `RAIL_GAP`, the docs' 3rem of breathing room, so the
792
+ rail never butts against the column), the reading column (`flex: 1, minWidth: 0`), and a right
793
+ gutter of `PANEL_GUTTER` (`PANEL_W` + the same `RAIL_GAP`) that BALANCES the rail without it
794
+ the column sits half a rail right of centre and that the discussion panel then floats over,
795
+ exactly as the rail floats over its own. The rail is NOT window chrome: no full-height border,
796
+ not glued to the window edge it FLOATS over its reserved gutter (`position: "absolute"`,
797
+ `left` mirroring the row's centring maths), so it stays put while the record scrolls
798
+ **without** web-only `position: sticky` (which RN does not type) and without stealing the
799
+ page's scrollable width. Both gutters earn their place only while the reading column still
800
+ clears its FLOOR beside them (`pageWidth >= PAGE_PAD * 2 + GUTTER + PANEL_GUTTER +
801
+ CONTENT_FLOOR` every term the row spends, `PAGE_PAD` included, or the check passes while the
802
+ column sits under the floor the same code declares); below that they collapse onto the pinned
803
+ bar. Sizing the column is the SHELL's job — inner blocks are plain `width: "100%"`, never a
804
+ second `maxWidth` + `alignSelf: "center"`.
805
+
806
+ ### Anything that ADVANCES a record must be able to walk it back
807
+
808
+ This holds wherever the progression is rendered — a stage CTA on a register row, a tick in a
809
+ drawer's checklist, a bulk action over a selection, the handoff compound below. **Every control
810
+ that moves a record forward ships its inverse, in reach of the person who pressed it.**
811
+
812
+ A one-way control makes a misclick permanent, and stage CTAs invite the misclick: the row leaves
813
+ the tab the moment it is pressed, so the feedback for "that was wrong" is the record vanishing,
814
+ with no obvious way back.
815
+
816
+ The undo belongs **beside the forward act, as its secondary** — the same pairing the kit already
817
+ uses for a commit and the control that undoes it. Do not bury it in an `ActionMenu`. Where a
818
+ stage is genuinely irreversible (money has moved, a document has been filed, something left the
819
+ building), the honest answer is not a missing undo — it is a confirmation *before* the act, and
820
+ copy that says why it cannot be taken back.
699
821
 
700
822
  ### The handoff shape — a CONNECTED checklist, and where it lives
701
823
 
@@ -707,57 +829,47 @@ It is NOT its own template. A handoff is something a RECORD does, so it renders
707
829
  that record — see `tpl_record` § Progress, where the desks (Sales → Operations → Accounting) are
708
830
  `ChecklistGroup`s and the milestones they produce are the rows.
709
831
 
710
- **Two levels, because a desk and a milestone are not peers.** The ladder was briefly one flat run
711
- of eight — "Operations" beside "Collected" — which asked the reader to know which name was a PLACE
712
- and which was an EVENT. Grouping the milestones under the desk that produces them says it in the
713
- shape instead.
832
+ **Two levels, because a desk and a milestone are not peers.** One flat run "Operations" beside
833
+ "Collected" — asks the reader to know which name is a PLACE and which is an EVENT; grouping the
834
+ milestones under the desk that produces them says it in the shape instead.
714
835
 
715
- **A group is a name and nothing else.** No control: a phase owns no completion of its own, so any
716
- ring it wore would report without responding and one identical to the pressable rings beneath it
717
- invites the press it refuses, which is worse than showing nothing, because the reader clicks and
718
- the product says nothing back. No BODY either: a condition or an act hung off a heading leaves the
719
- reader working out which of the rows below it they were about.
836
+ **A group is a name and nothing else.** No control: a phase owns no completion of its own, and a
837
+ ring identical to the pressable rings beneath it invites the press it refuses. No BODY either: a
838
+ condition or an act hung off a heading leaves the reader working out which of the rows below it
839
+ they were about.
720
840
 
721
- **Rows tick ONE at a time, and position is the highest stamp.** That is what lets one click say
722
- "everything up to here": ticking `Delivered` puts the record there and the rungs below it read
723
- done immediately. The alternative a parent tick that fills its children marks the same rows
724
- but INVENTS their dates, writing today onto milestones that happened on days nobody recorded. A
725
- rung the record passed without a recorded day shows a BLANK date, which is the honest answer to
726
- "when" and stays one click from being filled.
841
+ **Rows tick ONE at a time, and position is the highest stamp**: ticking `Delivered` puts the
842
+ record there and the rungs below it read done immediately. The alternative a parent tick that
843
+ fills its childrenmarks the same rows but INVENTS their dates, writing today onto milestones
844
+ that happened on days nobody recorded. A rung the record passed without a recorded day shows a
845
+ BLANK date, one click from being filled.
727
846
 
728
847
  **A checklist REPORTS.** It does not collect and it does not preview: a field some section owns is
729
848
  NAMED as a gap by a `ChecklistNote` whose `action` jumps there, never edited in the row and never
730
849
  rendered as a value. The row's OWN stamp (`trailing`) and a value with no other home
731
850
  (`ChecklistField`) are the two exceptions.
732
851
 
733
- **It replaced a per-desk CHECKLIST without the line, and that swap is the lesson.** Ticking boxes
734
- described the WORK; it never said where the record SAT, so the reader inferred position from which
735
- group still had unticked rows — and the handoff CTA sat in a different section entirely, the act
736
- divorced from the state that gated it. A connected run says the position outright, and the act
737
- rides the row that owes it. `connected={false}` is for rows that are genuinely N independent
738
- items; a record's own progress asserts sequence.
852
+ A connected run says the position outright, and the act rides the row that owes it.
853
+ `connected={false}` is for rows that are genuinely N independent items; a record's own progress
854
+ asserts sequence.
739
855
 
740
856
  Built on `checklist` (`Checklist`, `ChecklistGroup`, `ChecklistItem`, `ChecklistNote`,
741
857
  `ChecklistActions`, `ChecklistField`), which composes over `Stepper` — `Stepper` renders POSITION,
742
858
  this adds the anatomy that turns a position into a workspace.
743
859
 
744
- Three rules the anatomy holds, each learned from getting it wrong:
860
+ Rules the anatomy holds:
745
861
 
746
862
  - **Only the OWED row carries an act.** A control on an unreached row invites acting out of
747
863
  order — and where position is derived from the highest stamp, one stray press jumps the record
748
864
  past the desk that owns it. On a passed row it re-offers something already done.
749
865
  - **A condition belongs to its row.** "Sent back", "rejected", "waiting on the customer" are
750
- facts about ONE position, so they render inside it (`ChecklistNote`) rather than as a `Callout`
751
- above the run, which reads as "something is wrong with this record" and sits nowhere near the
752
- control that answers it — and nowhere near enough to say WHICH row it is about.
866
+ facts about ONE position, so they render inside it (`ChecklistNote`) rather than as a
867
+ `Callout` above the run, which sits nowhere near the control that answers it.
753
868
  - **A passed row stays correctable** — its stamp is editable, which is how a date recorded wrong
754
- gets fixed and how a record walks BACK. Otherwise a value typed wrong becomes unreachable the
755
- moment the record moves on, and the only fix is direct table access.
756
- - **The stage that OPENS gets stamped by the act that opened it**, and the TERMINAL act is not
757
- a handoff. Handing off asks whom (a dialog) and writes that answer into the receiving stage's
758
- own fields — coming up blank re-asks a question just answered. Finishing has no receiving
759
- desk, so it asks for confirmation and nothing else; routing it through the same dialog is how
760
- a template ends up asking "Assignee at Closed" and then confirming nothing.
869
+ gets fixed and how a record walks BACK.
870
+ - **The stage that OPENS gets stamped by the act that opened it**, and the TERMINAL act is not a
871
+ handoff. Handing off asks whom (a dialog) and writes that answer into the receiving stage's
872
+ own fields. Finishing has no receiving desk, so it asks for confirmation and nothing else.
761
873
 
762
874
  **Altitude rule**: the pipeline is ONE record's progress. N records each at their own stage is
763
875
  a REGISTER (`tpl_item_list`) with a derived stage column — the same ladder module feeding both,
@@ -784,8 +896,7 @@ grouped sections, and the aligned rows. It demonstrates:
784
896
  - A trailing `ActionMenu` (the `⋯` overflow — a `danger` Remove behind a confirm `Alert`).
785
897
  - CUSTOM pressable cells (files, the action link) hover with the **background-tint WASH**
786
898
  (`zinc-100`) — the ROW's own hover language, distinct from the field frame a `bare` editor
787
- fades in on its own hover. (The row washes; the field's edge draws — the two
788
- hover languages, one per surface.)
899
+ fades in on its own hover.
789
900
  - Per-group add rows that pre-set the group's field (align with the exported `gridRowStyle`),
790
901
  and a Draft-from-notes CTA.
791
902
 
@@ -871,10 +982,10 @@ proposes parameters. The panel carries the single Download.
871
982
  Look-up-and-explain (classification, tariff/fee lookup, policy Q&A, a spec/compliance desk).
872
983
  Describe the subject on the LEFT (`Composer` + `AgentRun`), the agent RANKS the matching
873
984
  candidates (nearest matches, the top one wearing a Recommended `Badge`), you pick one and its
874
- structured ANSWER pins on the RIGHT — the code, the exact breakdown as hand-composed spec
875
- rows, policy `Callout`s, `Confidence`, and `Sources`. The flow is input → matches → pick, not
876
- a single confident verdict: classification is ambiguous, so the alternatives are first-class.
877
- Refine the description in a follow-up and the ranking updates.
985
+ structured ANSWER pins on the RIGHT — the code, the exact breakdown as hand-composed spec rows,
986
+ policy `Callout`s, `Confidence`, and `Sources`. The flow is input → matches → pick, not a single
987
+ confident verdict: the alternatives are first-class. Refine the description in a follow-up and
988
+ the ranking updates.
878
989
 
879
990
  ### The document desk — in `tpl_record`
880
991
 
@@ -889,17 +1000,15 @@ so review opens at the top):
889
1000
  - **Extract** (read the documents, fill the record) — a `DetailTable` of proposed values, each
890
1001
  row a label plus a `DiffValue` and its own Keep/Drop, with `useChangeSet` holding the
891
1002
  verdicts. **Editing IS the review**; the four shapes are ONE component — an add omits
892
- `before`, a removal omits `after` (the struck value IS the change), an update passes both,
893
- and a conflict is a `DiffValue` on its placeholder over `CardSelectItem` candidates plus a
1003
+ `before`, a removal omits `after` (the struck value IS the change), an update passes both, and
1004
+ a conflict is a `DiffValue` on its placeholder over `CardSelectItem` candidates plus a
894
1005
  type-another-value option, Keep gated until one is picked. Proposed line items arrive as a
895
1006
  `Table` with `DiffMark` leading each row — a set of lines reads like the ledger it is about.
896
1007
  The line table also works the ATTACHMENT case: a dropped invoice's proposal is WHICH LINE it
897
1008
  belongs to, so the Document column is a `DiffValue` whose sides are file chips, and a file
898
- replacing one already filed shows both with the superseded one struck. Its untouched lines
899
- are deliberate — a fixture where every row changed would prove nothing about the screen that
900
- matters, and they are why an untouched row draws NO mark: the eye finds the filled discs
901
- among the blanks without reading the column.
902
- Keep-all + one outcome-named Apply commit.
1009
+ replacing one already filed shows both with the superseded one struck. Its untouched lines are
1010
+ deliberate — they are why an untouched row draws NO mark. Keep-all + one outcome-named Apply
1011
+ commit.
903
1012
  - **Cross-check** (compare the documents against the record and each other) — ranked,
904
1013
  display-only `Finding`s the human acts on, closing in a recorded verdict.
905
1014
  - **Edit with AI** — the `askAi` handoff: dialogue-shaped file iteration happens in the