@lotics/ui 47.6.0 → 47.6.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.
Files changed (63) hide show
  1. package/AGENTS.md +22 -3
  2. package/MIGRATION.md +61 -62
  3. package/docs/ai_patterns.md +9 -2
  4. package/docs/catalog.md +37 -3
  5. package/docs/composition.md +148 -10
  6. package/docs/data_entry.md +8 -0
  7. package/docs/reviewing.md +51 -2
  8. package/examples/tpl_attendance.tsx +1 -1
  9. package/package.json +3 -1
  10. package/src/alert.tsx +1 -1
  11. package/src/avatar.tsx +1 -1
  12. package/src/avatar.web.tsx +1 -1
  13. package/src/avatar_group.tsx +1 -1
  14. package/src/bar_chart.tsx +10 -1
  15. package/src/board.tsx +1 -1
  16. package/src/button.tsx +4 -4
  17. package/src/cell_stack.tsx +140 -0
  18. package/src/column_filter.tsx +1 -1
  19. package/src/combobox.tsx +2 -2
  20. package/src/comments_thread.tsx +2 -2
  21. package/src/count.tsx +1 -1
  22. package/src/date_calendar.tsx +2 -2
  23. package/src/date_filter.tsx +1 -1
  24. package/src/diff_value.tsx +2 -2
  25. package/src/eyebrow.tsx +1 -1
  26. package/src/file_thumbnail.tsx +2 -2
  27. package/src/file_thumbnail_grid.tsx +1 -1
  28. package/src/filter_chip.tsx +5 -2
  29. package/src/finding.tsx +19 -2
  30. package/src/form_field.tsx +1 -1
  31. package/src/inline_date_picker.tsx +1 -1
  32. package/src/inline_edit.tsx +2 -1
  33. package/src/line_chart.tsx +3 -1
  34. package/src/list_item.tsx +1 -1
  35. package/src/loading.tsx +1 -1
  36. package/src/matrix.tsx +1 -1
  37. package/src/member_chip.tsx +1 -1
  38. package/src/member_profile_card.tsx +1 -1
  39. package/src/menu_list_item.tsx +2 -2
  40. package/src/metric.tsx +1 -1
  41. package/src/option_list.tsx +1 -1
  42. package/src/page_header.tsx +26 -25
  43. package/src/pagination.tsx +1 -1
  44. package/src/pie_chart.tsx +4 -2
  45. package/src/radio_picker.tsx +3 -3
  46. package/src/section_heading.tsx +74 -5
  47. package/src/segmented_control.tsx +1 -1
  48. package/src/select.tsx +1 -1
  49. package/src/shortcut_badge.tsx +1 -1
  50. package/src/stacked_bar_chart.tsx +1 -1
  51. package/src/switcher.tsx +1 -1
  52. package/src/table.tsx +46 -8
  53. package/src/tabs.tsx +2 -2
  54. package/src/text.tsx +2 -7
  55. package/src/text_ink.ts +96 -0
  56. package/src/text_link.tsx +1 -1
  57. package/src/text_utils.ts +1 -46
  58. package/src/thumbnail_stack.tsx +1 -1
  59. package/src/time_picker.tsx +1 -1
  60. package/src/timeline.tsx +1 -1
  61. package/src/tokens.ts +28 -4
  62. package/src/tooltip.tsx +1 -1
  63. package/src/uploading_thumbnail.tsx +1 -1
package/AGENTS.md CHANGED
@@ -16,9 +16,9 @@ CURRENT major only — upgrading an app across majors is `MIGRATION.md`.
16
16
  |---|---|
17
17
  | [docs/catalog.md](./docs/catalog.md) | **The complete inventory** — incl. `just_arrived` (`useJustArrived` + `TableRow`'s `justArrived`: the one-shot wash a row wears when it LANDS, so a realtime push is visible rather than one row of two hundred silently changing) — Reach-by-role (each data role → the ONE canonical component) + every `@lotics/ui/<module>` entry point (incl. `@lotics/ui/vite`'s `loticsOptimizeDeps` + `loticsResolve()` — the pre-bundle list and the whole `resolve` block a custom-code app's `vite.config.ts` imports rather than hand-carries, dev-link included). Read before building any screen; reuse first. |
18
18
  | [docs/data_entry.md](./docs/data_entry.md) | Which editing pattern for which job — inline edit, fieldset forms, choosing a CHOICE control by option count, find-or-create (`Combobox`), line items, handoffs, phased records, billing, tags, dispositions, attachments (`InlineFiles` for a record ROW, the `FilesEditor` COMPOUND for a whole section, plus the three-way file INTAKE), stage gates, and the commit-on-blur vs action-press ordering law. |
19
- | [docs/ai_patterns.md](./docs/ai_patterns.md) | AI acts, the human stays in charge — composer, live run feed (`AgentRun`), the one law's split — it turns on WHO supplied the values; findings, provenance, confidence; **after the run** — a stored record that fills up from several writers; the whole run in a dialog, **stopping**; **review surfaces compose from atoms** — `DiffValue`, `DiffMark`, `useChangeSet`. |
20
- | [docs/composition.md](./docs/composition.md) | The design-language contract — **the form comes before the treatment** (name what the subject IS before reaching for parts), canvas + content column, heading altitude, register vs inset rows, the button ladder, master-detail `Drawer`, the register's rhythm, craft and TRIAGE bands, where the accent goes, theming as an app-level ESCAPE HATCH, color discipline, typography, **one fact one surface** (a chooser over the thing it chooses is a SELECTOR, not a second card grid; a control that only swaps one number for another is replaced by showing both), **a width floor answers can this be READ and never can this be operated** (the operating question passes a row whose every string is already cut), **a status column earns its slot by carrying a REASON rather than a state** (the floor asks for the fact to be visible, not for a badge), **a row subject's supporting line takes a REFERENCE and never a sentence** (an explanation truncates to the clause the reader already knew), **a list stays fast by keeping IDENTITY rather than memoizing** (key by the record not the index; `memo` is the last resort and does nothing against a freshly-built prop), and where a commit sits. |
21
- | [docs/reviewing.md](./docs/reviewing.md) | **Reviewing a screen you built — and the answer when one "looks bad"** — the other docs say what good looks like; this one says how to find out whether you achieved it. Run it BEFORE the deploy: a typecheck, a lint, a test and `app check` are all blind to how a screen reads. GATES that outrank every treatment question, then the probes, each naming what to COLLECT and the SIGNATURE in the numbers — including the truncation sweep (a row whose halves both measure fine and whose text is cut) the summary-vs-facet check (a strip that keeps describing the unfiltered set) and the derivable-column test (a status badge its neighbour already answers) and the cross-FRAME check (what the screen destroys when a row arrives — the one defect that exists in no single frame). Plus the extraction snippets, what measurement cannot see, and how to act on a finding. |
19
+ | [docs/ai_patterns.md](./docs/ai_patterns.md) | AI acts, the human stays in charge — composer, live run feed (`AgentRun`), the one law's split — it turns on WHO supplied the values; findings, provenance, confidence (`Finding` is filed here because AI produces most of them, NOT because it is AI-only — a rules-derived contradiction is as in scope as an extraction's); **after the run** — a stored record that fills up from several writers; the whole run in a dialog, **stopping**; **review surfaces compose from atoms** — `DiffValue`, `DiffMark`, `useChangeSet`. |
20
+ | [docs/composition.md](./docs/composition.md) | The design-language contract — **the form comes before the treatment** (name what the subject IS before reaching for parts), canvas + content column, heading altitude, register vs inset rows, the button ladder, master-detail `Drawer`, the register's rhythm, craft and TRIAGE bands, where the accent goes, theming as an app-level ESCAPE HATCH, color discipline, typography, **one fact one surface** (a chooser over the thing it chooses is a SELECTOR, not a second card grid; a control that only swaps one number for another is replaced by showing both), **a width floor answers can this be READ and never can this be operated** (the operating question passes a row whose every string is already cut), **a status column earns its slot by carrying a REASON rather than a state** (the floor asks for the fact to be visible, not for a badge), **an identity mark's GLYPH is derived from the name's distinguishing part** (an organization's first word is its legal form, so untreated every row marks one letter), **a row subject's supporting line takes a REFERENCE and never a sentence** (an explanation truncates to the clause the reader already knew), **a list stays fast by keeping IDENTITY rather than memoizing** (key by the record not the index; `memo` is the last resort and does nothing against a freshly-built prop), and where a commit sits. |
21
+ | [docs/reviewing.md](./docs/reviewing.md) | **Reviewing a screen you built — and the answer when one "looks bad"** — the other docs say what good looks like; this one says how to find out whether you achieved it. Run it BEFORE the deploy: a typecheck, a lint, a test and `app check` are all blind to how a screen reads. GATES that outrank every treatment question, then the probes, each naming what to COLLECT and the SIGNATURE in the numbers — including the truncation sweep (a row whose halves both measure fine and whose text is cut) the summary-vs-facet check (a strip that keeps describing the unfiltered set) and the derivable-column test (a status badge its neighbour already answers) the first-line drift a column shows when its cell is sometimes one line and sometimes two, and the cross-FRAME check (what the screen destroys when a row arrives — the one defect that exists in no single frame). Plus the extraction snippets, what measurement cannot see, and how to act on a finding. |
22
22
  | [docs/testing.md](./docs/testing.md) | Driving the kit in a browser — the three anatomies where the a11y tree says one thing and a driver must do another: a `PressDoor` row whose named button always intercepts pointer events (by design), portalled overlays that render at the top of the DOM, and custom pointer drag that `dragTo` cannot move. |
23
23
  | [docs/templates.md](./docs/templates.md) | The map of `examples/tpl_*.tsx` — what shape each template solves and which to start from (copy + adapt, never import) — plus the record-surface composition rules (pipeline order, static shape, decision budget) and the ACTIVITY shape — a communications feed where the row's label is the GIST and the body varies by medium, one anatomy rather than a row type per kind. |
24
24
 
@@ -81,6 +81,19 @@ CURRENT major only — upgrading an app across majors is `MIGRATION.md`.
81
81
  identity. And a radius is a PROPORTION of the box (`proportionalRadius`), because a component
82
82
  sized by data renders as a pill at one height and barely a corner at another.
83
83
  → [composition.md §"A body, not an edge"](./docs/composition.md).
84
+ - **A subject over one qualifying line is `CellStack`, never two `Text`s in a `View`.** It owns
85
+ the whole stacked-pair rule — which rung the caption takes (from what ELSE separates the pair),
86
+ which lines are tightened, and the zero gap. A hand-rolled pair gets one of the three wrong
87
+ silently: the excess comes from the LINE BOX, so no margin and no gap exists to explain it and
88
+ no spacing probe can see it.
89
+ → [catalog.md §"Lists, tables & registers"](./docs/catalog.md),
90
+ [composition.md §"Typography"](./docs/composition.md).
91
+ - **An exception a CHECK produced is `Finding`; a state with nothing to contradict it is a
92
+ `Callout`.** The discriminator is the shape of what was found, never who found it — two
93
+ readings that disagree plus a magnitude is a `Finding` whether an extraction or a rules engine
94
+ compared them. Reaching for a Callout on a real contradiction throws away the ranking, the
95
+ delta and the provenance, and the call site cannot get them back.
96
+ → [catalog.md §"Status / feedback"](./docs/catalog.md), `src/finding.tsx`.
84
97
  - **`Badge` = STATUS only; supporting detail is the muted second line.** A type / category /
85
98
  attribute / count is not a status — it belongs under its identity as `size="xs" color="muted"`,
86
99
  never a second chip. →
@@ -151,6 +164,12 @@ CURRENT major only — upgrading an app across majors is `MIGRATION.md`.
151
164
  `detail` + `expanded` reveal the detail beneath the row; keep the drawer for a heavy form.
152
165
  → [composition.md §"No dead rows"](./docs/composition.md),
153
166
  [catalog.md §"Lists, tables & registers"](./docs/catalog.md).
167
+ - **A register with a TRAILING CONTROL beside multi-line cells passes `Table align="top"`.**
168
+ Centring is per cell, so one taller cell drops the control below the field it acts on by a
169
+ data-dependent amount. Without such a control keep the default — centring is what makes a
170
+ one-line cell sit level with its two-line neighbours rather than stranded at the top.
171
+ → [composition.md §"No dead rows"](./docs/composition.md),
172
+ [reviewing.md probe 8d](./docs/reviewing.md).
154
173
  - **ONE LEFT EDGE — a row that paints a wash BLEEDS it, and never insets its content.**
155
174
  `marginHorizontal: -ROW_WASH_BLEED` absorbed by an equal `paddingHorizontal` is net zero, so
156
175
  a row's content sits on its container's own edge. **An indent is legible only when something
package/MIGRATION.md CHANGED
@@ -101,7 +101,68 @@ exported for anything that needs the same lane geometry.
101
101
  There are deliberately **no dependency connectors**. Lane order and bar position already carry
102
102
  the sequence, and elbow arrows over an already-ruled grid restate it as line noise.
103
103
 
104
+ ## 47.0.0
105
+ **`Text`'s `color` drops every palette name, and `inverted` becomes `onInverse`.** The vocabulary
106
+ is now seven ROLES and no shades: `default`, `muted`, `inactive`, `onInverse`, `danger`, `warning`,
107
+ `success`. `zinc-900` / `zinc-700` / `zinc-500` / `zinc-400` no longer typecheck.
108
+
109
+ ```diff
110
+ -<Text color="zinc-500">{caption}</Text>
111
+ +<Text color="muted">{caption}</Text>
112
+ -<Text color="inverted">{label}</Text>
113
+ +<Text color="onInverse">{label}</Text>
114
+ ```
115
+
116
+ Map them: `zinc-900` → `default` (it was the same ink under a second name), `zinc-700` and
117
+ `zinc-500` → `muted`, `zinc-400` → `inactive`. **Text previously set to `zinc-500` gets slightly
118
+ darker**, which is the point — it clears AA on white by 0.33 (4.83:1) and FAILS on `zinc-100`, the
119
+ kit's own selected-row ground, at 4.40:1, for text at `sm`/`xs` where the bar is 4.5. A call site
120
+ naming a shade cannot know which ground it will land on; a role is resolved once against the
121
+ darkest ground the kit paints. `text_ink_contrast.test.ts` now asserts this for every ink, over a
122
+ list the `TextColor` type is DERIVED from, so a new ink cannot ship unchecked.
123
+
124
+ `inverted` → `onInverse` names the GROUND rather than the operation: it is the one ink that is
125
+ illegal on the canvas, and the contract a caller needs is "there must be a dark surface under this".
126
+
127
+ **`TextColor`, `getTextColor` and `TEXT_COLORS` moved** from `@lotics/ui/text_utils` to
128
+ **`@lotics/ui/text_ink`**, which is react-native-free so `@lotics/ui/tokens` can be imported by an
129
+ app that hand-rolls plain DOM. `text_utils` keeps the input-metrics exports.
130
+
131
+ **New: the ink vocabulary is addressable.** `TEXT_INK` is an alias table (`muted` → `zinc.600`),
132
+ `textInkTokens()` serializes both layers in the W3C Design Tokens format with the alias intact for
133
+ a Figma Variables import, and `getCssVariables()` gains `--lotics-ink-*` pointing at the primitive
134
+ var rather than copying its value.
135
+
104
136
  ## 46.3.0
137
+ **Six components lay themselves out differently at NARROW widths, and the three charts no longer
138
+ claim their host's height.** No type changes and nothing to edit — but each of these was invisible
139
+ at 1280 and therefore invisible to the width most reviews measure, so a screen you signed off at a
140
+ desk width may look different on a phone now (better, is the intent).
141
+
142
+ - **`Tabs` scrolls.** The strip was a plain row with no wrap and no scroller, so once the options
143
+ exceeded the container the trailing tabs were cut by whatever clipped the surface — a tab band
144
+ partitions a register, so on a phone a whole band was unreachable. It is now a horizontal
145
+ `ScrollView`. If you wrapped `Tabs` in your own scroller, delete yours; nesting two is what
146
+ makes a strip that scrolls in one direction only.
147
+ - **`BarChart`, `LineChart` and `PieChart` are content-sized.** All three declared `flex: 1` on
148
+ their root, which RN-Web resolves to a zero basis on the parent's main axis — so dropping one
149
+ into a card body (a vertical stack) collapsed it to no height and painted its fixed-height
150
+ content outside the card. `Funnel` and `Breakdown` were content-sized all along. **If you
151
+ neutralised this by wrapping a chart in a row, that wrapper is now unnecessary**; a chart that
152
+ should fill a box is told so on the box.
153
+ - **`SectionHeading`'s actions wrap as ONE group.** With two verbs, one stayed on the title row
154
+ and the other dropped to the next at a different indent, so a single action group rendered as
155
+ two unrelated controls. Two or more trailing children are now wrapped in one row that moves
156
+ together, and the heading top-aligns so that group rides the title's FIRST line instead of the
157
+ centre of a column whose height came from however many lines the description wrapped to. One
158
+ trailing child keeps the previous geometry exactly.
159
+ - **`Finding`'s headline stacks at narrow widths.** Side by side, a title long enough to wrap
160
+ broke AROUND the delta and the sentence read as two fragments with a number interposed. It
161
+ never overflowed — it only wrapped mid-sentence — so no clipping probe saw it.
162
+ - **`PageHeader`'s description sits with the title, above the actions.** It rendered after the
163
+ whole title row, and that row wraps, so at narrow widths the reading order became title → verbs
164
+ → the sentence explaining them. `actions` was effectively unusable on any header with a
165
+ `description`.
105
166
 
106
167
  **A `Text` with `numberOfLines` now declares `flexShrink: 1` + `minWidth: 0` for you.**
107
168
  A clamp that cannot shrink cannot clamp: on web a clamped `Text` is `white-space: nowrap`, so
@@ -189,7 +250,6 @@ so `status` changes identity whenever a decision does — which a screen that me
189
250
  `review.status` needs, and which a predicate held out of the dependency lists could not give.
190
251
 
191
252
  ## 46.0.0
192
-
193
253
  **`AgentRun` / `AgentProgress` drop `stepsLabel`, and the `agentRun` locale slice drops `steps`.**
194
254
  The run row no longer prints a step count — not on the folded run row, not on a settled
195
255
  `ToolGroup` header. The tally was a number a reader could act on in no way: while the run is live
@@ -216,7 +276,6 @@ now share a name once the work is expanded (the run row and the step it was name
216
276
  `getAllByLabelText(...)[0]` over `getByLabelText` there.
217
277
 
218
278
  ## 45.9.0
219
-
220
279
  **`BrandMark` no longer carries product logos, and `SwitchButton` takes `leading` instead of
221
280
  `brand`.** `BRAND_NAMES` drops `gmail`, `outlook` and `google-drive`; the kit's marks are the
222
281
  single-colour CC0 silhouettes that NAME a channel — `facebook`, `zalo`, `linkedin`, `x`,
@@ -234,7 +293,6 @@ A retired name no longer typechecks, and a bundle compiled against an older kit
234
293
  where the mark was rather than throwing.
235
294
 
236
295
  ## 45.5.0
237
-
238
296
  **`InlineSelect` and `InlineMemberSelect` are a `combobox`, not a `button`.** They rest as a
239
297
  `<div role="combobox">` carrying `aria-expanded`, where they rested as a `<button>`
240
298
  (`InlineMemberSelect` is built on `InlineSelect`, so it follows). Other inline fields are
@@ -273,7 +331,6 @@ owners, since ticking the box also opened the file. A slot the docs tell you to
273
331
  cannot be inside the control.
274
332
 
275
333
  ## 45.0.0 — a section heading stops shouting over the panel that holds it
276
-
277
334
  *A MAJOR, because an export was removed.* Every consumer pinning `^44.x` is excluded by design:
278
335
  the removal below breaks a build, and the altitude change silently re-ranks headings in any pane
279
336
  that already composes its body in a content region. Neither should arrive with a routine install —
@@ -330,7 +387,6 @@ sat a rung AND a weight below its own peers, measured live as 12px/400 beside 14
330
387
  structural role, same treatment.
331
388
 
332
389
  ## 44.9.3 — an option's description stops being erased, and a pivot's headers rejoin the ramp
333
-
334
390
  **`OptionList` no longer suppresses `getOptionDescription` when `renderOptionContent` is set.**
335
391
  The two are independent facts about an option — how its value renders, and a subtitle under it —
336
392
  and one was erasing the other. A caller who wanted both had one slot for two things, so they put
@@ -347,7 +403,6 @@ reading as a caption that lost its value."* A screen carrying both a `Table` and
347
403
  rendered the same structural role two ways.
348
404
 
349
405
  ## 44.9.3 — the rules probe could not see a rule
350
-
351
406
  **`docs/reviewing.md`'s probe 3 collector missed border-drawn rules and counted transparent
352
407
  spacers.** Docs only, no code change — but this is the method app authors run, so it shipped a
353
408
  blind spot: it selected on "thin and wide" with no paint check, which counts a zero-height flex
@@ -361,7 +416,6 @@ edge rather than the box origin, and reports what each rule SEPARATES — which
361
416
  actually asks.
362
417
 
363
418
  ## 44.9.2 — a pivot's figures actually align, and a ledger row keeps its own name
364
-
365
419
  **`Matrix.Grid`'s cell stretches its figure instead of centring it, so 44.9.1's right-alignment
366
420
  actually applies.** A `Text` centred by its parent shrink-wraps, so the `align="right"` added in
367
421
  44.9.1 had nothing to align against and the value cells never moved — while the headers and totals,
@@ -393,7 +447,6 @@ own code; that is the component's job.
393
447
  for one line.
394
448
 
395
449
  ## 44.9.1 — a pivot's figures align, and a ledger row stops losing its own name
396
-
397
450
  No API is removed and nothing fails to compile. Three renders change.
398
451
 
399
452
  **`Matrix` right-aligns its figures instead of centring them.** Every value, column
@@ -426,7 +479,6 @@ keeps 55 and the caption 16. Any caller that dropped `meta` at small widths to d
426
479
  can stop.
427
480
 
428
481
  ## 44.8.0 — a value column gets its own floor, and eight defaults stop fighting their own docs
429
-
430
482
  No API is removed. Three of these change what an existing screen renders, so read them even
431
483
  though nothing will fail to compile.
432
484
 
@@ -532,7 +584,6 @@ heading row WRAPS rather than crushing its title; `Accordion`'s meta yields befo
532
584
  `CardHeaderTitle` reads the ⓘ's name from the locale pack; and `ReferenceField`'s peek footer
533
585
  gives its clear the same resting surface as the verb beside it.
534
586
  ## 44.5.0 — `Pagination` drops the page index
535
-
536
587
  The pager rendered a range AND a page index — `16–30 of 45` beside `Page 2 of 3`. Both answer
537
588
  "where am I", and the second enables no decision the first does not: how much is left is in the
538
589
  total, and whether this is the end is in the disabled arrow. The index is gone; the range is the
@@ -555,7 +606,6 @@ total, so a `Table` paired with the pager takes **`counted`** instead of `count`
555
606
  the matched total prints twice a few rows apart.
556
607
 
557
608
  ## 44.4.0 — `MemberChip`'s `marker` is gone
558
-
559
609
  A minor that broke a type, recorded here under its exact version rather than held
560
610
  for a major: `marker` existed for one release, had a single consumer inside the
561
611
  kit, and underlined the chip's name to say "this opens something".
@@ -578,7 +628,6 @@ requested `md`), because two lines of text stand taller than a 28px avatar. Sing
578
628
  beside a two-line chip will need the number.
579
629
 
580
630
  ## 44.0.0 — `Finding` is horizontal, and `FindingComparison` is gone
581
-
582
631
  `Finding` stacked six blocks — badge, title, detail, a labelled row per side, a hairline, the
583
632
  delta, then `Sources` chips. It spent roughly 200px to say one sentence and one figure, buried
584
633
  the delta in the middle where nothing scans it, and named every source twice: once in the
@@ -616,7 +665,6 @@ composes. Readings render at `sm` rather than `xs` — they are the values the f
616
665
  and hierarchy comes from weight and colour, never from shrinking the evidence.
617
666
 
618
667
  ## 43.5.0 — registers, type and theming all change appearance
619
-
620
668
  No API is removed and nothing throws, but four DEFAULTS moved, so every register and every avatar
621
669
  in your app renders differently on upgrade. Each is reversible at the call site if you need the
622
670
  old look while you catch up.
@@ -708,7 +756,6 @@ prop (`"open"` — no gallery), `Avatar`'s `shape` prop, and the `avatar_tone` /
708
756
  `avatar_props` / `type_ramp` modules.
709
757
 
710
758
  ## 43.3.0 — a `bare` inline field sits on the column, and Enter finishes a one-line one
711
-
712
759
  Two fixes to `Inline*`, both of them behaviour a caller cannot see in a prop name.
713
760
 
714
761
  **A `bare` field now aligns its TEXT, not its box.** The field surface carries 8px
@@ -739,7 +786,6 @@ paragraph break and blur saves, exactly as before. **If you wrote
739
786
  line budget** — that is now what says so.
740
787
 
741
788
  ## 43.0.0 — a time is PICKED, and there is nothing to type
742
-
743
789
  `TimePicker` is no longer a segmented text field. It is the value on a pressable
744
790
  control — the bordered surface a `Select` uses — that opens an **hour / minute /
745
791
  day-period** picker. `InlineTimePicker` is the same thing on the inline shell.
@@ -813,7 +859,6 @@ needs the pointer, set `CURSOR_ACTION` in its style — and if it is built on th
813
859
  react-native `Pressable`, note that the pointer is what you get by DEFAULT, from
814
860
  react-native-web, unless you set `CURSOR_DEFAULT`.
815
861
  ## 42.5.0 — an expanded `Timeline` row shows its whole label
816
-
817
862
  The label was clamped to two lines whether the row was open or shut. Expanding a
818
863
  row is a request to see more of what is there, so a label that stays truncated
819
864
  through it leaves the caller exactly one way to make its own label readable:
@@ -826,7 +871,6 @@ Nothing else changes: a collapsed row is identical, and a row whose label alread
826
871
  fitted in two lines is identical open or shut.
827
872
 
828
873
  ## 42.4.0 — `Picker` is named by its `FormField`, and its way back to empty has a name
829
-
830
874
  Two fixes to the same control, both about a thing on screen that the control did
831
875
  not actually say.
832
876
 
@@ -867,7 +911,6 @@ site passing `placeholder` **as** the empty option's name should move that strin
867
911
  which is why they were all rendering blanks.
868
912
 
869
913
  ## 42.0.0 — `DateFilter`'s presets are a listbox, and "Custom" is not one of them
870
-
871
914
  Three defects with one root: the preset row used EMPHASIS to mean "current state", inside a
872
915
  panel where emphasis means "action".
873
916
 
@@ -901,7 +944,6 @@ object you pass — it is an excess property now and the compiler will point at
901
944
  matches no preset simply leaves every row unselected, which is what it always meant.
902
945
 
903
946
  ## 41.4.0 — embedded markdown is demoted in the OUTLINE, not only on the type ladder
904
-
905
947
  `<Markdown variant="embedded">` sized its headings down and left them as the tags the author
906
948
  wrote, so an `h2` inside a record value still landed in heading navigation as a PEER of the
907
949
  page's own sections. Measured on a record surface, the outline read:
@@ -918,7 +960,6 @@ a section's level. **Nothing to migrate** — document-scale markdown is untouch
918
960
  embedded type scale is unchanged.
919
961
 
920
962
  ## 41.3.0 — `Timeline` rows, embedded markdown, and a disclosure that is not a link
921
-
922
963
  Nothing here removes an API or breaks a type. Two of the changes alter what your rows render on
923
964
  sight; the rest is additive.
924
965
 
@@ -1001,7 +1042,6 @@ Anything that MUTATES still carries a control surface — `Button` in chrome, `I
1001
1042
  field. Nothing to migrate: existing `Link`/`TextLink` usage is unaffected.
1002
1043
 
1003
1044
  ## 41.1.0 — `FileRows` no longer captions every row with the file type
1004
-
1005
1045
  `meta` defaulted to the file-type label, so a row read `contract.pdf` with `PDF` under it. The
1006
1046
  type was already stated three other ways — the `FileBadge`'s colour, the label printed inside
1007
1047
  that glyph, and the filename's own extension — so the caption was a fourth statement of one
@@ -1024,7 +1064,6 @@ Better: pass something the NAME does not already carry — what an invoice cover
1024
1064
  it, when it landed. That is what a second line is for.
1025
1065
 
1026
1066
  ## 41.0.0 — `RingGauge` is `ProgressRing`, and circular progress is one component
1027
-
1028
1067
  `RingGauge` is **deleted**. `ProgressRing` replaces it and covers both what it did and the
1029
1068
  bare arc it could not.
1030
1069
 
@@ -1066,7 +1105,6 @@ decoration inside a labelled control, and it means a bare ring must never be the
1066
1105
  carrying a meaning — whatever owns it says what it measures.
1067
1106
 
1068
1107
  ## 40.0.0 — `PopoverContent` decides its own presentation; `small` is gone
1069
-
1070
1108
  `PopoverContent` took a `small` prop meaning "render as a bottom sheet". It now asks
1071
1109
  `useScreenSize()` itself. **Delete the prop** — passing it is a type error.
1072
1110
 
@@ -1095,7 +1133,6 @@ remains exported for its real uses — deciding whether a surface is a `Dialog`
1095
1133
  at all, sizing touch targets, showing a keyboard shortcut.
1096
1134
 
1097
1135
  ## 39.0.0 — `PageHeader.leading` is `trailing`, and it renders AFTER the title
1098
-
1099
1136
  38.0.0 shipped `leading`, a slot before the title. It is now **`trailing`**, in the same
1100
1137
  place in the props but rendered on the other side of the title text.
1101
1138
 
@@ -1120,7 +1157,6 @@ The layout law is unchanged and now covers both: the TITLE gives way and wraps,
1120
1157
  `trailing` and `actions` keep their width.
1121
1158
 
1122
1159
  ## 38.0.0 — the `lg` avatar is a control (40), not a size (48)
1123
-
1124
1160
  `AVATAR_PX.lg` was 48 and `AVATAR_TEXT.lg` was `lg` type. They are now **40** and `md`
1125
1161
  type. Every `size="lg"` avatar — `Avatar`, `MemberChip`, `GroupAvatar` — renders 8px
1126
1162
  smaller. Nothing to pass; there is no way to ask for 48.
@@ -1148,7 +1184,6 @@ a title, hiding the CTA at narrow widths, or capping the header's width, that wo
1148
1184
  is now what is distorting the layout — drop it.
1149
1185
 
1150
1186
  ## 37.0.0 — `PageContent`'s side gutter is a token, and responsive
1151
-
1152
1187
  `PageContent` padded `16` at every width. It now uses `pagePad` from
1153
1188
  `@lotics/ui/spacing` — **16 on a phone, 32 wider**. Nothing to pass; the only visible
1154
1189
  change is that a wide page breathes where it used to sit 16px off the chrome.
@@ -1175,7 +1210,6 @@ whether `PageContent` would do — it carries the centred `maxWidth` column and
1175
1210
  title band most screens re-type by hand.
1176
1211
 
1177
1212
  ## 36.0.0 — seven components stopped announcing English; their strings moved to the pack
1178
-
1179
1213
  **Nothing to change if you pass a shipped pack** (`en` / `vi`) — both carry every
1180
1214
  new entry, and no component API changed. A HAND-BUILT pack must add them:
1181
1215
 
@@ -1229,7 +1263,6 @@ If a surface of yours is genuinely not a button, say so (`accessibilityRole="lin
1229
1263
  …) — or reach for `PressableRow`, which carries no role by design.
1230
1264
 
1231
1265
  ## 35.0.0 — `FilterChip` has no Clear footer; the × is the clear
1232
-
1233
1266
  `FilterChip` no longer renders a Clear button in its popover footer. **Nothing to
1234
1267
  change at a call site** — `onClear` and `clearLabel` are unchanged, and the × on
1235
1268
  the pill still clears, still carries `clearLabel` as its tooltip, and still
@@ -1258,7 +1291,6 @@ controls with no per-option deselect), put one in the `footer` slot, which is
1258
1291
  still there and is now the only thing that draws a footer band.
1259
1292
 
1260
1293
  ## 34.0.0 — one avatar rung fits a control band
1261
-
1262
1294
  **`AVATAR_PX.md` is 28, not 36.** Nothing to change at a call site — `md` is still
1263
1295
  the default and still shows two initials — but every `Avatar`, `MemberChip` and
1264
1296
  `GroupAvatar` on that rung renders 8px smaller (its initials drop a type rung with
@@ -1302,7 +1334,6 @@ Two knock-on effects worth looking at once:
1302
1334
  It always claimed to; it was 2px more padded, invisible at one line.
1303
1335
 
1304
1336
  ## 33.0.0 — `DrawerScrollArea`
1305
-
1306
1337
  A `Drawer` gets the content primitive its siblings already had, named to match
1307
1338
  `DialogScrollArea`. **Wrap your drawer body in it and delete the padding you were
1308
1339
  applying:**
@@ -1343,7 +1374,6 @@ Import `DRAWER_GUTTER` in both cases; a hardcoded `20` stops matching the day it
1343
1374
  moves.
1344
1375
 
1345
1376
  ## 32.0.0 — `Table`'s `align` prop is gone; rows are centred
1346
-
1347
1377
  `align?: "center" | "top"` and the `TableAlign` type are removed. `center` was the
1348
1378
  default and nothing shipped on `top`, so a caller passing nothing is unaffected.
1349
1379
 
@@ -1364,7 +1394,6 @@ stack — `computeTableFit` already drops to stacked below the register floor. R
1364
1394
  that, or move the content to a record page. Do not reintroduce a cross-axis knob.
1365
1395
 
1366
1396
  ## 31.0.0 — avatar `size` is a rung, not a pixel count
1367
-
1368
1397
  `Avatar`, `MemberChip` and `GroupAvatar` no longer take `size?: number`. They take
1369
1398
  `size?: AvatarSize` — `"sm" | "md" | "lg" | "xl"` = **24 / 36 / 48 / 72** — defaulting to `md`.
1370
1399
 
@@ -1405,7 +1434,6 @@ The initials' TYPE SIZE now follows the rung instead of being a fixed `xs`. Most
1405
1434
  large rungs: an avatar that was 80px wore 12px letters and looked empty; at `xl` it wears 28px.
1406
1435
 
1407
1436
  ## 30.0.0 — a washed row bleeds; content sits on the container's edge
1408
-
1409
1437
  No API changed. Every register, table and choice list **moves its content 20px
1410
1438
  left** onto its container's own edge, because `PressableRow` (all three
1411
1439
  variants), `ChoiceList` and `Table` now bleed their hover/selection wash outward
@@ -1428,7 +1456,6 @@ padding, so a column that used to drop at a given width may now survive. Nothing
1428
1456
  that was visible becomes hidden — the change only ever un-drops.
1429
1457
 
1430
1458
  ## 29.0.0 — review surfaces compose from atoms
1431
-
1432
1459
  `ChangeReview` and its family are DELETED, replaced by three layout-free pieces. The
1433
1460
  `change_review` export and the `changeReview` locale slice are gone. Apps pinned to 28.x are
1434
1461
  unaffected until they upgrade.
@@ -1454,7 +1481,6 @@ The opinionated stack is not gone, it is demoted: build it in your own screen fr
1454
1481
  Every app needed a slightly different shape, which is why one container could not serve them.
1455
1482
 
1456
1483
  ## 28.2.0 — `SubsectionHeadingTitle` takes a `description`
1457
-
1458
1484
  Additive; nothing to change. The heading ramp's three rungs are meant to carry the SAME slots
1459
1485
  and differ only in type size — `description` was missing from the middle one, so a group that
1460
1486
  needed a muted line under its title had to borrow the section rung above it or hand-roll a
@@ -1474,7 +1500,6 @@ a dropped file says which bucket it belongs to) carries those regions at subsect
1474
1500
  The rung is unchanged (`###`, lg semibold, level 3) — only the slot is new.
1475
1501
 
1476
1502
  ## 28.1.0 — `ChecklistItem` can announce more than its title
1477
-
1478
1503
  Additive; nothing to change. `accessibilityLabel` overrides the row's accessible name, which
1479
1504
  still defaults to `title`. It exists for a surface carrying one checklist PER person, party or
1480
1505
  period, where the same row labels repeat and only the headings between them tell the copies
@@ -1488,7 +1513,6 @@ and always about a CONTROL's text rather than a step's, which is why it did not
1488
1513
  of `task` into `checklist`.
1489
1514
 
1490
1515
  ## 28.0.0 — `task` and `pipeline` merge into `checklist`
1491
-
1492
1516
  Two families had converged on one anatomy: a ring, a title, a value beside it, a muted note, a
1493
1517
  detail block, an optional grouping level. What differed was a LINE between the rings — a mode,
1494
1518
  not a family. The proof: a single session added `trailing`/`meta` to `TaskTitle` **and**,
@@ -1552,7 +1576,6 @@ it, and the rungs it passes show a BLANK date, which is the honest answer to "wh
1552
1576
  others.
1553
1577
 
1554
1578
  ## 27.15.0 — a register row's text is selectable, and a drag no longer opens the record
1555
-
1556
1579
  No API change, but two behaviors changed on **every** `PressableRow` / `TableRow` / `PressDoor`
1557
1580
  in every app:
1558
1581
 
@@ -1572,7 +1595,6 @@ reads as a selection, not a press. Programmatic `fireEvent.click` / `press()` ar
1572
1595
  The shipped `en` / `vi` packs carry it; a HAND-AUTHORED pack must add it or it will not typecheck.
1573
1596
 
1574
1597
  ## 27.13.3 — a stacked `Table` row's identity cell no longer renders its column label
1575
-
1576
1598
  No API change, but the rendered output changed on NARROW containers only (below the
1577
1599
  two-column floor, where `Table` stacks). The identity cell — column 0 — used to render as a
1578
1600
  `DetailRow` spread like every other cell, so its column label appeared beside the value. It
@@ -1591,7 +1613,6 @@ and it had nowhere to go: a label and a full-width identity on one line with not
1591
1613
  made the label collapse under a long value, which is what a narrow register used to render.
1592
1614
 
1593
1615
  ## 27.4.0 — a `Select` trigger announces as `combobox`, not `button`
1594
-
1595
1616
  No API change, but the DOM and the accessibility tree changed, so **a test or script that
1596
1617
  finds a Select by its button role must be updated**:
1597
1618
 
@@ -1611,7 +1632,6 @@ Note for `searchable`: `OptionList`'s filter input is a combobox too, so scope s
1611
1632
  to the trigger (or use `.first()`) rather than assuming one match per Select.
1612
1633
 
1613
1634
  ## 27.2.0 — a `LedgerGroup`'s sum moved below its rows
1614
-
1615
1635
  No API change; the rendering moved. A summed group used to print `LABEL … sum` as an eyebrow
1616
1636
  ABOVE its rows and now closes them: the rows come first, then `Label sum` at `sm/medium`,
1617
1637
  with no eyebrow (the label travels with the sum instead of being said twice).
@@ -1623,7 +1643,6 @@ bare rather than grouping it (a one-row group's sum only restates the row). Omit
1623
1643
  remains fully supported for a statement whose groups all name categories without summing them.
1624
1644
 
1625
1645
  ## 27.0.0 — `TextButton` is REMOVED; an act carries a control surface
1626
-
1627
1646
  **`@lotics/ui/text_button` is gone, with no replacement component.** It was the wrong
1628
1647
  abstraction, and 25.0.0 had already retreated from it inside `ReferenceField`.
1629
1648
 
@@ -1661,7 +1680,6 @@ two rungs — `Button` (40px, a surface) and `InlineButton` (28px, filled, on a
1661
1680
  on a value whose press opens a peek — were never this component's job and keep working.
1662
1681
 
1663
1682
  ## 26.0.0 — a section's ADD moves to its heading row; `EmptyState` loses its verb
1664
-
1665
1683
  **`EmptyState.action` is REMOVED — no replacement on that component.** Two different things
1666
1684
  were going into it, and neither belonged:
1667
1685
 
@@ -1712,7 +1730,6 @@ whose title row carries an inline control. Hand-rolling that row leaves the mark
1712
1730
  text line while the label centres in the taller row, so every label reads ~10px low.
1713
1731
 
1714
1732
  ## 25.0.0 — `ReferenceField` edits in place; `onRemove` becomes `onClear`
1715
-
1716
1733
  **`onRemove` is now `onClear` — a pure rename, identical behaviour.** The act was always
1717
1734
  "detach and leave it empty"; only the VERB was wrong. Named "Remove" and coloured `danger`, a
1718
1735
  recoverable unset wore the ink reserved for destruction. Rename the prop and nothing else
@@ -1750,7 +1767,6 @@ on the left act on the LINK, Edit and Open on the right act on the RECORD, Edit
1750
1767
  filled-dark rung and handing it to Save when the draft opens.
1751
1768
 
1752
1769
  ## 24.0.0 — `TextLink` navigates, `TextButton` acts
1753
-
1754
1770
  ```tsx
1755
1771
  <TextLink onPress={selectAll}>Select all</TextLink> // BEFORE
1756
1772
  <TextButton onPress={selectAll}>Select all</TextButton> // AFTER — same look, role=button
@@ -1787,7 +1803,6 @@ by construction and keeps a 40px target via `hitSlop`. Do NOT reach for a fill-l
1787
1803
  padding, so it reads as indented against the labels it should line up with.
1788
1804
 
1789
1805
  ## 23.0.0 — the `Inline*` editors are WHITE, and `variant` is renamed
1790
-
1791
1806
  ```tsx
1792
1807
  <InlineSelect variant="cell" … /> // BEFORE
1793
1808
  <InlineSelect variant="bare" … /> // AFTER — same axis, named for the LOOK not the use
@@ -1824,7 +1839,6 @@ Row hover on a dense surface is unaffected: a `TaskItem`/`PressableRow` still wa
1824
1839
  What changed is only what the editor inside it looks like at rest.
1825
1840
 
1826
1841
  ## 23.0.0 — `DetailRow.trailing` and `DetailTable.trailingWidth` are DELETED
1827
-
1828
1842
  A row's third column is gone; a `DetailTable` is label + value, and the value fills what the
1829
1843
  label leaves. `InlineButton` (`@lotics/ui/inline_button`) is the replacement for the case that
1830
1844
  mattered.
@@ -1872,7 +1886,6 @@ Porting, by what the slot held:
1872
1886
  (`description` / `warning` / `error`).
1873
1887
 
1874
1888
  ## 23.0.0 — `LinkedRecordBox` is DELETED; a reference is a field
1875
-
1876
1889
  ```tsx
1877
1890
  // BEFORE — a bordered card, its own section, three surfaces for one name
1878
1891
  <LinkedRecordBox
@@ -1927,7 +1940,6 @@ Porting, piece by piece:
1927
1940
  `tpl_record`'s Customer and Handoff sections are the worked examples.
1928
1941
 
1929
1942
  ## 22.0.0 — `RangeSlider` is `Slider`, and single-value is a mode on it
1930
-
1931
1943
  ```ts
1932
1944
  import { RangeSlider, rangeSummary } from "@lotics/ui/range_slider"; // BEFORE
1933
1945
  import { Slider, rangeSummary } from "@lotics/ui/slider"; // AFTER
@@ -1958,7 +1970,6 @@ the DOM as a bare `role="slider"` with no value, so a screen reader read "slider
1958
1970
  `View` in both targets. A test asserting the old (absent) attributes was asserting a bug.
1959
1971
 
1960
1972
  ## 21.2.0 — `Stepper` is a list, not a progressbar
1961
-
1962
1973
  No API changed, but the RENDERED role did, so a test that queried it breaks:
1963
1974
 
1964
1975
  ```ts
@@ -1974,7 +1985,6 @@ positions is a `list` whose live entry has `aria-current="step"`. `StepProgress`
1974
1985
  values it was missing.
1975
1986
 
1976
1987
  ## v21 from 20.x
1977
-
1978
1988
  **`FilesEditor` is a COMPOUND.** It rendered a fixed toolbar — Upload, Select, Download all,
1979
1989
  swapping into a select-mode row whose actions hid behind a generic "Menu" — and that shape cost
1980
1990
  two things in real screens. A HOST verb had nowhere to go, so a surface that needed one (an AI
@@ -2031,7 +2041,6 @@ function ReadWithAi() {
2031
2041
  ```
2032
2042
 
2033
2043
  ## v20 from 19.x
2034
-
2035
2044
  **`@lotics/ui/section` is DELETED.** Two modules exported a `Section` — the layout grammar's
2036
2045
  card-less region (`section_heading`) and a standalone titled block with its own `title` string,
2037
2046
  `collapsible` flag and imperative `SectionHandlers.expand/collapse`. The second one predated both
@@ -2065,7 +2074,6 @@ Compose the heading rung exactly as for a static section — `SectionHeadingTitl
2065
2074
  carries the same slots, so a disclosure header loses no affordance.
2066
2075
 
2067
2076
  ## v19 from 18.x
2068
-
2069
2077
  **`TaskFields` is DELETED, with `fieldsWidth`.** A task row was carrying a
2070
2078
  value COLUMN — a width declared on the list and reserved by every row. It aligned, and it read
2071
2079
  badly: a two-word field name sat at the title's origin while its control started a quarter of
@@ -2131,7 +2139,6 @@ wrap against), and `TaskDetail` now ends on the content edge instead of overhang
2131
2139
  inset.
2132
2140
 
2133
2141
  ## v18 from 17.x
2134
-
2135
2142
  **`TimePicker` is a segmented field, not `<input type="time">`.** A native time input takes
2136
2143
  its 12- vs 24-hour form from the BROWSER's UI locale and ignores the `lang` attribute
2137
2144
  entirely, so a Vietnamese screen on an en-US browser rendered "01:45 PM" beside its own
@@ -2151,7 +2158,6 @@ The value is unchanged — canonical 24-hour `"HH:mm"` either way. Two prop chan
2151
2158
  than spreading the locale pack) must add it.
2152
2159
 
2153
2160
  ## v17 from 16.x
2154
-
2155
2161
  **`Checklist` / `ChecklistRow` are DELETED.** They are replaced by the `Task` compound
2156
2162
  (`@lotics/ui/task`), which composes the anatomy instead of configuring it:
2157
2163
 
@@ -2198,7 +2204,6 @@ it through (`state={s.status === "done" ? "done" : s.status === "doing" ? "parti
2198
2204
  rather than leaving the ring binary.
2199
2205
 
2200
2206
  ## v16 from 15.x
2201
-
2202
2207
  **`Confidence` IS now a callout with a REQUIRED basis** — `children` (what was checked and
2203
2208
  what came of it) is mandatory, the meter + level phrase head the block, and the box stays
2204
2209
  white with a hairline border at every level (the meter is the only colored element). The
@@ -2211,7 +2216,6 @@ Additively in v16: `ResultHeader` (`@lotics/ui/result_header`) — the save-dire
2211
2216
  outcome strip; see ai_patterns §the one law for the receipt composition.
2212
2217
 
2213
2218
  ## v15 from 14.x
2214
-
2215
2219
  `AgentRun`/`AgentProgress`'s **`labelForTool`** prop was replaced by **`labelForCall`** — the
2216
2220
  callback now receives the whole call (`AgentToolCall = { toolName, input, state }`) instead of
2217
2221
  just the name, so labels can vary by input and by the new **`awaiting`** step state (ai's
@@ -2220,7 +2224,6 @@ an expanded step's Output panel), `onRetry` (a retry button under the terminal e
2220
2224
  the new `ApprovalPrompt` + `MessageActions` components with their locale slices.
2221
2225
 
2222
2226
  ## v14 from 13.x
2223
-
2224
2227
  The ai-sdk `UIMessage.parts` shape IS the `AgentRun` contract — the bespoke transcript type is
2225
2228
  gone. `AgentRun`/`AgentProgress`'s **`items: AgentRunItem[]`** was replaced by **`parts:
2226
2229
  readonly AgentUIPart[]`**, and the **`AgentRunItem`** / **`AgentRunStep`** / **`AgentStepStatus`**
@@ -2232,7 +2235,6 @@ parts. Additively: a run-level **`error`** prop for the BREAKING failure that li
2232
2235
  — and tool I/O now expands IN PLACE on press instead of opening a `Peek` popover.
2233
2236
 
2234
2237
  ## 13.9.0 — a BREAKING type change in a MINOR
2235
-
2236
2238
  **`ClarifyOption.description` went optional → REQUIRED** (every answer explains itself). It
2237
2239
  shipped in a minor, which is why it surprises an app on `^13`: a `<Clarify options={…}>` whose
2238
2240
  options carry only `label` stops typechecking on any 13.9.0+ install. Fix by writing the
@@ -2241,14 +2243,12 @@ eyebrow was dropped (the question text stays), and `allowCustom` ("Other…" fre
2241
2243
  `ClarifyWizard` were added.
2242
2244
 
2243
2245
  ## v13 from 12.x
2244
-
2245
2246
  `RadioPicker`'s **`direction`** prop was removed — the picker is always the stacked column of
2246
2247
  full-width choices. A former `direction="row"` usage just drops the prop. Additively, `DetailRow`
2247
2248
  and `FormField` gained a **`warning`** field annotation (amber, the third tone between
2248
2249
  `description` and `error`).
2249
2250
 
2250
2251
  ## v12 from 11.x
2251
-
2252
2252
  Two exports were removed: **`CommentComposer`** (compose comments with THE kit `Composer` —
2253
2253
  attach via `actionsButton`/`files`; `CommentList` is unchanged) and **`DetailRow.info`** (field
2254
2254
  annotations are explicit — use the new **`DetailRow.description`** under the value and
@@ -2262,7 +2262,6 @@ resting chip gained 6px vertical padding (identical for one-line values; multi-l
2262
2262
  content now breathes).
2263
2263
 
2264
2264
  ## v11 from ≤10.x
2265
-
2266
2265
  The type scale was re-slotted — the display sizes gained the missing `##` step: `xl` is now 22/24
2267
2266
  (section title), `xxl` is 28/32 (page/record title — the OLD `xl` look), and the new `xxxl` is
2268
2267
  32/48 (hero numbers — the OLD `xxl` look). Migrate an app with two renames IN THIS ORDER: first
@@ -607,13 +607,20 @@ fail → low) — never render a fabricated score.
607
607
 
608
608
  ## Findings — evidence, not writes
609
609
 
610
- `Finding` (`@lotics/ui/finding`) is one ranked insight from an AI check — a cross-check
611
- discrepancy, an audit observation, a briefing item — laid out HORIZONTALLY, in the order a reader
610
+ `Finding` (`@lotics/ui/finding`) is one ranked discrepancy from a cross-check — a contradiction
611
+ between two readings, an audit observation, a briefing item — laid out HORIZONTALLY, in the order a reader
612
612
  needs it: a severity dot, the `title`, and the `delta` right-aligned on that same line, then the
613
613
  `readings` beneath — ONE PER LINE, each a dim `source` plus its `value`, never joined by a
614
614
  separator. `detail` is the CONSEQUENCE and is optional — a second sentence restating the title in
615
615
  longer words costs a line and adds nothing.
616
616
 
617
+ **It is described here because AI produces most of them, NOT because it is an AI component.**
618
+ Nothing in it is AI-specific — the labels are four severity words and "difference" — and a
619
+ rules-derived contradiction (two stored figures disagreeing, a carrier moving an ETA) is exactly
620
+ as in scope as an extraction's. The author who reads "AI" as a fence hand-rolls a `Callout` for a
621
+ deterministic check and loses the ranking, the magnitude and the provenance. The test is the
622
+ SHAPE: two readings that disagree → `Finding`; a state with nothing to contradict it → `Callout`.
623
+
617
624
  BOTH readings stay on screen and NEITHER is struck through or marked wrong: a finding reports
618
625
  that two sources disagree, and which one is right is the reader's call. Omit `delta` where the
619
626
  fields are not commensurable — two spellings of a name have no difference to compute. The