@lotics/ui 27.17.0 → 28.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/AGENTS.md CHANGED
@@ -44,16 +44,19 @@ CURRENT major only — upgrading an app across majors is `MIGRATION.md`.
44
44
  (`message`/`detail`/`onRetry`) on failure → `EmptyState` (succeeded, found nothing) →
45
45
  `CompletionState` on done. An alert glyph inside an empty state claims the read succeeded when
46
46
  nothing is known. → [catalog.md §Status / feedback](./docs/catalog.md).
47
- - **Progress: rows of WORK vs positions of ONE thing.** N items ticked in any order, every row
48
- the same shape → `task` (`TaskList`). ONE record walking ordered stages where the stage decides
49
- which fields, conditions and act are even offered **`pipeline`** (`Pipeline` +
50
- `PipelineStage`, over `stepper`) worked in `examples/tpl_record.tsx` § Progress, which it took
51
- over FROM a per-desk checklist. Rendering positions as a checklist forces every row to carry
52
- every control and never says where the record sits; rendering work items as a pipeline implies
53
- an order that is not there. A ladder is TICKED `PipelineStage.onToggle` makes the marker the
54
- completion control (never a checkbox beside it), and the stamped date rides the title row via
55
- `trailing` as a `DateStamp`: muted text, no frame, click to correct. One ladder per record,
56
- never a handoff run beside a milestone run see `docs/catalog.md`.
47
+ - **Progress: ONE compound, TWO axes and both are about meaning.** A run of markable rows is
48
+ `checklist` (`Checklist` + `ChecklistGroup` + `ChecklistItem`, over `stepper`). **`connected`**
49
+ asks *is this a sequence* on for one record walking positions, off for N items handled in any
50
+ order, where a line claims an order the work does not have. **`mark`** asks *does ticking mean
51
+ DONE or CHOSEN* — a ring for completion, the square box for picking (which forms to produce,
52
+ which records to export). Spend the ring on a choice and a reader who learned that a filled
53
+ ring means "done" reads a ticked pick-list as work already carried out. It replaced separate
54
+ task/pipeline families that had converged on one anatomy. A GROUP is a phase a name, optionally a
55
+ Show/Hide disclosure, and no body: it owns no COMPLETION, and a ring that never responds invites
56
+ a press it refuses. Rows tick ONE at a time; derive position from the highest stamp and one click marks
57
+ everything below it without inventing dates. A checklist REPORTS — a field owned by a section is
58
+ named as a gap with a `ChecklistNote action` that jumps there, never edited or previewed in the
59
+ row. Worked in `examples/tpl_record.tsx` § Progress. See `docs/catalog.md`.
57
60
  - **`Badge` = STATUS only; supporting detail is the muted second line.** A type / category /
58
61
  attribute / count is not a status — it belongs under its identity as `size="xs" color="muted"`,
59
62
  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,70 @@ 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
+ ## 28.0.0 — `task` and `pipeline` merge into `checklist`
8
+
9
+ Two families had converged on one anatomy: a ring, a title, a value beside it, a muted note, a
10
+ detail block, an optional grouping level. What differed was a LINE between the rings — a mode,
11
+ not a family. The proof: a single session added `trailing`/`meta` to `TaskTitle` **and**,
12
+ separately, the same two to `PipelineStep`, then `tone` to `TaskCaption` and `tone`+`action` to
13
+ `PipelineNote`. One feature, implemented twice.
14
+
15
+ `@lotics/ui/task` and `@lotics/ui/pipeline` are **gone**. `@lotics/ui/checklist` replaces both.
16
+
17
+ **`Checklist` and the `checklist` locale slice are REUSED names, not restored components.** A
18
+ different `Checklist` / `ChecklistRow` shipped before v17 and was deleted there in favour of
19
+ `Task`; this one shares only the word and takes none of its props (`trailingWidth`,
20
+ `controlWidth`, `expansion`, `subtasks`). The `checklist` locale slice v17 deleted is likewise
21
+ back under the same key with different strings — `expand` / `collapse`, the group disclosure's
22
+ wording, where the old slice was note and subtask chrome. An app arriving from 16.x follows the
23
+ table below; nothing of its old `Checklist` carries over.
24
+
25
+ | was | now |
26
+ | --- | --- |
27
+ | `<TaskList>` / `<Pipeline>` | `<Checklist connected={…}>` — the line is an AXIS |
28
+ | `<TaskItem>` + `<TaskStatus>` + `<TaskTitle>` | `<ChecklistItem title done current onToggle trailing meta menu>` |
29
+ | `<PipelineStage>` | `<ChecklistGroup title>` (a phase) — or just a `ChecklistItem` (an atomic rung) |
30
+ | `<PipelineStep>` | `<ChecklistItem>` |
31
+ | `<TaskCaption>` / `<PipelineNote>` | `<ChecklistNote tone action>` |
32
+ | `<PipelineActions>` / `<TaskActions>` | `<ChecklistActions>` / `ChecklistItem.menu` |
33
+ | `<PipelineField>` | `<ChecklistField>` |
34
+ | `<TaskDetail>` | a `ChecklistItem`'s own children — free-form content needs no wrapper |
35
+ | nested `<TaskList>` for subtasks | `<ChecklistGroup>` — groups and items are SIBLINGS in one run |
36
+
37
+ **`connected` is the whole difference.** On for a record walking positions (the line asserts
38
+ sequence); off for a set picked in any order, where a line claims an order the work does not
39
+ have.
40
+
41
+ **A group carries no control and no body.** A phase owns no completion of its own, so a ring
42
+ there is a control that never responds — and one identical to the pressable rings beneath it
43
+ invites the press it refuses. A condition hung off a heading also leaves the reader working out
44
+ which row it was about, so a row's wait, warning and act now sit on the row that owes them.
45
+
46
+ **Rows are ticked one at a time.** A parent that ticked its children wrote what the reader never
47
+ said — on a date ladder, today's date onto milestones that happened on unrecorded days. Where a
48
+ run is a sequence, derive position from the highest stamp: one click then marks everything below
49
+ it, and the rungs it passes show a BLANK date, which is the honest answer to "when".
50
+
51
+ ### Also cut
52
+
53
+ - **`TaskSubRow`** and `TaskList.labelWidth`. A checklist REPORTS: it does not collect, and it
54
+ does not preview. A field with a home elsewhere — an assignee, an address — is named as a gap
55
+ by a `ChecklistNote` that links to the section owning it. An editor in a task row gave every
56
+ row two modes with nothing to say which one the reader was in; a rendered value (an avatar, a
57
+ badge) is a copy that has to be kept in step with the field's real home. `ChecklistField`
58
+ survives for the narrow case of a value with NO other home (a portal login created at that
59
+ step), and `ChecklistItem.trailing` for the row's OWN stamp.
60
+ - **`Marker`** — zero consumers.
61
+ - **`Step.partial`** — its only caller was the stage ring, and stages no longer wear one.
62
+ `CheckCircle`'s three positions are unchanged for direct use.
63
+ - `TaskList.density` — one surface used it; a run of rows has one rhythm.
64
+
65
+ ### Kept
66
+
67
+ `Stepper` / `Step` remain the low-level position READOUT — a wizard's rail is not a checklist.
68
+ `Stepper` gains `connected` and `gap`; `Step` gains `marker={false}` for a row that groups
69
+ others.
70
+
7
71
  ## 27.15.0 — a register row's text is selectable, and a drag no longer opens the record
8
72
 
9
73
  No API change, but two behaviors changed on **every** `PressableRow` / `TableRow` / `PressDoor`
package/docs/catalog.md CHANGED
@@ -111,24 +111,40 @@ input chrome, so it aligns pixel-for-pixel). A stack of labelled field rows live
111
111
  `DetailTable` + `DetailRow`; the record's identity band is `RecordSummary`; its money
112
112
  summary is `Ledger`.
113
113
 
114
- ### Tasks / to-dos — pick by ALTITUDE
115
-
116
- A task-management PAGE (many tasks, grouping, filters, expandable rows) is COMPOSITION
117
- the `Task` compound owns the row `TaskStatus` (a `CheckCircle`), `TaskTitle` (a struck
118
- `InlineTextInput`), `TaskActions`. **The row carries the TITLE; the fields a user can SET hang
119
- under it as `TaskSubRow`s** (`InlineDatePicker`/`InlineMemberSelect`/`InlineSelect`, each with
120
- its NAME beside it), never a `DetailTable` in a `TaskDetail`, which declares a grid of its own.
121
- One composition serves a standalone task surface at any density — a flat list or a grouped desk
122
- boardbecause a sub-row reflows instead of being authored per surface; add `SuggestionChip`
123
- commons + `CaptureRow` for the capture case. **A list you scan DOWN columnsthe same four values
124
- compared across twenty rows is a `Table`, not a `TaskList`.**
125
-
126
- **A RECORD never carries a checklist.** Its work state is `Pipeline`
127
- ([`tpl_record`](../examples/tpl_record.tsx) § Progress): a reached stage owns the facts it stamps,
128
- the LIVE stage alone carries its condition and the one act that leaves it. A per-desk checklist
129
- described the work but never said where the record SAT, and it put the CTA a section away from the
130
- state that gated it — so `tpl_record` replaced one with the pipeline, and a register's workspace
131
- drawer follows the same shape. `TaskList` is for a surface whose SUBJECT is tasks; see
114
+ ### Tasks, to-dos, phased work ONE compound, two axes
115
+
116
+ Anything a reader marks is `checklist` `Checklist` + `ChecklistGroup` + `ChecklistItem`. Two
117
+ axes carry every shape it takes, and BOTH are decisions about meaning, not looks:
118
+
119
+ - **`connected`** is this a SEQUENCE? On for one record walking positions (the line asserts
120
+ order), off for N items handled in any order, where a line claims an order the work does not
121
+ have.
122
+ - **`mark`** does ticking mean DONE or CHOSEN? `check` is a ring, `select` is the square box.
123
+ Reach for the square whenever the row is PICKED rather than finished which forms to produce,
124
+ which records to export. Spending the ring on a choice is the failure this axis exists to
125
+ prevent: a reader who has learned that a filled ring means "done" reads a ticked pick-list as
126
+ work already carried out, and nothing on screen corrects them. (The shapes are 24px either way
127
+ and share one gutter, so a surface swaps between them without moving anything.)
128
+
129
+ A record's Progress and a desk's to-do list are the same component two booleans apart; they were
130
+ two families until the anatomy proved identical.
131
+
132
+ **A row carries its TITLE and its own stamp, and nothing else.** A checklist REPORTS: it does not
133
+ collect, and it does not PREVIEW. A field some section owns — an assignee, an address, a portal
134
+ login — is NAMED as a gap by a `ChecklistNote` whose `action` jumps to that section, never edited
135
+ in the row (two edit surfaces for one fact) and never rendered as a value (an avatar or badge is
136
+ a copy that has to be kept in step). The exceptions are narrow and stated: `ChecklistItem.trailing`
137
+ for the row's OWN stamp (a tick writes today, so a run filled in after the fact needs the
138
+ correction in reach), and `ChecklistField` for a value with NO other home.
139
+
140
+ **A list you scan DOWN columns — the same four values compared across twenty rows — is a
141
+ `Table`.** Grouping, filters and a per-row register belong there.
142
+
143
+ **A record's work state is a connected checklist** ([`tpl_record`](../examples/tpl_record.tsx)
144
+ § Progress), where the desks are `ChecklistGroup`s and the milestones are rows. A per-desk
145
+ checklist without the connecting line described the work but never said where the record SAT, and
146
+ put the CTA a section away from the state that gated it. A register's workspace drawer renders the
147
+ same record, so it follows automatically; for a surface whose SUBJECT is tasks see
132
148
  [`tpl_task_board`](../examples/tpl_task_board.tsx).
133
149
 
134
150
  ### Tabular data — pick by SCALE + intent
@@ -494,6 +510,21 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
494
510
  heading row) where no text edge exists to betray, or give it the filled tier.
495
511
  - **`icon_button`** — `IconButton`: the icon-only circular action (see
496
512
  [Actions](#actions)).
513
+ - **`comments_button`** — `CommentsButton`: THE discussion affordance for a listed record — the
514
+ count and a bubble as ONE control, opening the thread. Reach for it wherever a record is listed
515
+ and its conversation would otherwise be invisible until opened (a register row, a card, a
516
+ compact header); it is `CopyButton`'s peer — the other thing a dense row lets you press — and
517
+ shares its surface, scale and focus ring, so a row carrying both reads as one grammar. The count
518
+ is INSIDE the target, never loose text beside an `IconButton`: the number is what the reader
519
+ aims at, and splitting them leaves half the affordance dead under the pointer. FULL INK, unlike
520
+ the muted values around it — everything else on a row is a fact ABOUT the record, this is people
521
+ talking about it and the one thing there that can be UNREAD. `subject` reaches the accessible
522
+ NAME only ("3 comments · Northwind Packaging" is a destination; "3 comments" on the fortieth row
523
+ is not) — never the visible label, which would spend row width restating whose record it is.
524
+ **Render it only when `count > 0`** — a zero on every quiet row is a column of noise that trains
525
+ the eye to skip exactly where the signal will appear. Pair it with a jump that LANDS on the
526
+ thread (`tpl_item_list` opens the record's drawer on its comments section), because a count the
527
+ reader cannot act on states that a conversation exists and leaves them to find it.
497
528
  - **`copy_button`** — `CopyButton`: THE copy control — `value` onto the clipboard, glyph
498
529
  flips to a check and the tooltip to the pack's "Copied" for ~2s. The confirmation follows
499
530
  a REAL write: an unreachable clipboard throws and the button stays at rest rather than
@@ -834,8 +865,8 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
834
865
  not a drag handle — dragging is invisible to the keyboard and fiddly on a phone, and a
835
866
  three-to-six position list does not need it. Indent anything that follows the list (an "Add"
836
867
  link) by `SEQUENCE_INSET` so it lands on the rail's column. Distinct from `Timeline` (an
837
- activity FEED), `Stepper` (a wizard's fixed positions) and `Pipeline` (ONE record walking
838
- stages that own their controls) — this is the list a user EDITS.
868
+ activity FEED), `Stepper` (a wizard's fixed positions) and `Checklist` (rows a reader MARKS —
869
+ a record walking positions, or a set picked) — this is the list a user EDITS.
839
870
  - **`inline_button`** — `InlineButton`: a verb that sits INSIDE an inline field's surface
840
871
  (the `Open` on a reference, a `Copy` on a value worth copying) rather than in the row's
841
872
  trailing column — put it inside when the act is ABOUT THE VALUE, so it travels with what it
@@ -864,12 +895,12 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
864
895
  wire both); invalid/cross-field STATE = a co-located `Callout`; **`flat`**
865
896
  on a flat-text value row (`InlineStatic`, plain `Text`) tucks the annotation up by the
866
897
  control band's slack so its gap matches a chip row's.
867
- The three annotations are ONE anatomy shared with a task's `TaskSubRow` — same names, same
898
+ The three annotations are ONE anatomy shared with a checklist row's `ChecklistNote` — same
868
899
  order, same alert semantics — because a record's field and a task's field are the same thing
869
900
  on two surfaces.
870
901
  `DetailTable`: the compound parent of
871
902
  a row STACK — `labelWidth` (default `DETAIL_LABEL_WIDTH`, 130 — THE kit's label column, and
872
- a `TaskList`'s default too) / `minHeight` (default 40, the inline-control grid) declared
903
+ the record surface's) / `minHeight` (default 40, the inline-control grid) declared
873
904
  ONCE + the `SPACE.md` (16) row gap — 8 was right while a field was a tint, but a stack of
874
905
  BORDERED rows that close together fuses into one block. TWO columns, no third: the value
875
906
  FILLS what the label leaves, so nothing one row does can narrow its neighbours' editors. RESPONSIVE with no prop: it
@@ -1020,6 +1051,20 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
1020
1051
  by the row's own toggle), the row shows the open wash while it is out, and the door announces
1021
1052
  `aria-expanded`. Prefer the columns first: a fact every row needs is a column, not a reason
1022
1053
  to expand.
1054
+ **`Table.count` + `TableRow.ordinal` make it a COUNTED register** — a narrow gutter left of
1055
+ `leading` carrying each row's position, headed by the total. Reach for it on any register
1056
+ long enough to scroll: it answers "how many" without paging to the end and "where am I"
1057
+ without counting rows, and it is what a reader coming from a spreadsheet (STT) expects to
1058
+ find. `count` is the single switch — omit it and no gutter is reserved, so an uncounted
1059
+ register keeps every pixel it had. It is CHROME, not a column: it never sorts, never drops,
1060
+ and is excluded from the column band, but it IS reserved space the fit accounts for.
1061
+ **Both numbers are the caller's**, and deliberately: `count` is what MATCHED (a paged
1062
+ register renders fifty of it, a filtered one counts the matches), and `ordinal` continues
1063
+ across pages (`offset + i + 1`) or restarts inside a group band, depending on which question
1064
+ the reader is asking — "which of all" or "which of these". Omit `ordinal` on a row with no
1065
+ position (a group band, a totals line) and the gutter stays blank so the cells beside it stay
1066
+ in line. A position is NEVER an identity: sort or filter and row 12 is a different record —
1067
+ a stable handle is a column, and it leads the cells. Worked screen: `tpl_item_list`.
1023
1068
  - **`sort_header`** — `SortHeader` + `SortState`/`SortDir` + `cycleSort` + `sortBy` +
1024
1069
  `SortHeaderLabels`: the sortable column header and the sort-state helpers `Table`/
1025
1070
  `DataGrid` consumers drive.
@@ -1038,7 +1083,7 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
1038
1083
  summarizes — including a record's child list (a consol's houses, an order's lines). **Never on
1039
1084
  a record's identity band reporting that record's own state**: derived verdicts on one record
1040
1085
  ("documents incomplete", "2 fields need checking") are the checklist a record must not carry
1041
- (§`pipeline`) and restate at a distance what the sections below state in place.
1086
+ (§`checklist`) and restate at a distance what the sections below state in place.
1042
1087
  - **`use_selection`** — `useSelection`: always-on multi-select state for a register/list —
1043
1088
  the `selected` Set + `toggle`/`setAll`/`allSelected`/`indeterminate`/`count`/`clear`;
1044
1089
  selectability gating stays with the caller. The checkbox-always-visible counterpart to
@@ -1068,89 +1113,6 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
1068
1113
  (`onChange(done)`) so the ring means one thing everywhere; `partial` is reached through
1069
1114
  whatever NAMES it — a status cell, or children ticking off — never by cycling the ring. Keep
1070
1115
  it monochrome and let colour live in the status cell.
1071
- - **`task`** — `TaskList` + `TaskItem` + `TaskStatus` / `TaskTitle` / `TaskCaption` /
1072
- `TaskActions` / `TaskSubRow` / `TaskDetail` — the task COMPOUND, for anything
1073
- from a 5-item drawer checklist to a grouped desk board. `TaskList` owns geometry only: the two
1074
- GUTTERS — `controlWidth` (the left one, that every title aligns on, and so the INDENT step) and
1075
- `actionWidth` (the right one, that every ⋯ pins into; `0` on a list whose rows carry no
1076
- actions) — plus `labelWidth` (the column every `TaskSubRow` renders its NAME in; default
1077
- `DETAIL_LABEL_WIDTH`, 130) and `density` (`comfortable` = a 44px minimum tap target, `dense` =
1078
- 32 for a pointer-driven register). All four are inherited by nested lists. Everything else is
1079
- composed, and JSX order is screen order — nothing inspects child types.
1080
-
1081
- `TaskStatus` takes the `CheckCircle` (omit `onChange` for a read-only ring; a PICKER list
1082
- puts a `CheckboxInput` here and sets `controlWidth={24}`). `TaskTitle` takes the
1083
- `InlineTextInput variant="bare"` — or the title TEXT itself as a string child
1084
- (`<TaskTitle struck={done}>{label}</TaskTitle>`), which is the READ-ONLY form: the compound
1085
- applies the cell inset and the row's band, so a plain `<Text>` + a hand-rolled
1086
- `TASK_TEXT_INSET` is never needed and a long title that wraps keeps its first line beside the
1087
- control. It spans the content box between the gutters, like every line hung beneath it.
1088
- `TaskCaption` is the row's state IN WORDS on its own line under the title — a
1089
- sentence about the ROW, never a field, and never a sentence about one FIELD (that is the
1090
- sub-row's own `description` / `warning` / `error`).
1091
- `TaskActions` carries the row's ⋯ `ActionMenu`, pinned into the RIGHT
1092
- gutter on the first line (Delete lives BEHIND it, danger-styled and last, never a bare ✕).
1093
- `TaskDetail` is a FREE-FORM block under the row on the title's text edge — a chart, a table, a
1094
- form with its own submit — rendered only while open. The module also exports
1095
- **`TASK_TEXT_INSET`** (9), the inset an inline control puts on its own text: every
1096
- slot above already applies it, so reach for it ONLY when a custom title NODE (not a cell
1097
- control) has to land on the same text edge.
1098
-
1099
- **THE ⋯ IS A COLUMN, THE MIRROR OF THE CONTROL GUTTER.** Both pinned controls sit outside the
1100
- flow and the ROW reserves each edge (`actionWidth` + the row gap), so what is left between them
1101
- is ONE content box every line spans exactly — title, caption, sub-row, detail, nested list. That
1102
- is what keeps a list where only SOME rows carry a menu straight: a menu-less row's content ends
1103
- on the same x as a menu-carrying one's, and so does everything hanging under either. A nested
1104
- list hands the parent's reservation back before its own rows re-take it, so the column does not
1105
- step inward per level. Declare `actionWidth={0}` when NO row in the list carries actions.
1106
-
1107
- **THE ROW CARRIES THE TITLE; THE FIELDS THE USER CAN SET ARE SUB-ROWS BENEATH IT.** One task's
1108
- own fields hang under it as **`TaskSubRow`** (`label`, the control, `description` /
1109
- `warning` / `error`), indented ONE step — the same step a nested `TaskList` takes, because
1110
- belonging is expressed by indentation and there is only one device for it. Label and value sit
1111
- ADJACENT so the eye pairs them. **The label column is the LIST's, not the row's** — one
1112
- `labelWidth` for every sub-row at every depth, exactly as `DetailTable labelWidth` is the
1113
- record's — so every value on the surface starts on ONE x. A name longer than the column WRAPS
1114
- inside it and its value does not move; a column each row sized to its own words is not a
1115
- column (it started a short label's value at one x and a long one's at another, and any
1116
- vocabulary with longer field names staggered nearly every row). The value takes the slack from
1117
- a readable minimum — on a phone or in a narrow drawer it drops onto its own line under the
1118
- label rather than ellipsizing beside it. Pass a form-variant `Inline*` editor (the default),
1119
- `variant="bare"`: a dense grid drops the resting frame.
1120
-
1121
- **A TASK'S FIELD ANNOTATES EXACTLY LIKE A RECORD'S FIELD.** `description` (persistent
1122
- guidance), `warning` (a consequence to weigh — amber, announced) and `error` (field-level
1123
- failure — danger, alert semantics) are the same three props with the same order and a11y as
1124
- `DetailRow`'s, from ONE shared implementation. A fault in a single field belongs ON that
1125
- field; `TaskCaption` is a sentence about the whole ROW, so demoting a field fault into one
1126
- says something different.
1127
-
1128
- **ANYTHING THAT NEEDS SCANNABLE COLUMNS IS A `Table`, NOT A `TaskList`.** The compound spent a
1129
- version carrying a declared value column so cells would line up down the list; it aligned and
1130
- it read worse — each field's name ended up a quarter of the surface from its control, sized for
1131
- the widest value on the page. If the job is comparing the same four values across twenty rows,
1132
- that is a table, and `Table`/`DataGrid` are built for it. A `DetailTable labelWidth={…}` inside
1133
- a `TaskDetail` is the other shape to avoid: the list ALREADY has a label column, so a second
1134
- one lands its label and its value at two more x positions matching nothing above them — a form
1135
- pasted into a list. Fields belonging to the task are `TaskSubRow`s, which annotate the same way
1136
- a `DetailRow` does, so there is nothing left that a nested `DetailTable` can say.
1137
-
1138
- **A row's FIRST LINE is a BAND** — 44px `comfortable`, 32 `dense`. Both gutter controls centre
1139
- in it and `TaskTitle` claims it, which is what keeps both columns of
1140
- controls straight as rows grow: a caption, a sub-row, a detail block, a nested list or a
1141
- wrapped title all hang BELOW the band instead of dragging the control down into the gap under
1142
- the title. So every sub-line sits the band's slack (12px comfortable) beneath the title's
1143
- words, the same whether the title is text or an editor. A title node TALLER than the band
1144
- overrides it — an inline editor is 40px, so a `dense` list of editable titles renders 40px rows
1145
- and its ring reads a few px high; `dense` is for text/static rows. A sub-row takes the TALLER
1146
- of the band and the 40px inline-control height, so its label's first line still meets its
1147
- control's centre in a `dense` list.
1148
-
1149
- **Subtasks are a nested `TaskList`**, so a step IS a task: give one a field, a menu or
1150
- children of its own and it works. Collapsing belongs to the app — hold a boolean and render
1151
- the nested list or don't. There is deliberately **no note slot**: a task's free text is its
1152
- title, a caption, or detail, and a fourth place to write invited writing it twice.
1153
-
1154
1116
  - **`suggestion_chip`** — `SuggestionChip`: the dismissible SUGGESTION pill — an item the
1155
1117
  record could have but doesn't yet (a common task, an expected line) as a `Chip` whose
1156
1118
  press MATERIALIZES it (plus glyph + label, one tuned anatomy) and whose ✕ refuses it;
@@ -1307,39 +1269,34 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
1307
1269
  provider-wired — pass `labels` (`DEFAULT_GANTT_LABELS` is English).
1308
1270
  - **`timeline`** — `Timeline`: a heterogeneous event LOG — per-row icon + expandable
1309
1271
  details; models the past, NOT progress.
1310
- - **`pipeline`** — `Pipeline` + `PipelineStage` + `PipelineField` / `PipelineNote` /
1311
- `PipelineActions` the handoff COMPOUND: one record walking ordered STAGES where each stage
1312
- owns its own fields, conditions and act. Composes over `stepper` (which renders position
1313
- only). Reach for it over `task` when the rows are POSITIONS rather than work items one thing
1314
- moving, one stage live, different controls per stage; `task` is N identical rows ticked in any
1315
- order. `PipelineNote` is a condition ON a stage (never a `Callout` above the run);
1316
- `PipelineField` stays editable on a PASSED stage, which is what makes a mis-entry fixable.
1317
- A field stacks UNDER the title (two lines per stage); on a ladder long enough that this
1318
- pushes the run past a screenful, **`PipelineStage.trailing`** puts ONE value on the title's
1319
- own row instead. Never hand-roll that row: a control is twice a text line's height, so the
1320
- marker which centres on the first row reads half the difference too high against it.
1321
- `trailing` fixes the row at `INLINE_CONTROL_HEIGHT` and tells the `Step`, so the ladder keeps
1322
- one rhythm whatever the value is. An ACT still belongs in `PipelineActions`.
1323
- Worked screen: `examples/tpl_record.tsx` § Progress the desks (Sales Operations
1324
- Accounting) ARE the stages, each owning the facts it stamps and, on the live one, the act
1325
- that leaves it. A `PipelineStage` body is deliberately NOT pressable: it holds the controls.
1326
- **`PipelineStage.onToggle`** is the one exception and lives on the MARKER the node becomes a
1327
- pressable `CheckCircle` instead of a status dot, for a ladder the reader TICKS OFF rather than
1328
- only reads. Reach for it before putting a checkbox in `trailing`: that lands a second completion
1329
- affordance beside a ring that already looks like one, and the reader has to guess which is real.
1330
- **Which of the two shapes you want**: if finishing a step must capture anything beyond "it
1331
- happened" who took it, a reason it is a HANDOFF, and the act belongs in `PipelineActions`
1332
- with the marker only reporting. If it does not, it is a MILESTONE and the marker IS the act.
1333
- ONE ladder per record, never two. A rung that needs more than a tick (a dialog, an owner, a
1334
- reason) renders it in its BODY the tick and the act coexist on the same stage, which is what
1335
- a real pipeline looks like. Splitting them into a handoff run and a milestone run puts "where
1336
- is this" in two places the reader has to watch together. Worked screen:
1337
- `examples/tpl_record.tsx` § Progress — tickable markers, the stamped date on the title row as a
1338
- `bare` `InlineDatePicker` (reads as text, border on hover, click to edit), and the live stage
1339
- still carrying its owner, its note and the act that leaves it. The date stays EDITABLE on
1340
- purpose: a tick always writes today, so inert text would make a ladder filled in after the
1341
- fact — every imported record — impossible to enter correctly. An unstamped rung shows no
1342
- placeholder; its empty ring already says "not yet".
1272
+ - **`checklist`** — `Checklist` + `ChecklistGroup` + `ChecklistItem` + `ChecklistNote` /
1273
+ `ChecklistActions` / `ChecklistField`: THE run of tickable rows, at any
1274
+ altitude. It replaced a `task` family and a `pipeline` family that had converged on one
1275
+ anatomy a ring, a title, a value beside it, a muted note, a detail block, an optional
1276
+ grouping level differing only in whether a LINE joined the rings. **`connected` is that
1277
+ line, and it is the whole difference**: on for ONE record walking positions (it asserts
1278
+ sequence), off for a SET picked in any order, where a line claims an order the work does not
1279
+ have. Rows are ticked ONE AT A TIME; where the run is a sequence, derive position from the
1280
+ highest stamp so one click marks everything below it a parent that ticked its children wrote
1281
+ what the reader never said (today's date onto milestones that happened on unrecorded days),
1282
+ and a rung passed without a recorded day shows a BLANK date, which is the honest answer to
1283
+ "when". **`ChecklistGroup` is a phase a name, and at most a way to fold its rows away**: no
1284
+ COMPLETION control, because a phase
1285
+ owns no completion of its own and a ring there is a control that never responds (one identical
1286
+ to the pressable rings beneath it invites the press it refuses); and no BODY, because a
1287
+ condition hung off a heading leaves the reader working out which row it was about — a row's
1288
+ wait (`meta`), warning (`ChecklistNote`) and act (`ChecklistActions`) sit on the row that owes
1289
+ them. Groups and items are SIBLINGS in one run, so the ladder keeps one ring column, one text
1290
+ edge and one line; a flat list renders no groups at all. **A checklist REPORTS — it does not
1291
+ collect and it does not PREVIEW**: a field with a home elsewhere is named as a gap by a
1292
+ `ChecklistNote` whose `action` jumps to the section owning it, never edited here (two edit
1293
+ surfaces for one fact) and never rendered as a value (an avatar or badge is a copy to keep in
1294
+ step). The exceptions are `ChecklistItem.trailing` for the row's OWN stamp a tick writes
1295
+ today, so a run filled in after the fact needs the correction in reach and `ChecklistField`
1296
+ for a value with NO other home (a portal login created at that step, unreachable otherwise).
1297
+ `ChecklistItem.meta` is two words on the title's row; prose that grows is a `ChecklistNote`,
1298
+ which owns its line. Worked screens: `tpl_record` § Progress (connected, grouped) and its
1299
+ document desk (unconnected set).
1343
1300
  - **`stepper`** — `Stepper` + `Step` — done/current/upcoming/warning/complete progress on a
1344
1301
  track (horizontal) or spine (vertical); compound `<Step status>children` OR data
1345
1302
  `steps[]`+`current`; **navigable** via `Step.onPress` (both orientations — the whole step
@@ -1350,7 +1307,7 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
1350
1307
  VERTICAL: the marker centres on the content's FIRST ROW, assumed to be one line of `sm` text.
1351
1308
  Put anything taller on that row — an inline editor beside the label — and pass
1352
1309
  **`Step.headHeight`** (`INLINE_CONTROL_HEIGHT` for an editor) or the marker stays pinned to
1353
- the text line while the label centres in the taller row. Prefer `PipelineStage.trailing`,
1310
+ the text line while the label centres in the taller row. Prefer `ChecklistItem.trailing`,
1354
1311
  which does this for you.
1355
1312
 
1356
1313
  ### Files