@lotics/ui 22.3.0 → 23.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.
Files changed (68) hide show
  1. package/AGENTS.md +9 -2
  2. package/MIGRATION.md +142 -2
  3. package/docs/ai_patterns.md +22 -22
  4. package/docs/catalog.md +119 -98
  5. package/docs/composition.md +134 -42
  6. package/docs/data_entry.md +67 -31
  7. package/docs/templates.md +86 -68
  8. package/examples/tpl_allocate.tsx +5 -5
  9. package/examples/tpl_attendance.tsx +2 -2
  10. package/examples/tpl_calendar.tsx +6 -6
  11. package/examples/tpl_dashboard.tsx +9 -9
  12. package/examples/tpl_dieline.tsx +2 -2
  13. package/examples/tpl_item_list.tsx +64 -37
  14. package/examples/tpl_lookup.tsx +5 -5
  15. package/examples/tpl_pick.tsx +6 -6
  16. package/examples/tpl_pivot.tsx +3 -3
  17. package/examples/tpl_record.tsx +919 -611
  18. package/examples/tpl_report.tsx +2 -2
  19. package/examples/tpl_rollup.tsx +5 -5
  20. package/examples/tpl_shifts.tsx +5 -5
  21. package/examples/tpl_statements.tsx +7 -7
  22. package/examples/tpl_stock.tsx +2 -2
  23. package/examples/tpl_task_board.tsx +42 -26
  24. package/examples/tpl_tower.tsx +6 -6
  25. package/package.json +3 -2
  26. package/src/agent_run.tsx +4 -4
  27. package/src/breakdown.tsx +1 -1
  28. package/src/calendar/calendar_view.tsx +1 -1
  29. package/src/change_review.tsx +9 -8
  30. package/src/chip_group.tsx +12 -2
  31. package/src/choice_list.tsx +2 -2
  32. package/src/confidence.tsx +2 -2
  33. package/src/data_grid.tsx +1 -1
  34. package/src/detail_row.tsx +50 -58
  35. package/src/file_dropzone.tsx +1 -1
  36. package/src/file_gallery_modal.tsx +3 -3
  37. package/src/file_rows.tsx +1 -1
  38. package/src/finding.tsx +4 -4
  39. package/src/form_field.tsx +1 -1
  40. package/src/format_date.ts +2 -2
  41. package/src/heatmap.tsx +1 -1
  42. package/src/inline_button.tsx +84 -0
  43. package/src/inline_date_picker.tsx +17 -10
  44. package/src/inline_edit.tsx +298 -59
  45. package/src/inline_member_select.tsx +8 -3
  46. package/src/inline_number_input.tsx +11 -4
  47. package/src/inline_select.tsx +26 -13
  48. package/src/inline_text_input.tsx +12 -4
  49. package/src/inline_time_picker.tsx +10 -4
  50. package/src/ledger.tsx +2 -2
  51. package/src/locale.tsx +7 -3
  52. package/src/matrix.tsx +1 -1
  53. package/src/number_input.tsx +18 -7
  54. package/src/pipeline.tsx +1 -1
  55. package/src/popover.tsx +1 -1
  56. package/src/press_door.tsx +1 -1
  57. package/src/pressable_highlight.tsx +1 -1
  58. package/src/progress_bar.tsx +3 -3
  59. package/src/record_summary.tsx +2 -2
  60. package/src/result_header.tsx +2 -2
  61. package/src/sequence.tsx +170 -0
  62. package/src/share_or_download.ts +2 -2
  63. package/src/step_progress.tsx +7 -5
  64. package/src/stepper.tsx +1 -1
  65. package/src/task.tsx +6 -6
  66. package/src/text_input_field.tsx +19 -1
  67. package/src/text_utils.ts +1 -1
  68. package/src/linked_record_box.tsx +0 -157
package/AGENTS.md CHANGED
@@ -53,8 +53,15 @@ CURRENT major only — upgrading an app across majors is `MIGRATION.md`.
53
53
  - **A button never contains a control** — a row that presses open AND carries its own controls
54
54
  (a CTA, ⋯, a checkbox, a `Link`) is a role-less `PressableRow` + a **`PressDoor`** sibling
55
55
  (the tab stop, name, focus ring), never `PressableHighlight`, which wraps its children in the
56
- button. `Table`/`TableRow` and `LinkedRecordBox` already do this. →
57
- [composition.md](./docs/composition.md).
56
+ button. `Table`/`TableRow` already does this. → [composition.md](./docs/composition.md).
57
+ - **A reference to another record is a FIELD** — it wears the inline editor's own resting
58
+ surface, carries an **`InlineButton`** Open inside it, and PEEKS its facts on press (detach
59
+ lives in the peek). Never its own Section, never a card, never the other record's fields as
60
+ rows on this one. → [composition.md](./docs/composition.md).
61
+ - **A verb about a VALUE goes on the value** — an `InlineButton` in the field's `actions`
62
+ (Copy, Open), never the row's `trailing`: a control at the page's right edge has to be paired
63
+ back to the field it belongs to, and one row's action makes EVERY row reserve the column.
64
+ → [composition.md](./docs/composition.md).
58
65
  - **Files land THREE ways on any surface that holds them** — the section's Add CTA
59
66
  (`pickFiles`), and a drag OR paste, both from ONE **`<FileDropTarget … paste>`** wrapping the
60
67
  region — a Files section, OR a WHOLE surface (a record's section stack, a register page), so a
package/MIGRATION.md CHANGED
@@ -4,6 +4,146 @@ Breaking changes, newest first — normally per major, plus the rare minor that
4
4
  anyway (recorded under its exact version). The current contract lives in `AGENTS.md` + `docs/`;
5
5
  this file exists only to move an app from one release to the next.
6
6
 
7
+ ## 23.0.0 — the `Inline*` editors are WHITE, and `variant` is renamed
8
+
9
+ ```tsx
10
+ <InlineSelect variant="cell" … /> // BEFORE
11
+ <InlineSelect variant="bare" … /> // AFTER — same axis, named for the LOOK not the use
12
+ ```
13
+
14
+ An inline editor now rests on THE pill surface — **white with a 1px border**, the same one `Chip`,
15
+ `ChipGroup` and a secondary `Button` wear — hovering by darkening that border and adding the 2px
16
+ ring when open. The zinc-50 chip is gone.
17
+
18
+ `InlineEditVariant` survives but is renamed for what it IS rather than where it was used:
19
+ **`"framed"`** (was `"form"`, still the default) keeps that frame visible; **`"bare"`** (was
20
+ `"cell"`) shows nothing at rest and fades the SAME border in on hover. `"cell"` named a use — a
21
+ grid cell — which is why nobody reached for it on the other dense surfaces it suits.
22
+
23
+ The tint existed as the editability affordance (a read-only `InlineStatic` stays flat, so you could
24
+ see what was editable without hovering). That still holds, and reads more plainly: a bordered box
25
+ beside bare text is a stronger signal than a tint. What it also fixes is a fork in the vocabulary —
26
+ a `ChipGroup` sitting in a field row was white-and-bordered next to zinc-50 editors, two species of
27
+ control on one line.
28
+
29
+ - **Rename every `variant="cell"` → `variant="bare"`.** The old literal is a type error, not a
30
+ silent no-op.
31
+ - **`bare` hovers with the BORDER now, not a background wash.** One hover language across the
32
+ kit; the ROW under it still washes as a row, so a task/register row is unaffected.
33
+ - **A `cell` used to read VALUE-FIRST** — the date dropped its resting calendar glyph and the
34
+ select its chevron, on the theory that a column header plus a uniformly-editable grid were
35
+ affordance enough. Both now show on every surface: one control, one promise.
36
+ - **`disabled` still rests FLAT and borderless**, so an inert field never makes the promise.
37
+ - **`InlineButton` inverted with it** — it is `Button secondary`'s zinc-100 fill now, not white,
38
+ and has no border. Its affordance was always CONTRAST with the field beneath it, not a colour;
39
+ a white verb on a white field dissolves.
40
+
41
+ Row hover on a dense surface is unaffected: a `TaskItem`/`PressableRow` still washes as the ROW.
42
+ What changed is only what the editor inside it looks like at rest.
43
+
44
+ ## 23.0.0 — `DetailRow.trailing` and `DetailTable.trailingWidth` are DELETED
45
+
46
+ A row's third column is gone; a `DetailTable` is label + value, and the value fills what the
47
+ label leaves. `InlineButton` (`@lotics/ui/inline_button`) is the replacement for the case that
48
+ mattered.
49
+
50
+ ```tsx
51
+ // BEFORE — the verb in a column the whole table pays for
52
+ <DetailTable labelWidth={150} trailingWidth={88}>
53
+ <DetailRow label="Reference" trailing={<Button title="Copy" color="secondary" onPress={copy} />}>
54
+ <InlineTextInput value={ref} onSave={save} />
55
+ </DetailRow>
56
+ <DetailRow label="Priority"><InlineSelect … /></DetailRow> {/* pays 88px, uses none */}
57
+ </DetailTable>
58
+
59
+ // AFTER — the verb on the field it acts on
60
+ <DetailTable labelWidth={150}>
61
+ <DetailRow label="Reference">
62
+ <InlineTextInput value={ref} onSave={save}
63
+ actions={<InlineButton title="Copy" disabled={!ref} onPress={copy} />} />
64
+ </DetailRow>
65
+ <DetailRow label="Priority"><InlineSelect … /></DetailRow>
66
+ </DetailTable>
67
+ ```
68
+
69
+ `trailingWidth` was declared on the TABLE, so one row's action made EVERY row reserve the
70
+ width — a stack of fields whose values all stopped short of an edge nothing occupied. And the
71
+ control it held was usually about the VALUE, which meant reading a field on the left and
72
+ pairing it with its verb at the page's right edge.
73
+
74
+ Porting, by what the slot held:
75
+
76
+ - **A verb about the value** (Copy, Call, Open) → **`InlineButton`** in the editor's `actions`.
77
+ Pass it UNCONDITIONALLY and `disabled` it when there is nothing to act on: a slot that
78
+ appears once the value is non-empty resizes the field as the user types. `actions` is on
79
+ `InlineTextInput` and on the `InlineEditView` / `InlineEditFrame` engine that custom editors
80
+ build on.
81
+ - **A verb the editor can't host** — the select/date editors pass their ref to a `Popover` as
82
+ its anchor — → compose it into the VALUE cell: a `flexDirection: "row"` with the editor on
83
+ `flex: 1` and the verb beside it. One row deep, no column.
84
+ - **A verb about the FORM, not the value** (a "Fetch" that fills three other fields) → the same
85
+ value-cell composition, or the surface's own footer. It was never a field's verb.
86
+ - **A `Badge` or other qualifier** → beside the value in the value cell. It was never a column.
87
+ - **Units** — already excluded before this release: `InlineNumberInput format`.
88
+
89
+ `DetailRow` keeps `labelWidth`, `labelSize`, `minHeight`, `flat`, and the annotation trio
90
+ (`description` / `warning` / `error`).
91
+
92
+ ## 23.0.0 — `LinkedRecordBox` is DELETED; a reference is a field
93
+
94
+ ```tsx
95
+ // BEFORE — a bordered card, its own section, three surfaces for one name
96
+ <LinkedRecordBox
97
+ icon="building-2" name={c.name} subtitle={c.code}
98
+ facts={[{ label: "Tax ID", value: c.taxId }, { label: "Contact", value: c.contact }]}
99
+ doorLabel={`${c.name} — details`} onOpen={open}
100
+ actions={<><Button title="Remove" color="danger" onPress={detach} /><View style={{ flex: 1 }} /><Button title="Open record" color="secondary" onPress={open} /></>}
101
+ />
102
+
103
+ // AFTER — a value in a row, in whatever section the reference belongs to.
104
+ // It wears the field surface, carries its go-to verb INSIDE, and peeks its
105
+ // facts on press; detach lives in the peek. `tpl_record`'s `ReferenceValue`
106
+ // is the worked composition.
107
+ <DetailRow label="Customer">
108
+ <ReferenceValue name={c.name} code={c.code} facts={…} onOpen={open} onRemove={detach} />
109
+ </DetailRow>
110
+ ```
111
+
112
+ There is no replacement component, because the kit already had the answer and this was the
113
+ exception to it: a person renders as a `MemberChip`, a file as a `FileBadge`, a select option
114
+ as an `OptionBadge`, and a picked value in a field as a `<Chip onDismiss>` (the tags pattern).
115
+ A record was the one reference drawn as a card — an icon tile, a facts grid and an action
116
+ footer for a single name — which made a pointer to another row outweigh the fields it sat
117
+ among and pushed hosts into giving it a whole Section.
118
+
119
+ Porting, piece by piece:
120
+
121
+ - **`icon`** — drop it. The label already names what the reference is; a glyph at value
122
+ altitude is decoration. (Keep one only where the *kind* varies row to row.)
123
+ - **`name` + `subtitle`** — both go INSIDE the pill, name in default ink, subtitle muted. A
124
+ code sitting outside the chip reads as an orphan next to a link.
125
+ - **`facts`** — they were the other record's fields, which never belonged on this one. A fact
126
+ THIS record actually uses (a tax id that gates invoicing) becomes this record's own
127
+ `DetailRow`, or the reference row's `warning` when it is a problem; the rest lives on the
128
+ record `Open` goes to.
129
+ - **`onOpen` / `doorLabel`** — an **`InlineButton`** ("Open") INSIDE the value's surface. The
130
+ whole-box door is gone, but the a11y contract it owned is not: a container with its own
131
+ controls is still a role-less surface + a `PressDoor` sibling, which is what makes pressing
132
+ the value (to peek) and pressing Open two separate acts.
133
+ - **`actions`** — the destructive verb moves into the PEEK's footer (destructive left, go-to
134
+ right), where it answers the question the peek asks. A detach link repeated down a column of
135
+ references is noise for an act most readers never perform.
136
+ - **The STATIC variant** (no `onOpen`, fact values as nodes with inline editors) has no
137
+ successor by design. A record with no surface of its own has no identity to reference — its
138
+ values simply ARE this record's fields, so render them as a `Subsection` of `DetailRow`s with
139
+ no box around them.
140
+ - **A whole Section built around one reference** collapses into the row. Its find-or-create
141
+ `Combobox` is the field's EMPTY state (same value slot), its create `Dialog` stays a dialog,
142
+ and its validity `Callout` becomes `DetailRow warning`. Remove the section's outline-rail
143
+ entry with it.
144
+
145
+ `tpl_record`'s Customer and Handoff sections are the worked examples.
146
+
7
147
  ## 22.0.0 — `RangeSlider` is `Slider`, and single-value is a mode on it
8
148
 
9
149
  ```ts
@@ -53,7 +193,7 @@ values it was missing.
53
193
 
54
194
  ## v21 from 20.x
55
195
 
56
- **`FilesEditor` is a COMPOUND.** It rendered a fixed toolbar — Upload · Select · Download all,
196
+ **`FilesEditor` is a COMPOUND.** It rendered a fixed toolbar — Upload, Select, Download all,
57
197
  swapping into a select-mode row whose actions hid behind a generic "Menu" — and that shape cost
58
198
  two things in real screens. A HOST verb had nowhere to go, so a surface that needed one (an AI
59
199
  read over the picked papers, "send these to the broker") hand-rolled the whole grid and lost the
@@ -90,7 +230,7 @@ the files is now composed below it.
90
230
  | `onDownloadZipSelected` | the same |
91
231
  | `readOnly` | withhold `onAdd`/`onRemove`; the pieces render nothing without a handler, so the composition IS the read-only shape and there is no second mode to keep in sync |
92
232
  | the "Menu" popover | the bar. Actions are visible controls, in the order the surface wants them |
93
- | `labels.menu` · `labels.share` · `labels.downloadZip` | deleted with the acts they named |
233
+ | `labels.menu`, `labels.share`, `labels.downloadZip` | deleted with the acts they named |
94
234
 
95
235
  **No children means no bar** — a grid that previews and nothing else. That is the one rendering
96
236
  path; there is no default toolbar to fall back to.
@@ -21,15 +21,15 @@ the human stays in charge follows the stakes — split by whether a before-state
21
21
  is nothing to diff → **apply directly, then end on the RECEIPT**
22
22
  (`@lotics/ui/result_header`) — the receipt states the outcome and ROUTES; it is never a
23
23
  record editor. Run DETERMINISTIC checks over what the save STORED (a checksum, a count
24
- reconciliation — binary math, never a fabricated score). ONE record: `ResultHeader` (tone ·
25
- outcome title) → the RECEIPT LINES — a `DetailTable` of spread `DetailRow`s (who · how
26
- much · how many · the schedule) — → **`Confidence`**: the level WITH its basis as
24
+ reconciliation — binary math, never a fabricated score). ONE record: `ResultHeader` (tone,
25
+ outcome title) → the RECEIPT LINES — a `DetailTable` of spread `DetailRow`s (who, how
26
+ much, how many, the schedule) — → **`Confidence`**: the level WITH its basis as
27
27
  one block, always (the happy path says what was checked; the flagged path NAMES the exact
28
28
  failing values — the user knows what to verify BEFORE opening). Confidence never floats
29
29
  alone as a bare meter on a result surface. NOTHING in the receipt edits: the RECORD is the edit surface (its
30
30
  flagged cells carry the same amber marks), one press away — correction uses the record's
31
31
  ordinary verbs. SEVERAL records: a compact attention-first RESULT REGISTER (`ListItem` per
32
- record — tone mark · outcome title · key figures · the needs-checking count) whose rows
32
+ record — tone mark, outcome title, key figures, the needs-checking count) whose rows
33
33
  press STRAIGHT THROUGH to the record — no intermediate screen. Never tabs (they cap out),
34
34
  never stacked collapsed cards, never a field wall duplicating the record page. A
35
35
  flagged value names its exact verdict in amber (a reason line under the value, or a check
@@ -69,7 +69,7 @@ Three shapes:
69
69
  the thread, version chains, branching, session memory. An in-app "edit chat" would
70
70
  re-implement all of that inside every app.
71
71
 
72
- The Document desk's Use-AI fork IS this table as UI: Extract data (1) · Cross-check (2) ·
72
+ The Document desk's Use-AI fork IS this table as UI: Extract data (1), Cross-check (2),
73
73
  Edit with AI (3) — one entry point, three outcome shapes
74
74
  ([`tpl_record`](../examples/tpl_record.tsx)'s Documents section).
75
75
 
@@ -151,7 +151,7 @@ Output panel; `undefined` keeps it. Input + error rendering are untouched.
151
151
  **Activity grouping.** Consecutive tool parts fold into ONE group. While the agent is mid-tools
152
152
  the tail group is a SINGLE pulsing row whose label swaps in place as each call fires (no growing
153
153
  stack of dots); once prose resumes the group settles: a single call becomes one done row, and a
154
- multi-call group becomes a persistent "{final action} · {n} steps" HEADER (a `complete` terminal
154
+ multi-call group becomes a persistent "{final action} ({n} steps)" HEADER (a `complete` terminal
155
155
  dot — distinct from the filled `done` step dots; amber `warning` if any call errored) that STAYS
156
156
  PUT and rolls the calls out BELOW it on press. The run ALWAYS ends on the agent's text — there is
157
157
  no global terminal node.
@@ -292,22 +292,22 @@ composes from twelve pieces in three groups (all in `@lotics/ui/change_review`;
292
292
  - **`ChangeFields` + `ChangeField`** — THE field unit. `ChangeFields` is the OPEN form (the
293
293
  section IS the record — an extract dialog's one order: no card chrome), stacking `ChangeField`s
294
294
  with hairline rules. A diff-form `ChangeField` renders, top to bottom: the label (sm medium,
295
- **full colour** — it names the subject of a decision, never `DetailRow`'s muted ink) · the
296
- field's `reasoning` directly under the label · the `−` band when replacing (`before`) · the
295
+ **full colour** — it names the subject of a decision, never `DetailRow`'s muted ink), the
296
+ field's `reasoning` directly under the label, the `−` band when replacing (`before`), the
297
297
  value — `ChangeValueInput` when `onChangeText` is given (the green `+` band; pressing it edits
298
298
  IN THE BAND — a borderless input with identical type metrics, so nothing shifts), the read-only
299
299
  `+` band when `valueReadOnly` or no `onChangeText` (muted `placeholder` while empty), or any
300
300
  input via `children` — compose `<ChangeValueInput unit="pcs" …>` there yourself for a unit
301
301
  suffix (`unit` fixes the suffix outside the editable core — type the number, never the unit;
302
- the built-in default doesn't take it) · conflict `candidates` as full-width decision rows
302
+ the built-in default doesn't take it), conflict `candidates` as full-width decision rows
303
303
  (picked via `onPickCandidate`) + the localized "Type another value" third option
304
304
  (`customValue`/`customSelected`/`onCustomValue`/`onCustomSelect`; the outcome band is read-only
305
305
  via `valueReadOnly` — the decision comes from picking, never from editing the band; gate with
306
- `keepDisabled`) · per-field **Keep/Drop** bottom-right
306
+ `keepDisabled`), per-field **Keep/Drop** bottom-right
307
307
  (`status: "pending" | "kept" | "dropped"`, `onKeep`/`onDrop`/`onUndo`). A pure REMOVAL is
308
308
  `before` with no value — the `−` band alone. A decided field collapses to the compact one-row
309
- card (mark · label → `summary` · Undo). A field with no diff and no decision is the plain
310
- inline label · value row — this form borrows `DetailRow`'s *metrics* (130px label column /
309
+ card (mark, label → `summary`, Undo). A field with no diff and no decision is the plain
310
+ inline label, value row — this form borrows `DetailRow`'s *metrics* (130px label column /
311
311
  40px row) — press-to-edit when `onChangeText` is given.
312
312
 
313
313
  **Limitation:** `ChangeFieldCandidate.source` and `.description` are accepted by the type but
@@ -432,8 +432,8 @@ meter + level phrase head the block, and the REQUIRED `children` carry the basis
432
432
  checked and what came of it ("all 7 references pass their checksum" / "2 of 7 fail (X, Y) —
433
433
  verify against the source"). The box stays white with a hairline border at EVERY level — the
434
434
  meter is the only colored element (the level already lives in the meter and the phrase; a
435
- tinted fill would say it a third time). Pass `level` or a 0–1 `score` (≥0.8 high ·
436
- ≥0.5 medium · else low; `levelFromScore` exported). There is NO standalone meter: a level
435
+ tinted fill would say it a third time). Pass `level` or a 0–1 `score` (≥0.8 high,
436
+ ≥0.5 medium, else low; `levelFromScore` exported). There is NO standalone meter: a level
437
437
  without its reasons cannot be acted on, so it cannot be rendered — and where a compact meter
438
438
  used to decorate a ranked row or a proposal card, remove it (rank, badges, and severity
439
439
  already convey standing). Derive levels HONESTLY (deterministic checks: all pass → high, any
@@ -442,16 +442,16 @@ fail → low) — never render a fabricated score.
442
442
  ## Findings — evidence, not writes
443
443
 
444
444
  `Finding` (`@lotics/ui/finding`) is one ranked insight from an AI check — a cross-check
445
- discrepancy, an audit observation, a briefing item: severity dot-badge + localized word ·
446
- `title` · `detail` · the PROMINENT `metric` (lg semibold, never a side note) + `metricCaption` ·
445
+ discrepancy, an audit observation, a briefing item: severity dot-badge + localized word,
446
+ `title`, `detail`, the PROMINENT `metric` (lg semibold, never a side note) + `metricCaption`,
447
447
  `Sources` chips at the bottom (`sources`/`onOpenSource`), with a `children` slot between body and
448
- sources. Severity → colour: `critical` red · `warning` amber · `info` zinc ("Note") · `positive`
448
+ sources. Severity → colour: `critical` red, `warning` amber, `info` zinc ("Note"), `positive`
449
449
  emerald ("On track"); words come from the `finding` locale slice. **Display-only**: a finding
450
450
  informs the action the human takes in the app; it decides nothing itself — no phantom "record
451
451
  verdict" write (a persisted check-status goes stale on the next edit). Stack several most-severe
452
452
  first — inside a `ChangeReview` wrap each in a display-only `Change` (the family's dividers
453
453
  apply). `FindingComparison` is the expected-vs-actual body: each disagreeing side a labeled row
454
- (source · value), a hairline, then the DELTA emphasized — compose it as the finding's children
454
+ (source, value), a hairline, then the DELTA emphasized — compose it as the finding's children
455
455
  for any one-value-disagrees insight (quantities, totals, dates).
456
456
 
457
457
  ## Session, not chat
@@ -496,10 +496,10 @@ ranked set, don't cram it into chat prose:
496
496
  document-driven records) — the record's files block feeds ONE "Use AI" entry that FORKS into
497
497
  the two document tasks, each a specialized run with a task-pure result: **Extract** (files
498
498
  read → fields already matching fold into one quiet line → every add / update / conflict a
499
- `ChangeField` (the `−` band · the editable value · candidate rows), the record's current value
499
+ `ChangeField` (the `−` band, the editable value, candidate rows), the record's current value
500
500
  a first-class choice — plus proposed new lines as record-body `Change`s → one outcome-named
501
501
  `ChangeReviewActions` commit) and **Cross-check** (documents compared against the record and
502
- each other → ranked `Finding`s — severity · title · the prominent metric · sources — separated
502
+ each other → ranked `Finding`s — severity, title, the prominent metric, sources — separated
503
503
  by hairlines; the findings ARE the outcome the human acts on). The fork carries an OPTIONAL
504
504
  instructions field — the user steers what the agent checks or extracts, so `Finding` serves ANY
505
505
  file-based AI request, not just the stock cross-check. Every file list in the flow opens the
@@ -530,7 +530,7 @@ ranked set, don't cram it into chat prose:
530
530
  The AI vocabulary has **no purple accent and no gimmick glyphs** (no sparkles) — but it is NOT
531
531
  monochrome: **colour is used where it carries meaning, not for decoration.** What the violet
532
532
  sparkle used to carry now reads structurally — **provenance** is a quiet sentence-case microlabel
533
- naming the artifact ("Proposed" · "Match" · "Mismatch" · "Suggested edit" · "Question" — xs,
533
+ naming the artifact ("Proposed", "Match", "Mismatch", "Suggested edit", "Question" — xs,
534
534
  muted, medium; all-caps is banned kit-wide), and the agent's **reasoning** is a left-ruled margin
535
535
  note (a hairline rule + muted text — `ChangeReasoning`), quoted apart from the facts and the
536
536
  human's controls — while **status/severity/diffs use functional colour** the way the rest of the
@@ -546,7 +546,7 @@ kit does:
546
546
  - **ChangeBand** — the removed value on the light **red** band with the `−` marker, the incoming
547
547
  value on the light **emerald** band with `+` (the GitHub-diff idiom, markers in one aligned
548
548
  column); everything else in a review stays neutral — a decided row reads a single emerald
549
- check, an add/remove record card wears the quiet tone wash (50 body · 100 header · 200 border).
549
+ check, an add/remove record card wears the quiet tone wash (50 body, 100 header, 200 border).
550
550
  Colour marks the change, never the chrome.
551
551
  - **Finding severity** — the coloured dot badge (red / amber / zinc / emerald), most severe
552
552
  first.