@lotics/ui 21.1.0 → 21.3.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
@@ -27,6 +27,13 @@ CURRENT major only — upgrading an app across majors is `MIGRATION.md`.
27
27
  license to hand-roll.
28
28
  - **One canonical component per data role** (member → `MemberChip`, select → `OptionBadge`,
29
29
  files → `FilePreview` family, …) — the catalog's Reach-by-role outranks neighboring code.
30
+ - **Progress: rows of WORK vs positions of ONE thing.** N items ticked in any order, every row
31
+ the same shape → `task` (`TaskList`). ONE record walking ordered stages where the stage decides
32
+ which fields, conditions and act are even offered → **`pipeline`** (`Pipeline` +
33
+ `PipelineStage`, over `stepper`) — worked in `examples/tpl_record.tsx` § Progress, which it took
34
+ over FROM a per-desk checklist. Rendering positions as a checklist forces every row to carry
35
+ every control and never says where the record sits; rendering work items as a pipeline implies
36
+ an order that is not there.
30
37
  - **`Badge` = STATUS only; supporting detail is the muted second line.** A type / category /
31
38
  attribute / count is not a status — it belongs under its identity as `size="xs" color="muted"`,
32
39
  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,22 @@ 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
+ ## 21.2.0 — `Stepper` is a list, not a progressbar
8
+
9
+ No API changed, but the RENDERED role did, so a test that queried it breaks:
10
+
11
+ ```ts
12
+ page.getByRole("progressbar", { name: "Pick path" }) // BEFORE
13
+ page.getByRole("list", { name: "Pick path" }) // AFTER — <ul> of <li>, one per Step
14
+ ```
15
+
16
+ `progressbar` is a range widget: without `aria-valuenow`/`min`/`max` it announces as an
17
+ indeterminate "busy", and its children are PRESENTATIONAL — which hid every step's label, and
18
+ in a `Pipeline` the date pickers, selects and buttons a stage carries. A sequence of named
19
+ positions is a `list` whose live entry has `aria-current="step"`. `StepProgress` keeps
20
+ `progressbar` — its segments really are decoration over one quantity — and now carries the
21
+ values it was missing.
22
+
7
23
  ## v21 from 20.x
8
24
 
9
25
  **`FilesEditor` is a COMPOUND.** It rendered a fixed toolbar — Upload · Select · Download all,
package/docs/catalog.md CHANGED
@@ -112,9 +112,10 @@ its NAME beside it), never a `DetailTable` in a `TaskDetail`, which declares a g
112
112
  The SAME composition serves a record's 5–8-task drawer checklist and a grouped desk board,
113
113
  because a sub-row reflows instead of being authored per surface; add `SuggestionChip` commons +
114
114
  `CaptureRow` for the checklist case. **A list you scan DOWN columns — the same four values
115
- compared across twenty rows — is a `Table`, not a `TaskList`.** See
116
- [`tpl_record`](../examples/tpl_record.tsx) / [`tpl_item_list`](../examples/tpl_item_list.tsx),
117
- and [`tpl_task_board`](../examples/tpl_task_board.tsx) for columns.
115
+ compared across twenty rows — is a `Table`, not a `TaskList`.** And a record's own PROGRESS is
116
+ not a checklist at all — that's `Pipeline` ([`tpl_record`](../examples/tpl_record.tsx) § Progress).
117
+ See [`tpl_item_list`](../examples/tpl_item_list.tsx) for the checklist grammar, and
118
+ [`tpl_task_board`](../examples/tpl_task_board.tsx) for columns.
118
119
 
119
120
  ### Tabular data — pick by SCALE + intent
120
121
 
@@ -158,7 +159,9 @@ plain sm tabular count beside it — the cell/heading/peek-trigger meter; a capt
158
159
  above a tiny bar reads misaligned. **The track clamps at 100%, the caption does not** — over
159
160
  its max it reads `2,100 / 2,000 · 105%`, because a meter that says "100%" when you are over
160
161
  tells the reader they are exactly at the limit. Numbers format in the reader's locale, so
161
- never hand-format the value you pass in) / `StackedProgressBar` / `StepProgress`, `Breakdown` (a
162
+ never hand-format the value you pass in when display precision differs from the true value
163
+ (whole credits off a fractional balance), `formatValue` reshapes the caption text and leaves
164
+ the fill and the percentage exact) / `StackedProgressBar` / `StepProgress`, `Breakdown` (a
162
165
  stacked bar + ranked share rows, pressable to drill; `maxRows` folds the long tail behind a
163
166
  "Show N more" toggle — `labels` to localize — so several facet cards align to one height in
164
167
  a row), `Funnel` (a CONVERSION funnel — ordered stages as bars that NARROW; the step
@@ -687,7 +690,7 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
687
690
  VALUE-FIRST: the date drops its resting calendar glyph, a select drops its chevron (the column
688
691
  header + the uniformly-editable grid are the affordance; the saving spinner still shows). For a
689
692
  DENSE, uniformly-editable surface — a register/`DataGrid` column, a task row (worked example:
690
- `tpl_task_board`'s columns, `tpl_record`'s Tasks section).
693
+ `tpl_task_board`'s columns, `tpl_item_list`'s rows).
691
694
 
692
695
  There is NO separate `*Cell` component family — a data-grid cell is `<InlineSelect variant="cell" …/>`,
693
696
  not a `SelectCell`. (Naming a component for a USE, and duplicating the picker stack to flip two
@@ -983,7 +986,9 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
983
986
  plain sm tabular count beside it.
984
987
  - **`stacked_progress_bar`** — `StackedProgressBar`: one whole split across segments (a
985
988
  status mix on one bar).
986
- - **`step_progress`** — `StepProgress`: N-of-M dots/segments progress.
989
+ - **`step_progress`** — `StepProgress`: N-of-M dots/segments progress. A real `progressbar`
990
+ (valuenow/min/max) because its segments are decoration over ONE quantity — the opposite
991
+ case from `stepper`, whose steps carry content a `progressbar` would hide.
987
992
  - **`breakdown`** — `Breakdown`: a stacked bar + ranked share rows, pressable to drill;
988
993
  `maxRows` folds the long tail behind a localized "Show N more" toggle
989
994
  (`BreakdownLabels`).
@@ -1020,11 +1025,23 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
1020
1025
  provider-wired — pass `labels` (`DEFAULT_GANTT_LABELS` is English).
1021
1026
  - **`timeline`** — `Timeline`: a heterogeneous event LOG — per-row icon + expandable
1022
1027
  details; models the past, NOT progress.
1028
+ - **`pipeline`** — `Pipeline` + `PipelineStage` + `PipelineField` / `PipelineNote` /
1029
+ `PipelineActions` — the handoff COMPOUND: one record walking ordered STAGES where each stage
1030
+ owns its own fields, conditions and act. Composes over `stepper` (which renders position
1031
+ only). Reach for it over `task` when the rows are POSITIONS rather than work items — one thing
1032
+ moving, one stage live, different controls per stage; `task` is N identical rows ticked in any
1033
+ order. `PipelineNote` is a condition ON a stage (never a `Callout` above the run);
1034
+ `PipelineField` stays editable on a PASSED stage, which is what makes a mis-entry fixable.
1035
+ Worked screen: `examples/tpl_record.tsx` § Progress — the desks (Sales → Operations →
1036
+ Accounting) ARE the stages, each owning the facts it stamps and, on the live one, the act
1037
+ that leaves it. A `PipelineStage` is deliberately NOT pressable: its body holds the controls.
1023
1038
  - **`stepper`** — `Stepper` + `Step` — done/current/upcoming/warning/complete progress on a
1024
1039
  track (horizontal) or spine (vertical); compound `<Step status>children` OR data
1025
1040
  `steps[]`+`current`; **navigable** via `Step.onPress` (both orientations — the whole step
1026
1041
  is the tap target) + `active` to wash the selected one, so it doubles as a section/phase
1027
- switcher; the guided-run / agent-feed primitive.
1042
+ switcher; the guided-run / agent-feed primitive. It is a **`list` of `listitem`s** with
1043
+ `aria-current="step"` on the live one — a sequence of NAMED positions whose steps carry
1044
+ their own content is a list, never a `progressbar`.
1028
1045
 
1029
1046
  ### Files
1030
1047
 
package/docs/templates.md CHANGED
@@ -41,8 +41,10 @@ the package index is [../AGENTS.md](../AGENTS.md).
41
41
  | A register / consolidated work-list (browse, per-row action, bulk action) | `tpl_item_list` |
42
42
  | A guided sequence of physical tasks (scan, confirm, next) | `tpl_pick` |
43
43
  | Splitting one source amount across many targets | `tpl_allocate` |
44
- | A record's create/edit surface — also the settings shape (its Tasks section IS the canonical task pattern) | `tpl_record` |
44
+ | A record's create/edit surface — also the settings shape | `tpl_record` |
45
+ | A checklist ON a record (the canonical task-list grammar) | `tpl_item_list` (its Tasks section) |
45
46
  | A team task board (inline-managed grouped table) | `tpl_task_board` |
47
+ | One record HANDED between desks — stages that each own their controls | `tpl_record` (its Progress section) |
46
48
  | Financial statements | `tpl_statements` |
47
49
  | A scoped lookup report with export | `tpl_report` |
48
50
  | A week calendar + agenda | `tpl_calendar` |
@@ -258,10 +260,10 @@ left panel as the bare circular `BackButton`). First paint is a `Skeleton` MIRRO
258
260
  layout, never a spinner. It absorbed the old order-form, inline-record, intake, settings,
259
261
  billing, and quick-capture templates. Top → bottom:
260
262
 
261
- - A MINIMAL header: `RecordSummary` (title · subtitle ONLY — no stage chip, no metric; Tasks
262
- carries progress, Handoff carries the desk, money lives in Billing), then the record's
263
+ - A MINIMAL header: `RecordSummary` (title · subtitle ONLY — no stage chip, no metric; Progress
264
+ carries the desk, Handoff carries what it created, money lives in Billing), then the record's
263
265
  ATTENTION state as a co-located `Callout` (e.g. overdue — record-scoped state reads here;
264
- field-scoped state stays on its field), then a quiet `SummaryLine` (tasks done · documents
266
+ field-scoped state stays on its field), then a quiet `SummaryLine` (desk · documents
265
267
  · to collect).
266
268
  - **GENERAL — the MAIN details, FIRST in the rail**; every later section is supplementary.
267
269
  A `SubsectionStack` (space-only beat) of a headingless LEAD group (the key facts) plus
@@ -283,20 +285,15 @@ billing, and quick-capture templates. Top → bottom:
283
285
  (author-only edit/delete; attachments as a pressable `FileGrid` → gallery preview; a
284
286
  file-capable edit form injected via `renderEditForm`) + THE kit `Composer` (attach via
285
287
  `actionsButton`, staged files in its `files` slot) — never a bespoke comment box.
286
- - **Tasks** — the full task-list grammar: clearable Group-by `FilterChip` (+ assignee/status
287
- chips), the `CaptureRow` on top, `TaskList`/`TaskItem` rows and per-row `TaskActions` menus,
288
- suggestions as `SuggestionChip`s; groups divide via the `SubsectionStack` beat with plain
289
- text heads (no dot badges). Each row's **due + assignee are `TaskSubRow`s UNDER the title** —
290
- the SAME `InlineDatePicker` / `InlineMemberSelect` used in the General section, in the default
291
- form variant, each with its NAME beside it one indent step in: the due an urgency-coloured date
292
- (`tone` = `dueTone` red past due, amber ≤3d, else muted), the assignee the full member chip.
293
- A task row is not a table row a value column reserved on the list put each name a quarter of
294
- the record from its own control. ONE row ("Book the carrier") carries **SUBTASKS** — a nested
295
- `TaskList` of four child ticks, each a `TaskItem` with a STRING `TaskTitle`; every other row
296
- holds an empty array and renders no list at all. There is no built-in expander: collapsing is
297
- the app's call, and this template always shows them. It is the line to copy — a booking's steps
298
- carry only a tick, so they stay child rows; anything needing its own due date or assignee is a
299
- task instead.
288
+ - **Progress** — the desk handoff as the ordered positions it is: a bare `Pipeline` over
289
+ Sales Operations Accounting. Each REACHED stage owns the facts it stamps as
290
+ `PipelineField`s (taken-on `InlineDatePicker`, owner `InlineMemberSelect`) and keeps them
291
+ editable once the record has moved on; the LIVE one adds its condition (`PipelineNote`, e.g.
292
+ past the due date) and the single act that leaves it (`PipelineActions` the handoff dialog).
293
+ An unreached stage is its title alone. `meta` carries how long the desk has held it — prose
294
+ the reader can't set, never the date that the stage's own field already shows. See § The
295
+ handoff shape for the rules; for a record that genuinely needs a CHECKLIST, the task-list
296
+ grammar is worked in `tpl_item_list`.
300
297
  - **Documents** — the Agents "Document desk" pattern (this template is its worked example —
301
298
  see the Agents chapter below): the register `Table` (search · Add files) whose selection
302
299
  feeds the `FloatingActionBar` → ONE "Use AI" fork (extract / cross-check / edit-with-AI).
@@ -385,9 +382,16 @@ billing, and quick-capture templates. Top → bottom:
385
382
  here: the dialog collects the receiver (`MemberSelect`, confirm disabled until chosen) +
386
383
  an optional note; confirm writes the trail mark and the SIBLING record, shown as the same
387
384
  linked-record box (its drawer holds the reference info + Open record + **Recall**). After
388
- the handoff there is NO further CTA. Recall is the undo: confirmed, it withdraws the
389
- sibling and returns the desk history is NEVER erased (the trail keeps both marks) and
390
- the CTA returns, so redo is possible.
385
+ the handoff there is NO further CTA the act to leave the NEW desk rides that desk's stage
386
+ in Progress, and this section shows only what the handoff produced (with an empty state when
387
+ it has produced nothing yet).
388
+
389
+ Recall is the undo: confirmed, it withdraws the sibling, clears the custody stamp that
390
+ handoff wrote, and returns the record to the desk BEFORE it — one step, never a jump to the
391
+ first desk, which would silently drop the desks between. History is NEVER erased (the trail
392
+ keeps both marks) and the CTA returns, so redo is possible. **Only the LATEST handoff offers
393
+ Recall.** Undoing a superseded one would leave the record behind a sibling that still exists
394
+ — the stage and the linked records would disagree — so a walk-back is one handoff at a time.
391
395
  - **Danger zone** — with the full section anatomy (heading + description) like every other
392
396
  section; the fenced red `DangerZone` card is the section's BODY.
393
397
  - A LEFT OUTLINE RAIL (`MenuButton` + `useSectionNav`) — **the rail is the page's COMPLETE
@@ -415,6 +419,51 @@ billing, and quick-capture templates. Top → bottom:
415
419
  below that it collapses to the pinned bar. Sizing the column is the SHELL's job — inner
416
420
  blocks are plain `width: "100%"`, never a second `maxWidth` + `alignSelf: "center"`.
417
421
 
422
+ ### The handoff shape — `Pipeline`, and where it lives
423
+
424
+ One record walking N ordered stages, where **the stage decides what you can see and do**: work
425
+ passed between desks — an application through intake → review → production → filing, a claim
426
+ through submitted → assessed → paid.
427
+
428
+ It is NOT its own template. A handoff is something a RECORD does, so it renders as one section
429
+ OF that record — see `tpl_record` § Progress, where the desks (Sales → Operations → Accounting)
430
+ become the stages, each owning the facts it stamps (taken-on, owner) and the live one carrying
431
+ the act that leaves it.
432
+
433
+ **It replaced a per-desk CHECKLIST, and that swap is the lesson.** Ticking boxes described the
434
+ WORK; it never said where the record SAT, so the reader had to infer position from which group
435
+ still had unticked rows — and the handoff CTA sat in a different section entirely, the act
436
+ divorced from the state that gated it. A stage says the position outright, and the act rides the
437
+ stage it belongs to. Reach for a checklist only when the rows are genuinely N independent
438
+ tickable items (`TaskList`); a record's own progress is a pipeline.
439
+
440
+ Built on the `Pipeline` compound (`Pipeline` · `PipelineStage` · `PipelineField` ·
441
+ `PipelineNote` · `PipelineActions`), which composes over `Stepper` — `Stepper` renders POSITION,
442
+ this adds the anatomy that turns a position into a workspace.
443
+
444
+ Three rules the anatomy holds, each learned from getting it wrong:
445
+
446
+ - **Only the CURRENT stage carries an act.** A control on an unreached stage invites acting out
447
+ of order — and where the stage is derived from the highest stamp, one stray press jumps the
448
+ record past the desk that owns it. On a passed stage it re-offers something already done.
449
+ - **A condition belongs to its stage.** "Sent back", "rejected", "waiting on the customer" are
450
+ facts about ONE stage, so they render inside it (`PipelineNote`) rather than as a `Callout`
451
+ above the run, which reads as "something is wrong with this record" and sits nowhere near the
452
+ control that answers it.
453
+ - **A passed stage stays correctable** — its date (clearable, which is how you walk a record
454
+ BACK) and any field it owns. Otherwise a value typed wrong becomes unreachable the moment the
455
+ record moves on, and the only fix is direct table access.
456
+ - **The stage that OPENS gets stamped by the act that opened it**, and the TERMINAL act is not
457
+ a handoff. Handing off asks whom (a dialog) and writes that answer into the receiving stage's
458
+ own fields — coming up blank re-asks a question just answered. Finishing has no receiving
459
+ desk, so it asks for confirmation and nothing else; routing it through the same dialog is how
460
+ a template ends up asking "Assignee at Closed" and then confirming nothing.
461
+
462
+ **Altitude rule**: the pipeline is ONE record's progress. N records each at their own stage is
463
+ a REGISTER (`tpl_item_list`) with a derived stage column — the same ladder module feeding both,
464
+ so a row's badge and its filter bucket can never disagree. Rows that are structurally identical
465
+ and tickable in any order are a `tpl_task_board`, not a pipeline.
466
+
418
467
  ### `tpl_task_board` — the columns shape
419
468
 
420
469
  The manager's board: a search · group-by · filter toolbar over a grouped, sortable grid of