@lotics/ui 44.7.1 → 44.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +15 -3
- package/MIGRATION.md +110 -5
- package/docs/ai_patterns.md +20 -0
- package/docs/catalog.md +100 -8
- package/docs/composition.md +100 -6
- package/docs/data_entry.md +60 -6
- package/docs/reviewing.md +44 -3
- package/examples/tpl_item_list.tsx +8 -4
- package/package.json +4 -1
- package/src/accordion.tsx +9 -1
- package/src/agent_run_pane.tsx +7 -5
- package/src/card.tsx +7 -1
- package/src/card_select_item.tsx +15 -2
- package/src/checklist.tsx +23 -3
- package/src/control_surface.ts +18 -4
- package/src/data_grid.tsx +9 -1
- package/src/date_range_filter_field.tsx +9 -2
- package/src/date_stamp.tsx +8 -2
- package/src/detail_row.tsx +17 -12
- package/src/empty_state.tsx +9 -2
- package/src/error_state.tsx +9 -2
- package/src/field_annotations.tsx +25 -2
- package/src/finding.tsx +19 -10
- package/src/format_money.ts +16 -2
- package/src/inline_edit.tsx +29 -9
- package/src/inline_select.tsx +8 -1
- package/src/inline_text_input.tsx +6 -7
- package/src/ledger.tsx +5 -2
- package/src/locale.tsx +35 -1
- package/src/number_input.tsx +8 -1
- package/src/reference_field.tsx +9 -2
- package/src/related_record_row.tsx +80 -0
- package/src/running_ledger.tsx +196 -0
- package/src/section_heading.tsx +17 -5
- package/src/select.tsx +16 -16
- package/src/sequence.tsx +41 -25
- package/src/step_progress.tsx +6 -4
- package/src/stepper.tsx +26 -2
- package/src/table_fit.ts +18 -8
- package/src/tabs.tsx +19 -0
- package/src/text_input_field.tsx +35 -1
package/AGENTS.md
CHANGED
|
@@ -16,8 +16,8 @@ CURRENT major only — upgrading an app across majors is `MIGRATION.md`.
|
|
|
16
16
|
|---|---|
|
|
17
17
|
| [docs/catalog.md](./docs/catalog.md) | **The complete inventory** — 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) | **a picker over a select field keeps the field's colours** (`optionPicker` — the hand-rolled `map` to `{value,label}` drops `color`, so a value reads coloured in a register and grey in the editor); Which editing pattern for which job — inline edit, fieldset forms, browser-autofill suppression (search controls only), find-or-create (`Combobox`), line items, handoffs, phased records, billing, tags, dispositions, attachments (`InlineFiles` for a record ROW — list + one CTA, `multiple` decides add-vs-replace; the `FilesEditor` COMPOUND for a whole section — root owns selection/gallery/confirm, you compose the bar, a HOST verb reads `useFilesEditorSelection` — plus the three-way file INTAKE: CTA + `FileDropTarget` + `usePasteFiles`), stage gates, whether a multiline value keeps its fixed reserve or `autoGrow`s (who decides the length — the field, or whoever is typing), the commit-on-blur vs action-press ordering law (the kit gates the press — `pending_commits`). |
|
|
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 (machine → a gate: a diff when something is being replaced, a full editable preview when records are being created from a document; human-typed → save-direct + the `ResultHeader` receipt), findings, provenance, confidence; **after the run** — a stored record that fills up from several writers (a person, a chat agent, an extraction, an automation) and remembers none of them: an unwritten value must not render like a written one, a machine's prose and a person's must not share a treatment, model markdown goes in `variant="embedded"`, and the read path must project every field the write paths set; the UI half of the SDK's [ai doc](../app-sdk/docs/ai.md), the whole run in a dialog (`AgentRunScope`/`AgentRunPane`/`AgentRunActions` — a parked question REPLACES the feed, actions in the footer, **Stop** while streaming), **stopping** (`cancel` stops the run, `abort` only stops listening — so closing a dialog must `cancel` or it keeps billing); **review surfaces compose from atoms** — `DiffValue` (a changed value, droppable in any cell/row/total), `DiffMark` (what happened to the row — ONE circular disc, every surface), `useChangeSet` (accept/reject/undo bookkeeping, no layout) — see [MIGRATION.md](./MIGRATION.md) for the `ChangeReview` family they replace |
|
|
20
|
-
| [docs/composition.md](./docs/composition.md) | The design-language contract — canvas + content column, heading altitude (incl. eyebrow vs group lead — a label is one or the other), banded cards, register vs inset rows (incl. the register laws a row centres its cells by: every cell a FIXED height, a pressable cell on the shared hover token, a column sized by what it carries), the button ladder and **what underlined text may mean** (it GOES somewhere or REVEALS something — never mutates; blue leaves the surface, muted stays on it, and the one in-prose disclosure exception is scoped there), master-detail `Drawer` on a LIST screen vs a child collection's row EXPANDING inside a record, view controls, RECORD EXTENT (one page, sections scrolled to and never routed to), **the register's rhythm** (no hairline between rows — their own height and the hover wash separate them; the register's one line is the band capping the columns, because that is the break whitespace cannot state. Density is `TableRow`'s `minHeight`, never a mode), **a register that TRIAGES** (`TableGroup` — titled bands ordered by what needs the reader now, grouped by what implies a different ACTION rather than by a category already in a column), **the register's own craft** (a column header treated as CHROME a step lighter than row metadata, sentence case in the table header too, and a supporting line one RUNG BELOW the line it supports rather than a fixed size — plus the half that rule was missing: every SUBJECT on one row starts at the same rung, the subject leads by WEIGHT and its identity mark rather than by size, and whether the supporting line drops a rung is decided by what else separates the pair), **identity marks** (a mark that looks the same on every row carries nothing — `Avatar` derives its hue from the name and takes circle/square for person vs organization; the palette is ordered by contrast, not spectrum, and never paints a person red or amber), **where the accent goes** ("which of these" — a selected row, an active filter; never DATA and never the focus ring, while a page-scale position marker like an active TAB takes `primary`, the ink the kit already spends on "this is on"; and selection must not share a wash with hover), **theming is an app-level ESCAPE HATCH, not a product surface** (`applyLoticsTheme({ … })` once at boot, before `mount`, writing the `--lotics-*` roles on the document root; nothing in the platform writes a theme, and `apps.theme.color` is the launcher icon only — the five `var()` roles plus the three font rungs ARE the whole mechanism, and wrapping a component cannot replace them because RN-Web writes `fontFamily` per element and background/border do not inherit), color discipline, **typography** (the ramp is DATA in `type_ramp`, one table both platforms derive from; leading and tracking are baked per rung and a hand-set `letterSpacing` is always a second copy of the curve), whitespace, and how to TEST an overlay component (a `Popover`-backed surface never mounts under jsdom). |
|
|
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 (machine → a gate: a diff when something is being replaced, a full editable preview when records are being created from a document; human-typed → save-direct + the `ResultHeader` receipt), findings, provenance, confidence; **after the run** — a stored record that fills up from several writers (a person, a chat agent, an extraction, an automation) and remembers none of them: an unwritten value must not render like a written one, a machine's prose and a person's must not share a treatment, model markdown goes in `variant="embedded"`, and the read path must project every field the write paths set; the UI half of the SDK's [ai doc](../app-sdk/docs/ai.md), the whole run in a dialog (`AgentRunScope`/`AgentRunPane`/`AgentRunActions` — a parked question REPLACES the feed, actions in the footer, **Stop** while streaming), **stopping** (`cancel` stops the run, `abort` only stops listening — so closing a dialog must `cancel` or it keeps billing); **review surfaces compose from atoms** — `DiffValue` (a changed value, droppable in any cell/row/total), `DiffMark` (what happened to the row — ONE circular disc, every surface, its kind derived from before/after rather than a boolean "did this change" — an empty `before` is `added`, never `changed`), `useChangeSet` (accept/reject/undo bookkeeping, no layout) — see [MIGRATION.md](./MIGRATION.md) for the `ChangeReview` family they replace |
|
|
20
|
+
| [docs/composition.md](./docs/composition.md) | The design-language contract — **the form comes before the treatment** (name what the subject IS — register, rail, board, timeline, deviation queue — before reaching for parts; a subject flattened into rows is well-typed and says nothing, and reuse-first is about not duplicating primitives, not about a screen's shape), canvas + content column, heading altitude (incl. eyebrow vs group lead — a label is one or the other, and a section heading's own row never wraps so a control label that repeats the title can squeeze the title+description into a narrow ribbon at phone width), banded cards, register vs inset rows (incl. the register laws a row centres its cells by: every cell a FIXED height, a pressable cell on the shared hover token, a column sized by what it carries), the button ladder and **what underlined text may mean** (it GOES somewhere or REVEALS something — never mutates; blue leaves the surface, muted stays on it, and the one in-prose disclosure exception is scoped there), master-detail `Drawer` on a LIST screen vs a child collection's row EXPANDING inside a record, view controls, RECORD EXTENT (one page, sections scrolled to and never routed to), **the register's rhythm** (no hairline between rows — their own height and the hover wash separate them; the register's one line is the band capping the columns, because that is the break whitespace cannot state. Density is `TableRow`'s `minHeight`, never a mode), **a register that TRIAGES** (`TableGroup` — titled bands ordered by what needs the reader now, grouped by what implies a different ACTION rather than by a category already in a column), **the register's own craft** (a column header treated as CHROME a step lighter than row metadata, sentence case in the table header too, and a supporting line one RUNG BELOW the line it supports rather than a fixed size — plus the half that rule was missing: every SUBJECT on one row starts at the same rung, the subject leads by WEIGHT and its identity mark rather than by size, and whether the supporting line drops a rung is decided by what else separates the pair), **identity marks** (a mark that looks the same on every row carries nothing — `Avatar` derives its hue from the name and takes circle/square for person vs organization; the palette is ordered by contrast, not spectrum, and never paints a person red or amber), **where the accent goes** ("which of these" — a selected row, an active filter; never DATA and never the focus ring, while a page-scale position marker like an active TAB takes `primary`, the ink the kit already spends on "this is on"; and selection must not share a wash with hover), **theming is an app-level ESCAPE HATCH, not a product surface** (`applyLoticsTheme({ … })` once at boot, before `mount`, writing the `--lotics-*` roles on the document root; nothing in the platform writes a theme, and `apps.theme.color` is the launcher icon only — the five `var()` roles plus the three font rungs ARE the whole mechanism, and wrapping a component cannot replace them because RN-Web writes `fontFamily` per element and background/border do not inherit), color discipline, **typography** (the ramp is DATA in `type_ramp`, one table both platforms derive from; leading and tracking are baked per rung and a hand-set `letterSpacing` is always a second copy of the curve), whitespace, and how to TEST an overlay component (a `Popover`-backed surface never mounts under jsdom). |
|
|
21
21
|
| [docs/reviewing.md](./docs/reviewing.md) | **Reviewing a screen you built** — the other docs say what good looks like; this one says how to find out whether you achieved it, because "it looks fine" is the same claim as "it should work" made with the same evidence. Measure, never eyeball: render it, extract computed values, let the table show the defect. Two GATES that outrank every treatment question (**SUBTRACT** — what question does this element answer, and count FACTS not elements; **RIGHT CONTROL** — name the species before treating it), then 10 probes each naming what to COLLECT and the SIGNATURE in the numbers: type inventory (range, singletons, the label/value shrink war), ground+border+radius across siblings, what each RULE separates, gap RATIOS, the state diff (paint may change, geometry may not), alignment drift (incl. a centred child that moves while its container measures identical), the surface walk (open it, press it, read the settled string), the DATA probe (what is really in the table — incl. grepping rendered text for internal ids), the ABSENCE pass that catches "bland", and microcopy. Plus the extraction snippets, what measurement cannot see, and how to act on a finding (fix the CLASS, and never resolve one with "the docs say X"). |
|
|
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. |
|
|
@@ -42,6 +42,16 @@ CURRENT major only — upgrading an app across majors is `MIGRATION.md`.
|
|
|
42
42
|
panel a few hundred pixels wide lays its contents out for the whole display. (A component that
|
|
43
43
|
needs a number rather than a bucket — how many columns fit — measures its own box; `Table`
|
|
44
44
|
does exactly that.) → [catalog.md §Utility hooks](./docs/catalog.md).
|
|
45
|
+
- **No user-facing string is a literal in `src/` when a locale slice already names it.** Every
|
|
46
|
+
string a component renders resolves prop → `useLoticsLocale()` slice → the pack's English
|
|
47
|
+
default. Write it as an allowlist, not a hunt: a literal typechecks, renders, and passes a
|
|
48
|
+
render test in the language it happens to be written in — the kit shipped a Vietnamese ⓘ label
|
|
49
|
+
to every tenant that way, invisible to a whole suite of vi-pack assertions. Assert a wired
|
|
50
|
+
string under the OTHER language. → [composition.md §Microcopy](./docs/composition.md).
|
|
51
|
+
- **Annotate every droppable table column, or none.** A partial `priority` is the trap: an
|
|
52
|
+
unannotated column sorts above the whole hand-written range, so one annotation makes every
|
|
53
|
+
other column drop first, and one unannotated column among annotated ones goes first however
|
|
54
|
+
important it is. → [composition.md §The register's own craft](./docs/composition.md).
|
|
45
55
|
- **A section's ADD rides its heading row, right edge** — Add files, Add fee, New line: a
|
|
46
56
|
`primary` `Button` beside `SectionHeadingTitle`, rendered empty or full. Under the rows it
|
|
47
57
|
extends, the verb MOVES with the row count and vanishes off-screen on a long list; a heading is
|
|
@@ -62,7 +72,9 @@ CURRENT major only — upgrading an app across majors is `MIGRATION.md`.
|
|
|
62
72
|
a press it refuses. Rows tick ONE at a time; derive position from the highest stamp and one click marks
|
|
63
73
|
everything below it without inventing dates. A checklist REPORTS — a field owned by a section is
|
|
64
74
|
named as a gap with a `ChecklistNote action` that jumps there, never edited or previewed in the
|
|
65
|
-
row.
|
|
75
|
+
row. A per-ROW third axis, `ChecklistItem.verdict` (`"pass"`/`"fail"`), is a DECIDED outcome — a
|
|
76
|
+
QC gate, a reconciliation match — never toggled; see `docs/catalog.md` for why it withholds
|
|
77
|
+
`onToggle`. Worked in `examples/tpl_record.tsx` § Progress. See `docs/catalog.md`.
|
|
66
78
|
- **`Badge` = STATUS only; supporting detail is the muted second line.** A type / category /
|
|
67
79
|
attribute / count is not a status — it belongs under its identity as `size="xs" color="muted"`,
|
|
68
80
|
never a second chip. A chip beside a name reads as its PEER (a colored one reads louder),
|
package/MIGRATION.md
CHANGED
|
@@ -4,6 +4,112 @@ 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
|
+
## 44.8.0 — a value column gets its own floor, and eight defaults stop fighting their own docs
|
|
8
|
+
|
|
9
|
+
No API is removed. Three of these change what an existing screen renders, so read them even
|
|
10
|
+
though nothing will fail to compile.
|
|
11
|
+
|
|
12
|
+
**`StepProgress`'s built-in caption is locale-wired, and `LoticsLocale` gains a `stepProgress`
|
|
13
|
+
slice.** A hand-built pack must supply it (both shipped packs already do):
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
stepProgress: {
|
|
17
|
+
complete: (total) => `Hoàn tất (${total}/${total})`,
|
|
18
|
+
stage: (name, index, total) => `${name} (${index}/${total})`,
|
|
19
|
+
none: (total) => `0/${total}`,
|
|
20
|
+
position: (index, total) => `${index} trên ${total}`,
|
|
21
|
+
},
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
The caption over named stages was built as `Complete (7/7)` / `0/7` in the component, and the
|
|
25
|
+
screen-reader fallback as `4 of 7` — hardcoded English inside a component the kit requires to
|
|
26
|
+
resolve prop → pack → English default. A Vietnamese board read `Complete (7/7)` in its own
|
|
27
|
+
register. Same defect class as the `fileRow`/`sources`/`counter` leak below, and it hid the same
|
|
28
|
+
way: every state renders correctly under `en`, because English is what the bug produces.
|
|
29
|
+
|
|
30
|
+
Each entry is a FUNCTION rather than a word to concatenate, for the reason the earlier entry
|
|
31
|
+
gives — where the number sits in the sentence is a property of the language, and `position`
|
|
32
|
+
is `4 of 7` against `4 trên 7`. Passing `label` still overrides the caption entirely; nothing
|
|
33
|
+
that already passed one changes.
|
|
34
|
+
|
|
35
|
+
**`Finding` stacks its `readings` one per line and drops the ` · ` between them.** No prop
|
|
36
|
+
changes; a two-reading finding is now two lines instead of one.
|
|
37
|
+
|
|
38
|
+
The separator failed twice over. `docs/reviewing.md` bans ` · ` outright — it claims a
|
|
39
|
+
relationship while refusing to name it, and it is the loudest templated-metadata tell there is —
|
|
40
|
+
so an app reaching for the kit's own ranked-insight component failed the kit's own microcopy
|
|
41
|
+
probe with nothing it could do about that. And a middot is the one mark a screen reader drops,
|
|
42
|
+
so the character carrying "these two disagree" never survived being read aloud: exactly the
|
|
43
|
+
defect `CommentsButton` was fixed for. It wrapped badly too — inline + `flexWrap` could start a
|
|
44
|
+
phone-width line WITH the separator, or split a source from its value across lines.
|
|
45
|
+
|
|
46
|
+
Stacked, each `source value` pair is a row at every width and needs no separator to relate them.
|
|
47
|
+
Pass `readings` shorter values, or move a long one to `detail`, if the extra line costs you
|
|
48
|
+
somewhere tight.
|
|
49
|
+
|
|
50
|
+
**A `SequenceItem` with no handlers renders no control column.** Reorder/remove controls still
|
|
51
|
+
render disabled where they cannot act — first item, last item, a row that can be removed but not
|
|
52
|
+
moved — because that is what holds the right edge steady between items that are editable. What
|
|
53
|
+
changed is the item that declares NO editing at all: it used to get three permanently dead
|
|
54
|
+
buttons, which is a dead affordance by `reviewing.md`'s own gates, and a read-only sequence (legs
|
|
55
|
+
already run, a route as recorded) is a common shape rather than an edge case. A list where some
|
|
56
|
+
rows are fully locked and others are editable will now step in the right margin; give the locked
|
|
57
|
+
rows a handler if that matters more than the dead controls did.
|
|
58
|
+
|
|
59
|
+
**`DateStamp` takes `compact`.** Additive, default `false`. It forwards the option `formatDate`
|
|
60
|
+
already had, dropping the year (`08/09`) so the stamp can agree with a neighbouring column the
|
|
61
|
+
caller formats itself. The format stays in `formatDate` — this is a passthrough, not a second
|
|
62
|
+
implementation.
|
|
63
|
+
|
|
64
|
+
**A `DetailTable` stacks earlier, because a value column's floor is no longer a control's.**
|
|
65
|
+
`minValueWidth` defaulted to `MIN_CONTROL_WIDTH` (160) — the width below which a CONTROL stops
|
|
66
|
+
being operable, which is a different question from the width below which a VALUE stops being
|
|
67
|
+
readable. A value column holds company names and addresses. At 150/160 the stack threshold was
|
|
68
|
+
334, so a 375px screen kept two columns and handed the value ~170px: a name needing 221 got 104.
|
|
69
|
+
The new `MIN_VALUE_WIDTH` (200) is the default; pass `minValueWidth` explicitly to pin the old
|
|
70
|
+
behaviour on a table whose values really are short.
|
|
71
|
+
|
|
72
|
+
**An unannotated table column's drop priority is `columns.length + index`, not `index`.** An
|
|
73
|
+
unannotated register still sheds right-to-left — that is unchanged and covered by a test — but a
|
|
74
|
+
MIXED table now behaves the way its author meant. Annotating your most important column
|
|
75
|
+
`priority: 1` used to TIE it with the unannotated column at index 1, and the right-to-left
|
|
76
|
+
tie-break then dropped yours first: the amount column, the fact the register exists for,
|
|
77
|
+
disappearing at narrow widths on a screen that reviewed as correctly annotated. **Annotate every
|
|
78
|
+
droppable column or none** — a partial annotation is what the old default punished.
|
|
79
|
+
|
|
80
|
+
**A selected `CardSelectItem` carries a GROUND**, not the same ring as hover. It was
|
|
81
|
+
`selected || hovered || pressed || focusVisible` → one style, so the card you had CHOSEN was
|
|
82
|
+
pixel-identical to the card under the pointer. Selection is now `accent_wash` + an accent edge,
|
|
83
|
+
the same "you are here" a selected table row and an active chip carry. This is the fix 43.5.0
|
|
84
|
+
made for `Table`'s selected row, one release late.
|
|
85
|
+
|
|
86
|
+
**`DataGrid`'s flexible column has a floor** (`FLEX_MIN_WIDTH`, 120) instead of `minWidth: 0`.
|
|
87
|
+
This grid sheds no columns by design — every column is a measure you came to compare — so the
|
|
88
|
+
fixed columns held their widths and the flexible identity column absorbed the whole squeeze,
|
|
89
|
+
all the way to zero: a narrow container rendered rows of unlabelled money, silently. Past the
|
|
90
|
+
floor the grid now overflows, and a narrow container needs to scroll it.
|
|
91
|
+
|
|
92
|
+
**`AgentRunLike.error` is OPTIONAL.** It was declared required while its own docblock promised
|
|
93
|
+
it accepted an SDK run's optional `error` — and an optional property is not assignable to a
|
|
94
|
+
required one however wide the required one's type, so the three-line usage printed in
|
|
95
|
+
`ai_patterns.md` did not compile. **If you worked around it** with a `useMemo` reshape or a cast
|
|
96
|
+
(`{ ...run, error: run.error ?? null }`), delete that: hand the SDK's run straight over.
|
|
97
|
+
|
|
98
|
+
**`Select` takes the `D` data generic** its siblings already had, so `optionPicker(...)` spreads
|
|
99
|
+
into it without a cast. Additive.
|
|
100
|
+
|
|
101
|
+
Also additive, nothing to change: `MIN_VALUE_WIDTH` and `FLEX_MIN_WIDTH` are exported (the
|
|
102
|
+
latter via a new `./table_fit` entry point, which also exports `ROW_HEIGHT` and `COLUMN_GAP` so
|
|
103
|
+
a skeleton can be built from the register's own constants); `formatMoney` takes
|
|
104
|
+
`maxFractionDigits` for a genuine per-unit price (`Intl`'s currency style floors at the
|
|
105
|
+
currency's minor unit, so an LCL rate of 0,019 US$/kg printed as `0,02`); `InlineSelect`
|
|
106
|
+
forwards `getOptionDescription` to the two-line option row `OptionList` already renders;
|
|
107
|
+
`NumberInput` takes a `placeholder`; `EmptyState` and `ErrorState` centre their TEXT and not
|
|
108
|
+
only their box (they swap into one another as a read settles, so only one of the two centred
|
|
109
|
+
would have shown up as the copy jumping alignment at the moment a region fails); a
|
|
110
|
+
heading row WRAPS rather than crushing its title; `Accordion`'s meta yields before its title;
|
|
111
|
+
`CardHeaderTitle` reads the ⓘ's name from the locale pack; and `ReferenceField`'s peek footer
|
|
112
|
+
gives its clear the same resting surface as the verb beside it.
|
|
7
113
|
## 44.5.0 — `Pagination` drops the page index
|
|
8
114
|
|
|
9
115
|
The pager rendered a range AND a page index — `16–30 of 45` beside `Page 2 of 3`. Both answer
|
|
@@ -94,11 +200,10 @@ No API is removed and nothing throws, but four DEFAULTS moved, so every register
|
|
|
94
200
|
in your app renders differently on upgrade. Each is reversible at the call site if you need the
|
|
95
201
|
old look while you catch up.
|
|
96
202
|
|
|
97
|
-
**Table column headers are sentence case, not all-caps
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
`muted
|
|
101
|
-
case alone leaves it reading as a stray label rather than as the band naming the columns.
|
|
203
|
+
**Table column headers are sentence case, not all-caps.** `Table` and `SortHeader` were
|
|
204
|
+
uppercasing every column label, which the heading ramp bans and names the #1 templated tell. What
|
|
205
|
+
keeps the band from reading as a stray label once the case is gone is WEIGHT: the header is
|
|
206
|
+
`sm muted medium`, and a row's supporting line is `sm muted` regular, so the two never collide.
|
|
102
207
|
|
|
103
208
|
**Row separators are lighter than the header rule.** The band capping a table keeps
|
|
104
209
|
`colors.border`; the hairlines between rows are `zinc-100`. Every rule used to be `border`, which
|
package/docs/ai_patterns.md
CHANGED
|
@@ -318,6 +318,16 @@ shape its root draws, and every surface needing a diff somewhere else rebuilds i
|
|
|
318
318
|
operator opening a job of twelve fee lines to see where a dropped invoice landed should find
|
|
319
319
|
the one filled shape in a column of blanks without reading anything or separating two hues.
|
|
320
320
|
The hole keeps the column's width and announces nothing.
|
|
321
|
+
|
|
322
|
+
**Derive the kind from the same before/after the row's `DiffValue` carries — never from a
|
|
323
|
+
boolean "did this differ".** A field with no prior value (`before` empty or absent) that a run
|
|
324
|
+
now proposes a value for is an ADD, not a CHANGE, and a local review row that only asks
|
|
325
|
+
"does `draft !== before`" answers yes for both and always renders `changed` — the amber pencil
|
|
326
|
+
on a value that was never there to begin with, which reads as the AI having second-guessed a
|
|
327
|
+
figure it actually just filled in for the first time. This bites hardest on the record that
|
|
328
|
+
looks most ordinary: the first proposal any fresh record ever gets has an empty `before` on
|
|
329
|
+
every field, so every mark on that one run is wrong until the kind is derived per field
|
|
330
|
+
(`before === after` → no mark; `before === ""` → `added`; otherwise → `changed`).
|
|
321
331
|
- **`useChangeSet(ids, { initial })`** (`@lotics/ui/use_change_set`) — the bookkeeping:
|
|
322
332
|
`status`/`accept`/`reject`/`undo`/`acceptAll`/`reset`, `accepted`/`rejected`/`pending`,
|
|
323
333
|
`keptCount`/`total`/`settled`. `initial` defaults to **`accepted`** so an operator drops
|
|
@@ -387,6 +397,16 @@ shape its root draws, and every surface needing a diff somewhere else rebuilds i
|
|
|
387
397
|
only reject costs the whole re-entry.
|
|
388
398
|
- **Gate the commit on what will actually be written**, not on the proposal count — disable at
|
|
389
399
|
zero kept.
|
|
400
|
+
- **A decided row's Keep/Drop must be reachable from BOTH directions when `initial: "accepted"`.**
|
|
401
|
+
`undo` returns a row to its DEFAULT, not to `"pending"` — so on the common default
|
|
402
|
+
(`initial: "accepted"`, the operator drops the exceptions rather than re-approving every
|
|
403
|
+
identical field) a row starts already decided and Undo is a no-op with nothing to reach:
|
|
404
|
+
there is no path back to a `pending`-style Keep/Drop pair, and the row is stuck accepted
|
|
405
|
+
forever. The collapsed-to-Undo anatomy (value + one "Undo" button, swapping to Keep/Drop only
|
|
406
|
+
while `pending`) is correct ONLY under `initial: "pending"`, where every row genuinely starts
|
|
407
|
+
undecided. Under `initial: "accepted"`, render Keep/Drop as an always-visible TOGGLE instead —
|
|
408
|
+
both buttons present, the one matching the row's current status disabled, the other live —
|
|
409
|
+
so either direction is one press away regardless of where the row started.
|
|
390
410
|
- **The mark rides the LABEL, and the value sits in an `InlineSlot`.** Both are alignment
|
|
391
411
|
before they are semantics. A mark beside the VALUE indents every row it marks by its own
|
|
392
412
|
width plus a gap, so an unmarked row needs a spacer of exactly the glyph's width to keep up —
|
package/docs/catalog.md
CHANGED
|
@@ -179,6 +179,17 @@ axes carry every shape it takes, and BOTH are decisions about meaning, not looks
|
|
|
179
179
|
|
|
180
180
|
A record's Progress and a desk's to-do list are the same component two booleans apart.
|
|
181
181
|
|
|
182
|
+
**A row can carry a DECIDED outcome instead of a tick — `ChecklistItem.verdict` (`"pass"` /
|
|
183
|
+
`"fail"`).** This is a per-ROW axis, not a list-level one: a QC gate, a reconciliation match,
|
|
184
|
+
any row whose mark means an inspection SETTLED rather than work getting done. `fail` renders in
|
|
185
|
+
red with an X (`StepStatus`'s `fail`, shared with `Stepper`/`AgentRun` via the same `Marker`) —
|
|
186
|
+
distinct from `warning`, which means "still open, needs a look," where `fail` means the answer is
|
|
187
|
+
already no. A verdict is reported, not toggled: passing `onToggle` alongside `verdict` is a
|
|
188
|
+
no-op (withheld internally), because neither of the toggle-capable marks (`CheckboxInput`,
|
|
189
|
+
`CheckCircle`) has a fail-shaped state — a caller with an inspector who taps to CONFIRM a
|
|
190
|
+
pass/fail should drive that from its own control, then set `verdict` from the result, never
|
|
191
|
+
reach for `done`+`onToggle` on the same row a verdict already settled.
|
|
192
|
+
|
|
182
193
|
**A row carries its TITLE and its own stamp, and nothing else.** A checklist REPORTS: it does not
|
|
183
194
|
collect, and it does not PREVIEW. A field some section owns — an assignee, an address, a portal
|
|
184
195
|
login — is NAMED as a gap by a `ChecklistNote` whose `action` jumps to that section, never edited
|
|
@@ -222,6 +233,13 @@ Two columnar shapes, and the choice is about data size:
|
|
|
222
233
|
heading, so digits line up under their label; **`DataGridColumn.footer` gives that column its
|
|
223
234
|
cell in the group's footer row** (a subtotal, a resolved measure) — the grid lays it out in
|
|
224
235
|
the column's own box, so a total can never drift from what it sums and no consumer rebuilds
|
|
236
|
+
**`DataGrid` SHEDS NOTHING at narrow widths, and that is deliberate** — every column is a
|
|
237
|
+
measure you came to compare, unlike `Table`'s register of objects where the row is an index
|
|
238
|
+
and the record holds the rest. So it has no `priority` and no stacked mode: fixed columns keep
|
|
239
|
+
their widths, the flexible column holds a `FLEX_MIN_WIDTH` floor, and past that the grid
|
|
240
|
+
OVERFLOWS. A narrow container must scroll it horizontally; the floor exists because without
|
|
241
|
+
one the flexible identity column absorbed the entire squeeze down to zero and the grid
|
|
242
|
+
rendered rows of unlabelled numbers, silently. If you want columns to drop, you want `Table`.
|
|
225
243
|
the row. `renderGroupFooter` remains for content that does NOT sit in a column (a per-group
|
|
226
244
|
add row). The grid takes exactly its container's width — no outdent to fight, so it needs no
|
|
227
245
|
scroller unless YOU add one. It renders ALL rows (no
|
|
@@ -376,7 +394,9 @@ your screen. See the AI-patterns doc indexed in [AGENTS.md](../AGENTS.md) for th
|
|
|
376
394
|
`ChoiceList` options), `Sources` (provenance chips for AI output — at review scale,
|
|
377
395
|
`label={null}` slots the chips at a section's bottom), `Finding` (one ranked insight from an
|
|
378
396
|
AI check, laid out HORIZONTALLY: a severity dot, the title, and the `delta` right-aligned on
|
|
379
|
-
that same line, over
|
|
397
|
+
that same line, over the `readings` stacked ONE PER LINE — each `source value`, no separator
|
|
398
|
+
joining them (a middot claims a relation it will not name, and is the one mark a screen reader
|
|
399
|
+
drops), both kept on
|
|
380
400
|
screen with NEITHER marked as the wrong one, since a finding reports that two sources disagree
|
|
381
401
|
and which is right is the reader's call. `detail` is the optional consequence. A reader scanning
|
|
382
402
|
a stack gets severity down the left edge and magnitude down the right. The readings ARE the
|
|
@@ -542,6 +562,11 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
542
562
|
one interaction it signals; the padding still comes off the 8-grid, the ring is only
|
|
543
563
|
the floor), `HOVER_BORDER`, `CONTROL_TRANSITION`, `chipSurfaceStyle` — the shared
|
|
544
564
|
control-surface tokens.
|
|
565
|
+
- **`table_fit`** — the register's shared geometry, importable so a skeleton or a
|
|
566
|
+
hand-built band derives from the same numbers `Table` lays out with: `ROW_HEIGHT` (72),
|
|
567
|
+
`COLUMN_GAP`, `ROW_GUTTER`, `FLEX_MIN_WIDTH` (120 — what a flexible column needs to stay
|
|
568
|
+
usable), plus `computeTableFit` itself. `MIN_VALUE_WIDTH` (200 — a record VALUE column's
|
|
569
|
+
floor, deliberately above a control's `MIN_CONTROL_WIDTH`) lives in `control_surface`.
|
|
545
570
|
- **`fonts.css`** — the Inter sheet (400/500/600, served by absolute URL so it resolves on
|
|
546
571
|
every origin an app runs from); the app entry imports it ONCE or every `Text` falls back
|
|
547
572
|
to system fonts.
|
|
@@ -1150,7 +1175,9 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
1150
1175
|
`Destination`) cannot hold a second middle entry and cannot say the entries are ordered.
|
|
1151
1176
|
`SequenceItem` takes the content, an optional `role` (DERIVE it from index — a stored role lies
|
|
1152
1177
|
the moment the list is reordered) and `onMoveUp`/`onMoveDown`/`onRemove`; the controls render
|
|
1153
|
-
even where they cannot act so the right edge never shifts between items
|
|
1178
|
+
even where they cannot act so the right edge never shifts between items — but an item passing
|
|
1179
|
+
NO handler at all renders no control column, so a read-only sequence (legs already run, a route
|
|
1180
|
+
as recorded) is a first-class shape rather than three dead buttons per row. Reorder is BUTTONS,
|
|
1154
1181
|
not a drag handle — dragging is invisible to the keyboard and fiddly on a phone, and a
|
|
1155
1182
|
three-to-six position list does not need it. Indent anything that follows the list (an "Add"
|
|
1156
1183
|
link) by `SEQUENCE_INSET` so it lands on the rail's column. Distinct from `Timeline` (an
|
|
@@ -1182,8 +1209,9 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
1182
1209
|
before acting (amber, announced); **`error`** = field-level failure under the value, danger +
|
|
1183
1210
|
alert semantics (the `Inline*` editors already render their own transient save errors — don't
|
|
1184
1211
|
wire both); invalid/cross-field STATE = a co-located `Callout`; **`flat`**
|
|
1185
|
-
on a
|
|
1186
|
-
|
|
1212
|
+
on a value sitting OFF the inline-control grid (a raw node — NOT `InlineStatic` / `InlineSlot`,
|
|
1213
|
+
which are on it) aligns the annotation to it on both axes: drops the control text inset and
|
|
1214
|
+
tucks up by the band's slack.
|
|
1187
1215
|
The three annotations are ONE anatomy shared with a checklist row's `ChecklistNote` — same
|
|
1188
1216
|
order, same alert semantics — because a record's field and a task's field are the same thing
|
|
1189
1217
|
on two surfaces.
|
|
@@ -1249,6 +1277,30 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
1249
1277
|
trust by summing exactly what its label claims. `LedgerBasis` is demoed on the gallery's
|
|
1250
1278
|
Charts page instead — a delivery order has no base to compute from, and inventing one to
|
|
1251
1279
|
place a component is how invented needs start.
|
|
1280
|
+
- **`running_ledger`** — `RunningLedger`: a CHRONOLOGICAL, self-totalling ledger — a stock
|
|
1281
|
+
kardex, a statement of account — where every row carries the balance AS OF that row. Not
|
|
1282
|
+
`Ledger`: that compound closes a fixed set of charge/receipt groups into one total and has no
|
|
1283
|
+
chronological concept at all. `opening` (label + optional date + the anchor balance) then
|
|
1284
|
+
`rows` (date, label, a SIGNED `amount` — positive in/debit, negative out/credit, one field
|
|
1285
|
+
rather than paired columns so the same shape serves a stock quantity and a money statement),
|
|
1286
|
+
each rendered with its running balance **computed by the component**, never accepted as a
|
|
1287
|
+
caller-supplied number — a running balance is exactly the figure that must equal what it
|
|
1288
|
+
accumulates, and a caller-supplied one is the one kind of drift no reader can catch by looking.
|
|
1289
|
+
`format` is ONE formatter for the whole ledger (`formatMoney`, or a quantity formatter with a
|
|
1290
|
+
unit suffix) — every row shares one unit, which is what lets the column state it once.
|
|
1291
|
+
`criticalAtOrBelow` reads a row's balance in danger tone once it reaches a caller-declared
|
|
1292
|
+
floor (a stock-out, a negative balance) — **the ONLY tone this component owns**, evaluated
|
|
1293
|
+
independently on EVERY balance in the column: each row, the opening anchor (so an already-critical
|
|
1294
|
+
opening reads danger even with zero rows following it), and the closing line. A row's `amount`
|
|
1295
|
+
is never auto-toned by its arithmetic sign, because an OUT movement is routine business as often
|
|
1296
|
+
as an IN one and colouring every negative red claims a meaning the number alone does not carry. Closes with a `Divider`-set current-balance line at `Ledger`'s own
|
|
1297
|
+
`LedgerTotal` weight, `closingLabel` overriding the locale default ("Current balance") for a
|
|
1298
|
+
domain the pack cannot own generically ("Tồn kho hiện tại"). No `examples/` worked example yet
|
|
1299
|
+
— a stock kardex or an AR statement of account is a genuinely different shape from every
|
|
1300
|
+
existing template (the 3-statement accrual grammar in `tpl_statements`, the fleet-register
|
|
1301
|
+
drawer in `tpl_stock`), so forcing it into either would be a mismatch rather than a worked
|
|
1302
|
+
example; proven against real, live deployed consumers instead (a warehouse and a receivables
|
|
1303
|
+
app) until a template of its own earns its place.
|
|
1252
1304
|
- **`reference_field`** — `ReferenceField`: a reference to ANOTHER RECORD, rendered as a
|
|
1253
1305
|
FIELD VALUE — the kit's inline-editor surface (so a pointer sits in the value column
|
|
1254
1306
|
like the editors above and below it), whose press opens a PEEK of that record's facts.
|
|
@@ -1257,7 +1309,8 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
1257
1309
|
panel narrower than the control that opened it reads as belonging to something else. So the
|
|
1258
1310
|
field truncates its name to one line and the peek's header WRAPS the whole of it: the column
|
|
1259
1311
|
stays scannable, the full value is one press away. Between the floor and `labelWidth +
|
|
1260
|
-
|
|
1312
|
+
MIN_VALUE_WIDTH + 24` a `DetailTable` stacks, which is the right grammar at that width — the
|
|
1313
|
+
VALUE floor (200), not the control floor, because that column holds names as well as editors.
|
|
1261
1314
|
The verbs live INSIDE the
|
|
1262
1315
|
peek, never on the field, and they are ALL `Button`s at one altitude — a peek is
|
|
1263
1316
|
dialog-scale and a dialog's verbs are buttons, so mixing weights only made the reader rank
|
|
@@ -1311,6 +1364,22 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
1311
1364
|
reference is a FIELD, not a section — one pointer to one other row never earns a heading
|
|
1312
1365
|
and a rail entry. Worked example: [`tpl_record`](../examples/tpl_record.tsx) § General
|
|
1313
1366
|
(customer) and its Transport parties.
|
|
1367
|
+
- **`related_record_row`** — `RelatedRecordRow`: a pointer to a record in a DIFFERENT app's
|
|
1368
|
+
table — never `ReferenceField`, which peeks an in-WORKSPACE link this app's own table holds
|
|
1369
|
+
(a facts draft, Change/Clear). A suite of apps that deploys one bundle per department has no
|
|
1370
|
+
in-app route to a sibling's record and no local fields to peek, so this only ever reports:
|
|
1371
|
+
`kind` (an `Eyebrow` naming what the reference IS — "Lệnh sản xuất"), `code` (the record's own
|
|
1372
|
+
human id), an optional `status` (pass a `Badge`), and `onOpen` — the HOST's job, typically
|
|
1373
|
+
`openExternal(url)` from `@lotics/app-sdk` into the sibling app's own deployed URL, since only
|
|
1374
|
+
the calling app's author knows that app's subdomain and its route shape for a record page.
|
|
1375
|
+
Omitting `onOpen` renders a plain, non-interactive reference — never a press target wired to
|
|
1376
|
+
nothing, which is worse than no affordance. Composes as a `DetailRow`'s value for a single
|
|
1377
|
+
relationship, or repeats plainly (no per-row label) in a list for several — the surrounding
|
|
1378
|
+
label or heading is the caller's, this renders one reference. No `examples/` worked example
|
|
1379
|
+
yet, for the same reason as `running_ledger` above: what this component is FOR — a reference
|
|
1380
|
+
into a genuinely separate deployed app — has no natural home inside any single-app template in
|
|
1381
|
+
this repo without inventing a fictional sibling app to point at; proven against real, live
|
|
1382
|
+
deployed consumers instead.
|
|
1314
1383
|
- **`peek`** — `Peek`: drill-down for a reference — press a name/id where it appears and get its
|
|
1315
1384
|
details in an anchored popover, without leaving the screen; keep the content a summary with ONE
|
|
1316
1385
|
action to the full record. **`variant` picks the register the TRIGGER sits in, and getting it
|
|
@@ -1350,6 +1419,13 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
1350
1419
|
On pressable rows dropped values stay one tap away — the row opens the record; a READ-ONLY
|
|
1351
1420
|
register (rows without `onPress`) has no door, so give the columns it can't afford to lose
|
|
1352
1421
|
a low `priority` (e.g. `priority: 1` — outlives its right-side neighbours).
|
|
1422
|
+
**Set `priority` from what the page already told the reader matters, never leave it at the
|
|
1423
|
+
default.** A `SummaryLine`/`KPIStrip` figure above the register is a claim that ONE column is
|
|
1424
|
+
the reason to scan this page; left unset, `priority` defaults to column order and sheds
|
|
1425
|
+
right-to-left, so that exact column is the first thing table_fit drops the moment the
|
|
1426
|
+
container narrows — the banner keeps promising a number the row beneath it can no longer
|
|
1427
|
+
show. The fix is one line per column, not a rewrite: rank by what the summary already flagged
|
|
1428
|
+
as the point, not by where the column happens to sit in the header row.
|
|
1353
1429
|
**`detail` + `expanded` reveal a row's detail BENEATH it instead of navigating away.** A
|
|
1354
1430
|
register answers "which one"; the moment it cannot answer "and what about it" the reader is
|
|
1355
1431
|
sent to a drawer, loses the surrounding rows, and pays a navigation to come back — which is
|
|
@@ -1374,6 +1450,9 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
1374
1450
|
position (a group band, a totals line) and the gutter stays blank so the cells beside it stay
|
|
1375
1451
|
in line. A position is NEVER an identity: sort or filter and row 12 is a different record —
|
|
1376
1452
|
a stable handle is a column, and it leads the cells. Worked screen: `tpl_item_list`.
|
|
1453
|
+
- **`tabs`** — a tab may state its band's size with `TabOption.count`, and needs attention with
|
|
1454
|
+
`TabOption.status` (a `ColorName` dot). Pressing the tab IS the filter, so the count is also the
|
|
1455
|
+
reason to press it. → [composition.md](./composition.md) §"A partitioning control states its band's size".
|
|
1377
1456
|
- **`table`** rhythm — rows are separated by their own height and the hover wash, never by a hairline; the register's one line is the band capping the columns. 72px rows, overridable per row with `TableRow`'s `minHeight` when a surface genuinely needs density. There is no `spacing` prop: a table of VALUES rather than of objects is a different component (`Matrix`, `DataGrid`). → [composition.md](./composition.md) §"The register's rhythm".
|
|
1378
1457
|
- **`table` › `TableGroup`** — a titled BAND of rows inside a `Table` (`label`, `count?`, `color?` for a single valence dot). Turns a register that presents records into one that triages them: group by what implies a different ACTION, order the bands by what needs the reader now, and let air rather than a rule separate them. → [composition.md](./composition.md) §"A register that TRIAGES".
|
|
1379
1458
|
- **`sort_header`** — `SortHeader` + `SortState`/`SortDir` + `cycleSort` + `sortBy` +
|
|
@@ -1435,6 +1514,9 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
1435
1514
|
empty — and a ladder of fields is a form with invisible borders. Reach for `InlineDatePicker`
|
|
1436
1515
|
when a person is expected to ENTER the date, and this when the system already knows it.
|
|
1437
1516
|
Empty renders a thin rule, not a placeholder; read-only (omit `onChange`) renders text alone.
|
|
1517
|
+
`compact` drops the year (`08/09`) — the stamp shares a row with dates the caller formats, and
|
|
1518
|
+
the option goes straight through to `formatDate`, which stays the one place deciding what a
|
|
1519
|
+
date looks like.
|
|
1438
1520
|
`onChange` fires on CLOSE, not on each pick — one commit per visit, and a dismissed panel
|
|
1439
1521
|
still writes whatever was picked in it, which is what makes a single tap enough.
|
|
1440
1522
|
Worked screen: `examples/tpl_record.tsx` § Progress.
|
|
@@ -1606,7 +1688,9 @@ component rather than showing it at zero.
|
|
|
1606
1688
|
status mix on one bar).
|
|
1607
1689
|
- **`step_progress`** — `StepProgress`: N-of-M dots/segments progress. A real `progressbar`
|
|
1608
1690
|
(valuenow/min/max) because its segments are decoration over ONE quantity — the opposite
|
|
1609
|
-
case from `stepper`, whose steps carry content a `progressbar` would hide.
|
|
1691
|
+
case from `stepper`, whose steps carry content a `progressbar` would hide. Pass the stage
|
|
1692
|
+
NAMES and it captions itself from the `stepProgress` locale slice ("Hạ bãi (2/4)"); `label`
|
|
1693
|
+
overrides, `captionTone="danger"` is the documented stalled-stage slot.
|
|
1610
1694
|
- **`breakdown`** — `Breakdown`: a stacked bar + ranked share rows, pressable to drill;
|
|
1611
1695
|
`maxRows` folds the long tail behind a localized "Show N more" toggle
|
|
1612
1696
|
(`BreakdownLabels`).
|
|
@@ -1632,7 +1716,13 @@ component rather than showing it at zero.
|
|
|
1632
1716
|
unsorted and out-of-bounds ranges all normalize; no input drops a character.
|
|
1633
1717
|
- **`legend_item`** — `LegendItem`: one swatch + label of a chart legend.
|
|
1634
1718
|
- **`remainder_meter`** — `RemainderMeter`: allocated-vs-remaining meter
|
|
1635
|
-
(`RemainderMeterLabels` localized via the provider).
|
|
1719
|
+
(`RemainderMeterLabels` localized via the provider). **A THREE-way comparison (ordered vs.
|
|
1720
|
+
received vs. invoiced, say) is TWO stacked meters against the same `total`** — one per pairwise
|
|
1721
|
+
relationship (ordered-vs-received, ordered-vs-invoiced) — not a new component: the primitive's
|
|
1722
|
+
whole model is one source distributed one way, and a caller with a second relationship to show
|
|
1723
|
+
reaches for a second instance before reaching for a wider one. Extract a true 3-column strip
|
|
1724
|
+
only once a SECOND real caller needs the exact same 3-way shape (the kit's own
|
|
1725
|
+
extract-on-the-second-hand-roll rule) — one confirmed need is not yet a pattern.
|
|
1636
1726
|
- **`allocation_row`** — `AllocationRow`: one target's row in an allocation surface (pairs
|
|
1637
1727
|
with `RemainderMeter`).
|
|
1638
1728
|
|
|
@@ -2019,7 +2109,9 @@ component rather than showing it at zero.
|
|
|
2019
2109
|
column quietly acquires TWO left edges and two baselines, and `DetailRow`'s label (which pads
|
|
2020
2110
|
down to meet a control) agrees with one kind of row and not the other. `InlineStatic` is the
|
|
2021
2111
|
STRING case and composes it; reach for the slot when the value is a node, which a review
|
|
2022
|
-
surface always is.
|
|
2112
|
+
surface always is. An ANNOTATED row needs the grid even with no editor in sight, because the
|
|
2113
|
+
annotation stack is inset to a control's words — data_entry.md §The editability affordance
|
|
2114
|
+
has the per-table test. NOT for a table cell — a `Table` sets its own row rhythm.
|
|
2023
2115
|
- **`use_change_set`** — **`useChangeSet(ids, { initial })`**: the accept/reject/undo
|
|
2024
2116
|
bookkeeping as a HOOK, zero layout — `status`/`accept`/`reject`/`undo`/`acceptAll`/`reset`,
|
|
2025
2117
|
`accepted`/`rejected`/`pending`, `keptCount`/`total`/`settled`. Decisions are stored as
|