@lotics/ui 45.10.0 → 45.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +70 -137
- package/docs/ai_patterns.md +88 -150
- package/docs/catalog.md +206 -284
- package/docs/composition.md +394 -518
- package/docs/data_entry.md +109 -155
- package/docs/reviewing.md +39 -55
- package/docs/templates.md +459 -423
- package/docs/testing.md +3 -7
- package/package.json +1 -1
- package/src/pressable_row.tsx +7 -5
- package/src/table.tsx +15 -14
package/docs/templates.md
CHANGED
|
@@ -2,11 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
The package ships full worked-example screens in `examples/tpl_*.tsx` — each a complete,
|
|
4
4
|
runnable recipe for one screen **job**, built purely from `@lotics/ui` components plus mock
|
|
5
|
-
data. This doc is the map: what shape each template solves
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
the package index is [../AGENTS.md](../AGENTS.md).
|
|
5
|
+
data. This doc is the map: what shape each template solves and which one to start from. Read it
|
|
6
|
+
when starting any new screen, then read the chosen template's source before writing code.
|
|
7
|
+
Component contracts live in [the catalog](./catalog.md), the layout/color laws in [the
|
|
8
|
+
composition grammar](./composition.md); the package index is [../AGENTS.md](../AGENTS.md).
|
|
10
9
|
|
|
11
10
|
## How to use an example
|
|
12
11
|
|
|
@@ -18,13 +17,12 @@ the package index is [../AGENTS.md](../AGENTS.md).
|
|
|
18
17
|
resolution by design. Copy the bands you need into your app, keep the component imports,
|
|
19
18
|
and replace the mock constants with real reads.
|
|
20
19
|
- **Pick by the job, not the domain.** Templates are scenario-flavoured (a pick run, cash
|
|
21
|
-
application, a delivery week) but generic-purpose — they teach the shape. Your orders /
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
goods/services business uses (a service level, a destination, a delivery receipt) — never
|
|
26
|
-
|
|
27
|
-
values; the patterns are what you keep.
|
|
20
|
+
application, a delivery week) but generic-purpose — they teach the shape. Your orders / items
|
|
21
|
+
/ customers screen starts from the template whose *job* matches, whatever the domain. **Flavor
|
|
22
|
+
lives in VALUES, never in STRUCTURE**: section identities, teaching comments, and registry
|
|
23
|
+
keys name the generic PATTERN; mock nouns stay at the common-denominator level any
|
|
24
|
+
goods/services business uses (a service level, a destination, a delivery receipt) — never one
|
|
25
|
+
vertical's jargon (an FCL, an EIR, an HS code).
|
|
28
26
|
- The templates encode the composition grammar (band order, toolbar law, color discipline,
|
|
29
27
|
status weight) as working code — when a template and your instinct disagree, the template
|
|
30
28
|
wins.
|
|
@@ -64,10 +62,150 @@ its own navigated route only when one of these holds:
|
|
|
64
62
|
- **The work needs the canvas.** A docked comments gutter, a document read beside the fields,
|
|
65
63
|
an outline rail worth navigating — width the drawer's single column cannot give.
|
|
66
64
|
|
|
67
|
-
The volume of DATA is not the test.
|
|
68
|
-
the
|
|
69
|
-
a
|
|
70
|
-
|
|
65
|
+
The volume of DATA is not the test. What an app without a destination skips is the ROUTE, never
|
|
66
|
+
the template: the drawer renders `tpl_record` either way, so "no record page" must never become
|
|
67
|
+
a hand-built detail surface.
|
|
68
|
+
|
|
69
|
+
### The shape taxonomy — two families
|
|
70
|
+
|
|
71
|
+
Everything above picks a TEMPLATE. This picks a SHAPE, which is the decision that comes first —
|
|
72
|
+
and the one an agent otherwise re-derives, badly and differently each time, because the register
|
|
73
|
+
has ~13 templates and the record surface has one.
|
|
74
|
+
|
|
75
|
+
**An app screen is `[tabs] + list → record`, and that is nearly every app in every industry.**
|
|
76
|
+
The LIST is the only constant: glancing, filtering, narrowing, summarising. It is where the reader
|
|
77
|
+
spends the day and where the app is judged. The RECORD is the detail layer behind it, with two
|
|
78
|
+
destinations decided by who arrives and from where (§ Register + drawer, or a navigated page?).
|
|
79
|
+
TABS are optional, and reaching for them by reflex is how one set becomes piles nobody asked for.
|
|
80
|
+
|
|
81
|
+
**A dashboard is the other family and shares none of it** — no list, no drawer, no work.
|
|
82
|
+
Read-only, visual-first, immediate: it answers "how is the operation doing", never "which of these
|
|
83
|
+
do I act on".
|
|
84
|
+
|
|
85
|
+
**This is a map of known territory, not a fence.** Building a shape that is not on it is the
|
|
86
|
+
intended behaviour when the job calls for one — the line between a choice and a defect is drawn in
|
|
87
|
+
[composition.md](./composition.md) §"The form comes before the treatment", and it is drawn there
|
|
88
|
+
for this too.
|
|
89
|
+
|
|
90
|
+
### App-screen shapes
|
|
91
|
+
|
|
92
|
+
**What earns a tab.** A band is a TAB when it changes what the screen IS — its columns, the act on
|
|
93
|
+
its row, what its summary states. A band showing the same columns and the same act over fewer rows
|
|
94
|
+
is a LENS, and a lens is a `ChipGroup` / `Select` / `FilterChip` in the toolbar (→
|
|
95
|
+
[composition.md](./composition.md) §"One view-control vocabulary"). Tabs are the COARSE cut above
|
|
96
|
+
the filters, never a second copy of them; each states its own size and trouble as props (→
|
|
97
|
+
[composition.md](./composition.md) §"A partitioning control states its band's size as a PROP").
|
|
98
|
+
|
|
99
|
+
**The lifecycle desk** — one record type walking ordered stages: an order, a claim, an
|
|
100
|
+
application, a shipment, a repair. *"What is stuck, and what do I move next?"*
|
|
101
|
+
|
|
102
|
+
- **Tabs: the stages**, and here they earn it — each band carries its own act (confirm, dispatch,
|
|
103
|
+
close), its own summary, often its own columns. Roughly five; past that the tail is a lens.
|
|
104
|
+
- **Record: a `Drawer`.** The reader came from the list and returns to it, and the drawer's ◀ ▶
|
|
105
|
+
sequences the band they were reading.
|
|
106
|
+
- **Row vs record:** the row carries what the NEXT act needs — the goods, the money, the clock —
|
|
107
|
+
so the decision is made without opening anything. The record carries the history, the documents,
|
|
108
|
+
and the way back (§ Anything that ADVANCES a record must be able to walk it back).
|
|
109
|
+
- **Adapt:** rename the stages and the shape survives intact. An approval queue is its two-stage
|
|
110
|
+
degenerate case, not a shape of its own.
|
|
111
|
+
|
|
112
|
+
**The party register** — a person or an organisation you have a relationship with: a customer, a
|
|
113
|
+
supplier, a member, a tenant, a patient. *"Who is this, what is our history, is there a risk?"*
|
|
114
|
+
|
|
115
|
+
- **Tabs: usually NONE.** A party has no stages; its segments (region, source, risk) are filters,
|
|
116
|
+
and splitting people into permanent piles buries the ones the reader did not pick.
|
|
117
|
+
- **Record: a PAGE.** A party is arrived at COLD — from an order, a call, a notification — so it
|
|
118
|
+
must stand up without the list that would have opened it.
|
|
119
|
+
- **Row vs record:** identity (`Avatar` initials), ONE reachable contact, what the relationship is
|
|
120
|
+
WORTH (spend, count, last contact), and the single flag that costs money when missed. The record
|
|
121
|
+
holds the transaction history and the interaction log the user writes into.
|
|
122
|
+
- **Adapt:** the worth metrics and the risk flag are the industry-specific half; identity and
|
|
123
|
+
reach are not.
|
|
124
|
+
|
|
125
|
+
**The offering register** — what you sell: products, services, plans, a rate card. *"What do we
|
|
126
|
+
offer, at what price, can I sell it today?"*
|
|
127
|
+
|
|
128
|
+
- **Tabs: none, or the sales categories** the business already thinks in.
|
|
129
|
+
- **Record: a PAGE.** Media, a long description and variants need canvas, and an offering is
|
|
130
|
+
arrived at cold from every order line that names it.
|
|
131
|
+
- **Row vs record:** the row is IMAGE-led — a `FileThumbnail` in one square slot, then the name
|
|
132
|
+
with its code on the supporting line, the price, and the one availability figure. The record
|
|
133
|
+
holds the media, the description, and the price/cost history.
|
|
134
|
+
- **Adapt:** a services business swaps availability for a rate basis. The image column stays: it
|
|
135
|
+
is the row's own data doing the work
|
|
136
|
+
([composition.md](./composition.md) §"Character comes from the DATA").
|
|
137
|
+
|
|
138
|
+
**The transaction ledger** — dated movements in a book: bank lines, journal entries, stock
|
|
139
|
+
movements, metered usage. *"Does this period reconcile, and what is still unexplained?"*
|
|
140
|
+
|
|
141
|
+
- **Tabs: the BOOKS** — one per source or account. Not the periods: a period is a
|
|
142
|
+
`DateRangeFilterField` in the header band
|
|
143
|
+
([composition.md](./composition.md) §"Period filters for time-constrained data"), and tabbing it
|
|
144
|
+
strands every other period behind a tab bar that grows monthly.
|
|
145
|
+
- **Record: a `Drawer`.** A line has little of its own; what it needs is the document that proves
|
|
146
|
+
it and whatever it was matched against.
|
|
147
|
+
- **Row vs record:** date, counterparty, ONE signed amount (never paired in/out columns), its
|
|
148
|
+
classification, and whether its document is attached — absence is the information. Where the
|
|
149
|
+
BALANCE is the point the list is a `RunningLedger`, which derives every running figure itself
|
|
150
|
+
rather than trusting a supplied one.
|
|
151
|
+
- **Adapt:** the classification vocabulary. **The period's default decides whether the screen
|
|
152
|
+
opens rich or empty** — open on a settled period and surface the incomplete one as a call to
|
|
153
|
+
action; a screen that opens on its own unfinished work reads as broken.
|
|
154
|
+
|
|
155
|
+
**The monitored-asset set** — a population with a live state and a history: stock, machines,
|
|
156
|
+
vehicles, containers, sensors. *"What needs attention now, and is that number normal?"*
|
|
157
|
+
|
|
158
|
+
- **Tabs: by RESPONSIBILITY, not by state** — holding, receiving and counting are different jobs
|
|
159
|
+
with different acts. State is what the reader sorts and filters by, so it is a lens.
|
|
160
|
+
- **Record: a `Drawer`** onto the unit's recent movements: a `RunningLedger` where they are
|
|
161
|
+
arithmetic, a `Timeline` where they are events.
|
|
162
|
+
- **Row vs record:** the row carries the level, the THRESHOLD it is measured against and the
|
|
163
|
+
derived shortfall — a level with no threshold beside it cannot be acted on — plus its trajectory
|
|
164
|
+
as a `Sparkline`. Where the population runs to hundreds and the job is scanning for red, the
|
|
165
|
+
list becomes a `StatusGrid` (`tpl_tower`); where it runs to thousands, `tpl_stock`.
|
|
166
|
+
- **Adapt:** name the two levels the way the trade does. A gross level and an available level are
|
|
167
|
+
never synonyms, and the available one is what a person checks before committing.
|
|
168
|
+
|
|
169
|
+
**The reconciliation desk** — two sources that should agree: a statement against the ledger, a
|
|
170
|
+
count against the system, a remittance against the orders it covers. *"What does not match, and by
|
|
171
|
+
how much?"* The kit ships no example for this one.
|
|
172
|
+
|
|
173
|
+
- **Tabs: the RUNS** — each a dated session — or none where only one run is ever open.
|
|
174
|
+
- **Record: a `Drawer` on the PAIR**, not on either side: the two figures and everything that
|
|
175
|
+
explains the gap between them.
|
|
176
|
+
- **Row vs record:** a row is a PAIRING, so it carries both figures and the signed difference. A
|
|
177
|
+
committed difference takes a REASON — an unexplained variance cannot be audited, and that is
|
|
178
|
+
where breakage hides. The unmatched remainder is the `SummaryLine`, and the desk is not finished
|
|
179
|
+
until it is zero (`RemainderMeter` + `tpl_allocate` where one source splits across many
|
|
180
|
+
targets).
|
|
181
|
+
- **Adapt:** the two sources and the reason vocabulary; the arithmetic does not change.
|
|
182
|
+
|
|
183
|
+
### Dashboard shapes
|
|
184
|
+
|
|
185
|
+
Both are read-only and visual-first, and neither runs AI (→ `tpl_dashboard`). They differ by the
|
|
186
|
+
reader's CLOCK, and a screen that mixes them answers neither: a wallboard filtered by quarter is
|
|
187
|
+
not glanceable, and a quarterly review showing only now explains nothing.
|
|
188
|
+
|
|
189
|
+
**The live-status board** — the operation's units, right now. *"Is everything OK?"* Glanced at
|
|
190
|
+
repeatedly, often on a wall.
|
|
191
|
+
|
|
192
|
+
- **No period control** — "now" has no range.
|
|
193
|
+
- Leads with STATE (`KPIStrip` + `StatusGrid`), then an exceptions rail ordered by severity and
|
|
194
|
+
then by how long each has gone unresolved, then where it clusters (`Heatmap`). `tpl_tower` is
|
|
195
|
+
the worked example.
|
|
196
|
+
- Every cell and every exception row is a door into that unit's `Drawer`.
|
|
197
|
+
- **Adapt:** the state vocabulary — one `ColorName` per state, at every weight.
|
|
198
|
+
|
|
199
|
+
**The trend deep-dive** — a period of the business. *"How did it go, and why?"* Opened to decide
|
|
200
|
+
something.
|
|
201
|
+
|
|
202
|
+
- **The period control governs every figure on the screen** (`DateRangeFilterField`).
|
|
203
|
+
- Leads with the shape of CHANGE (`LineChart`, `BarChart`), then composition (`StackedBarChart`,
|
|
204
|
+
`Breakdown`, `PieChart`), then the BRIDGE that explains a movement (`WaterfallChart`, where the
|
|
205
|
+
step that costs the money is the red one rather than a footnote). `tpl_dashboard` for the shape;
|
|
206
|
+
`tpl_pivot` / `tpl_rollup` when the answer is chased down a dimension.
|
|
207
|
+
- **Adapt:** the metrics — and every one traces to a stored or derived value. A chart licenses no
|
|
208
|
+
number the data cannot produce.
|
|
71
209
|
|
|
72
210
|
## Composition rules — how a record surface is assembled
|
|
73
211
|
|
|
@@ -81,16 +219,16 @@ Laws for any screen where a record's data is edited and a primary action produce
|
|
|
81
219
|
2. **Static shape.** The page never changes shape from output-selection state. Conditional
|
|
82
220
|
rendering is DATA-driven only (a control appears because two stored values differ), never
|
|
83
221
|
selection-driven. Relevance is an advisory `Callout` with a jump-to-section action — warn,
|
|
84
|
-
never hide, never hard-block. One sanctioned disclosure: a fill-what's-missing `Inset`
|
|
85
|
-
|
|
86
|
-
|
|
222
|
+
never hide, never hard-block. One sanctioned disclosure: a fill-what's-missing `Inset` editor
|
|
223
|
+
CO-LOCATED under a just-checked item edits the record field in place and clears once
|
|
224
|
+
satisfied.
|
|
87
225
|
3. **Colocation & ownership.** Every output option (selections, print/export switches) lives in
|
|
88
226
|
the output section beside its action; a per-entity selection carries its owner's name in its
|
|
89
227
|
heading. Persisted record DATA never lives there — data belongs in data sections. Misfiled
|
|
90
|
-
data announces itself as callout special-cases
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
228
|
+
data announces itself as callout special-cases. (HOMING is what's forbidden, not repair: the
|
|
229
|
+
output section may host a co-located fill editor that saves onto the record field — same
|
|
230
|
+
storage, the field's home row shows the value — rendered only while a checked output still
|
|
231
|
+
lacks the field.)
|
|
94
232
|
4. **Resolved state.** Controls show the EFFECTIVE state, never raw storage: a derived/suggested
|
|
95
233
|
selection renders checked; the first manual change persists the full explicit set; a
|
|
96
234
|
reset-to-auto affordance appears only in explicit mode. What's shown = what happens.
|
|
@@ -146,35 +284,22 @@ name `Peek`s the dossier). Also exercises the wider chart family (`LineChart`, `
|
|
|
146
284
|
filter actually filters the KPI datasets. Start here for any "state of the operation"
|
|
147
285
|
landing screen.
|
|
148
286
|
|
|
149
|
-
**A dashboard is VISUAL first.**
|
|
150
|
-
|
|
151
|
-
dashboard built as rows of figures is a report wearing a dashboard's name: correct, and it makes
|
|
152
|
-
the reader do the comparing that the screen was supposed to do for them. Lead with the charts, let
|
|
153
|
-
the figures support them. `KPIStrip` earns its place as the headline row above the charts, never as
|
|
154
|
-
the whole screen.
|
|
287
|
+
**A dashboard is VISUAL first.** Lead with the charts, let the figures support them. `KPIStrip`
|
|
288
|
+
earns its place as the headline row above the charts, never as the whole screen.
|
|
155
289
|
|
|
156
290
|
The chart family is `BarChart`, `LineChart`, `PieChart`, `Funnel`, `Heatmap`, `Sparkline`,
|
|
157
291
|
`ProgressRing`, `ProgressBar`, `StackedProgressBar`, `StackedBarChart`, `WaterfallChart`,
|
|
158
|
-
`StepProgress` — and **that list is a starting inventory, not the boundary.** When the truth
|
|
159
|
-
a shape the kit has no component for — a dual-axis pairing spend against return, a bullet
|
|
160
|
-
target — **build it into `@lotics/ui`** rather than hand-rolling it inside one app
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
**No AI run belongs on a dashboard.** A dashboard's entire contract is that the answer is already
|
|
171
|
-
there — the reader opens it and knows. A panel that has to be asked, that streams, that costs
|
|
172
|
-
credits and can fail, breaks exactly the property the surface exists for, and it lands the reader
|
|
173
|
-
on a spinner in the one place they came for an instant read. If a figure is worth showing, compute
|
|
174
|
-
it and show it; if the question is open-ended enough to need a model, it belongs in chat, where
|
|
175
|
-
interrogating the data is the point and waiting is understood. The same test disqualifies anything
|
|
176
|
-
else that makes a dashboard *ask before it answers*: a required mode chooser, an empty state
|
|
177
|
-
awaiting a selection, a panel that loads only on click.
|
|
292
|
+
`StepProgress` — and **that list is a starting inventory, not the boundary.** When the truth
|
|
293
|
+
needs a shape the kit has no component for — a dual-axis pairing spend against return, a bullet
|
|
294
|
+
against target — **build it into `@lotics/ui`** rather than hand-rolling it inside one app (→
|
|
295
|
+
AGENTS.md, "components graduate into `src/` + an `examples/tpl_*` + a catalog entry").
|
|
296
|
+
|
|
297
|
+
Read the `dataviz` guidance before choosing a chart type or a palette.
|
|
298
|
+
|
|
299
|
+
**No AI run belongs on a dashboard.** If a figure is worth showing, compute it and show it; if
|
|
300
|
+
the question is open-ended enough to need a model, it belongs in chat. The same test
|
|
301
|
+
disqualifies anything else that makes a dashboard *ask before it answers*: a required mode
|
|
302
|
+
chooser, an empty state awaiting a selection, a panel that loads only on click.
|
|
178
303
|
|
|
179
304
|
### `tpl_stock` — the drill-down overview (large populations)
|
|
180
305
|
|
|
@@ -226,73 +351,61 @@ master-detail panel.
|
|
|
226
351
|
The one work-execution list shape; it subsumes approvals, dispatch, batch-building, and run
|
|
227
352
|
screens — register, per-row action, gated selection, and act-on-many in one.
|
|
228
353
|
|
|
229
|
-
**A register is a WORK surface, and that fixes its altitude.** The
|
|
230
|
-
|
|
231
|
-
is
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
answer different questions: a dashboard says how the operation is doing, a register is where the
|
|
236
|
-
work gets done. Putting a KPI band on a register pushes the rows below the fold to say something
|
|
237
|
-
the reader did not come for. (A per-row micro-visual — a `Sparkline`, a bar in a cell — is row
|
|
354
|
+
**A register is a WORK surface, and that fixes its altitude.** The aggregate belongs elsewhere:
|
|
355
|
+
**no `KPIStrip`, no chart panel, no report band above the rows.** The register's whole summary
|
|
356
|
+
is the one `SummaryLine`, and what it states is *this filtered view* — money at stake, how many
|
|
357
|
+
need a decision, the worst case in view — never a count of rows the reader can already see.
|
|
358
|
+
Aggregates are a DASHBOARD's job: a dashboard says how the operation is doing, a register is
|
|
359
|
+
where the work gets done. (A per-row micro-visual — a `Sparkline`, a bar in a cell — is row
|
|
238
360
|
DATA, not a panel, and is fine.)
|
|
239
361
|
|
|
240
362
|
The corollaries, each of which a register is routinely missing:
|
|
241
363
|
|
|
242
|
-
- **Filter on the axis its reader actually thinks in**, and make anything transactional
|
|
243
|
-
by DATE.
|
|
244
|
-
- **Sortable columns wherever the reader ranks** — dates and amounts above all.
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
entry beside a file-drop that proposes rows for review.
|
|
364
|
+
- **Filter on the axis its reader actually thinks in**, and make anything transactional
|
|
365
|
+
filterable by DATE.
|
|
366
|
+
- **Sortable columns wherever the reader ranks** — dates and amounts above all.
|
|
367
|
+
- **Put intake ON the register**, and one CTA per tab covers the tab's own shape. Where a file
|
|
368
|
+
is the real-world input, the CTA pair is manual entry beside a file-drop that proposes rows
|
|
369
|
+
for review.
|
|
249
370
|
- **If a row can carry a document, the register states whether it has one.** Absence is the
|
|
250
371
|
information — a badge that shows nothing for a row missing its invoice is exactly the column
|
|
251
372
|
that earns its width.
|
|
252
373
|
|
|
253
374
|
- **One toolbar row** — search + a status `Select` + facet `FilterChip`s LEFT, the New CTA
|
|
254
|
-
RIGHT; then a light `SummaryLine` of the filtered view BELOW it, above the rows
|
|
375
|
+
RIGHT; then a light `SummaryLine` of the filtered view BELOW it, above the rows.
|
|
255
376
|
- **A sortable `Table`** — rows separated by their own height and the hover wash, with the one
|
|
256
|
-
hairline under the column band. It sets no row height: the register's rhythm is the kit's
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
without chrome; the supporting phone additionally carries a **`CopyButton`** as the worked
|
|
277
|
-
example of a verb riding its value — the one value copied often enough to earn a control, and
|
|
278
|
-
in its own cell rather than the trailing gutter, which two values in a cell give nothing to
|
|
279
|
-
name. A per-row action `Button` (here Print) is the row's primary action in
|
|
280
|
-
the trailing column, `⋯` its overflow. A select-all band, footer totals + `Pagination`, and a
|
|
281
|
-
`FloatingActionBar` carrying the bulk action while rows are ticked.
|
|
377
|
+
hairline under the column band. It sets no row height: the register's rhythm is the kit's. →
|
|
378
|
+
[composition.md](./composition.md) §"The register's rhythm". The record's SUBJECT is the first
|
|
379
|
+
column — who or what the record is FOR. The KEY is how the SYSTEM refers to the record, so it
|
|
380
|
+
rides the identity cell's supporting line, muted, where the other supporting values live; it
|
|
381
|
+
does NOT take a column of its own, and it is never the row's name. It also does not SHRINK
|
|
382
|
+
there — a long phone number gives way first (it has a `CopyButton` handing over the full value
|
|
383
|
+
regardless). The record surface names itself the same way. Every row carries a leading
|
|
384
|
+
checkbox; a row that can't take the bulk action gets a **disabled** checkbox (the same gating
|
|
385
|
+
as any blocked line). A row's status is `Badge variant="dot"` — never the tonal `Badge`
|
|
386
|
+
(that's the drawer/header twin; → [composition.md](./composition.md) §"`Badge` is for STATUS
|
|
387
|
+
only") — and the SAME dot vocabulary drives the status facet. A cell that carries supporting
|
|
388
|
+
detail is a HIERARCHY, not two peers: the identity on top (`size="sm"`, `weight="medium"` when
|
|
389
|
+
it IS the row's name), its supporting value beneath as `size="xs" color="muted"`, `gap: 2` —
|
|
390
|
+
and a category / type / attribute goes on THAT line, never a second chip beside the name (→
|
|
391
|
+
[composition.md](./composition.md) §"`Badge` is for STATUS only"). Every value in a row is
|
|
392
|
+
SELECTABLE, so a reader copies any of them without chrome; the supporting phone additionally
|
|
393
|
+
carries a **`CopyButton`** as the worked example of a verb riding its value, in its own cell
|
|
394
|
+
rather than the trailing gutter. A per-row action `Button` (here Print) is the row's primary
|
|
395
|
+
action in the trailing column, `⋯` its overflow. A select-all band, footer totals +
|
|
396
|
+
`Pagination`, and a `FloatingActionBar` carrying the bulk action while rows are ticked.
|
|
282
397
|
- **A row press opens the PRODUCTION workspace `Drawer`** — which RENDERS
|
|
283
398
|
[`tpl_record`](../examples/tpl_record.tsx) in `chrome="drawer"`, not a copy of it. Same
|
|
284
399
|
sections, same order, same rules, because it is the same component: `<TplRecord
|
|
285
400
|
chrome="drawer" code={row.id} />`. A hand-built second record surface is exactly how the two
|
|
286
|
-
|
|
287
|
-
section here and a field there — so the register owns the REGISTER and nothing else.
|
|
401
|
+
drift apart, so the register owns the REGISTER and nothing else.
|
|
288
402
|
- What the chrome flag changes is only what a nested surface must not do: the back button is
|
|
289
|
-
left to the Drawer's header
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
never behind a control.
|
|
403
|
+
left to the Drawer's header. Everything else follows from width — the layout measures its
|
|
404
|
+
CONTAINER via `onLayout`, never the window, so the outline rail and the mirrored gutters
|
|
405
|
+
collapse on their own, and the section picker is a popover ANCHORED to its bar button rather
|
|
406
|
+
than a full-page `Modal` that would escape the drawer. **The discussion follows the same
|
|
407
|
+
measure**: docked beside the record where there is room for its column, and a SECTION under
|
|
408
|
+
General where there is not — which a drawer always is. It is never behind a control.
|
|
296
409
|
- **Linked records** still push an editable screen for that record inside the drawer via the
|
|
297
410
|
hosted `ScreenRouter` — while `canGoBack` the drawer header swaps to a back button + the
|
|
298
411
|
pushed id and the ◀ ▶ sequencer hides; back pops with scroll preserved.
|
|
@@ -324,12 +437,9 @@ The corollaries, each of which a register is routinely missing:
|
|
|
324
437
|
they will be stored, each editable in place, a `DiffMark` per card, Keep/Drop via
|
|
325
438
|
`useChangeSet` (`initial: "accepted"` — the operator drops the exceptions rather than
|
|
326
439
|
approving six identical records) — and ONE commit named for its outcome creates them. The
|
|
327
|
-
manual form beside it skips all of that and saves direct
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
have been caught, and by then it is in the data. The receipt still follows the write — it
|
|
331
|
-
states the outcome, ROUTES, and carries the deterministic checks (a checksum, a count
|
|
332
|
-
reconciliation) that a person cannot eyeball anyway.
|
|
440
|
+
manual form beside it skips all of that and saves direct. Only the DOCUMENT path needs the
|
|
441
|
+
gate. The receipt still follows the write — it states the outcome, ROUTES, and carries the
|
|
442
|
+
deterministic checks (a checksum, a count reconciliation).
|
|
333
443
|
|
|
334
444
|
### `tpl_pick` — the guided queue
|
|
335
445
|
|
|
@@ -351,17 +461,17 @@ Oldest-first auto-allocates; each `AllocationRow` can be filled or typed.
|
|
|
351
461
|
|
|
352
462
|
### `tpl_record` — THE record surface
|
|
353
463
|
|
|
354
|
-
The record page — **the surface IS the editor**; every field refines in place. No breadcrumb
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
464
|
+
The record page — **the surface IS the editor**; every field refines in place. No breadcrumb and
|
|
465
|
+
no create CTA (creation belongs to the REGISTER — the list owns "new"; back lives in the left
|
|
466
|
+
panel as the bare circular `BackButton`). First paint is a `Skeleton` MIRRORING the layout,
|
|
467
|
+
never a spinner. It absorbs the order-form, inline-record, intake, settings, billing, and
|
|
468
|
+
quick-capture templates. Top → bottom:
|
|
359
469
|
|
|
360
470
|
- A MINIMAL header: `RecordSummary` (title, subtitle ONLY — no stage chip, no metric; Progress
|
|
361
471
|
carries the desk, money lives in Billing), then the record's
|
|
362
472
|
ATTENTION state as a co-located `Callout` (e.g. overdue — record-scoped state reads here;
|
|
363
|
-
field-scoped state stays on its field), then a quiet `SummaryLine` (desk, documents
|
|
364
|
-
|
|
473
|
+
field-scoped state stays on its field), then a quiet `SummaryLine` (desk, documents, to
|
|
474
|
+
collect).
|
|
365
475
|
- **GENERAL — the MAIN details, first in THIS record's rail** because its work starts at the
|
|
366
476
|
details; rule 1 orders sections by work flow, so a surface whose work starts at a DOCUMENT
|
|
367
477
|
(an invoice desk, a claims desk) leads with Documents and General follows. Every later
|
|
@@ -381,23 +491,20 @@ billing, and quick-capture templates. Top → bottom:
|
|
|
381
491
|
field renders only while its parent value makes it real (no disabled ghost rows).
|
|
382
492
|
Radio/checkbox/search are PERSISTENT controls — the control is the best display;
|
|
383
493
|
prose-shaped values stay inline editors.
|
|
384
|
-
- **Comments — a docked PANEL, not a section.** It is about the whole record, so it has no
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
`
|
|
391
|
-
- **Progress** — directly UNDER General
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
than editing it here) and the single act that leaves the desk (`ChecklistActions` → the handoff
|
|
399
|
-
dialog). `meta` is prose
|
|
400
|
-
the reader can't set, never the date that the stage's own field already shows. See § The
|
|
494
|
+
- **Comments — a docked PANEL, not a section.** It is about the whole record, so it has no rail
|
|
495
|
+
entry: it pins in the page's RIGHT gutter (the column the outline rail's balance already
|
|
496
|
+
reserved), scrolling inside itself so a long thread never lengthens the record, and collapses
|
|
497
|
+
onto the pinned bar's right on narrow. `CommentList` (author-only edit/delete; attachments as
|
|
498
|
+
a pressable `FileGrid` → gallery preview; a file-capable edit form injected via
|
|
499
|
+
`renderEditForm`) + THE kit `Composer` (attach via `actionsButton`, staged files in its
|
|
500
|
+
`files` slot) — never a bespoke comment box.
|
|
501
|
+
- **Progress** — directly UNDER General. The desk handoff as the ordered positions it is: a
|
|
502
|
+
`Checklist connected` where the desks are `ChecklistGroup`s (Sales → Operations → Accounting)
|
|
503
|
+
and the milestones they produce are the rows. The row that OWES the work carries all three:
|
|
504
|
+
how long it has been owed (`meta`), its condition (`ChecklistNote`, e.g. past the due date —
|
|
505
|
+
with an `action` jumping to the section that owns a missing field rather than editing it here)
|
|
506
|
+
and the single act that leaves the desk (`ChecklistActions` → the handoff dialog). `meta` is
|
|
507
|
+
prose the reader can't set, never the date that the stage's own field already shows. See § The
|
|
401
508
|
handoff shape for the rules; for a record that genuinely needs a CHECKLIST, the task-list
|
|
402
509
|
grammar is worked in `tpl_item_list`.
|
|
403
510
|
- **Documents** — the Agents "Document desk" pattern (this template is its worked example —
|
|
@@ -419,82 +526,69 @@ billing, and quick-capture templates. Top → bottom:
|
|
|
419
526
|
own (see [composition.md's reference law](./composition.md)). It wears the inline editor's
|
|
420
527
|
resting surface with an `InlineButton` **Open** inside it; pressing the surface PEEKS the
|
|
421
528
|
facts, and the peek's footer carries detach LEFT / Open RIGHT — so the row itself shows no
|
|
422
|
-
destructive verb at all. The tax-ID problem that gates invoicing is the row's `warning`, not
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
row's `trailing`, and not conditional, which would resize the field as you type.
|
|
529
|
+
destructive verb at all. The tax-ID problem that gates invoicing is the row's `warning`, not a
|
|
530
|
+
floating `Callout`. Transport's three party rows are the same shape repeated: N references as
|
|
531
|
+
field rows scan as one list, where N cards would be a wall. Empty state = the find-or-create
|
|
532
|
+
`Combobox` in the same value slot, custom row → the create `Dialog` (tax ID + Fetch). That
|
|
533
|
+
dialog is built from **`FormField` / `FormTextInput`, never the record surface's `Inline*`
|
|
534
|
+
editors** — see [data_entry.md](./data_entry.md) §"A dialog is a draft". **Reference (the
|
|
535
|
+
buyer's own) is the `actions` worked example**: its `Copy` is an `InlineButton` ON the field,
|
|
536
|
+
passed unconditionally and `disabled` while empty — not in the row's `trailing`, and not
|
|
537
|
+
conditional, which would resize the field as you type.
|
|
432
538
|
- **Fees — the DETAILED money ledger** (both directions), distinct from Billing's invoice
|
|
433
|
-
documents: a `SummaryLine` (collected, to collect, to pay, warning-toned when overdue) →
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
539
|
+
documents: a `SummaryLine` (collected, to collect, to pay, warning-toned when overdue) → a
|
|
540
|
+
`priority`-annotated register `Table` (Fee, Type, Party, Amount, Status — status in plain ink,
|
|
541
|
+
danger only when overdue) → EVERY row opens a right-docked entity `Drawer` (◀ ▶ + position
|
|
542
|
+
stepping) with all fields inline-editable and a confirmed Remove in the `DrawerFooter`. "Add
|
|
543
|
+
fee" rides the section heading's right edge and is create-then-refine: a blank fee opens
|
|
544
|
+
straight in the drawer. The empty state carries no button of its own, because that one is
|
|
545
|
+
already on screen.
|
|
440
546
|
- **Billing — INLINE bands, closed by a STATEMENT.** Each invoice is a `Subsection` costing ONE
|
|
441
547
|
line of chrome (its name, its total, its issued ref) over its charge rows in a `DetailTable`;
|
|
442
548
|
the charge editors carry ghost list prices with a one-tap "Standard …" fill and a "How paid…"
|
|
443
|
-
select.
|
|
444
|
-
|
|
445
|
-
then hid four charge lines behind three expansions, which trades a real fault for a worse one
|
|
446
|
-
— you can no longer see what you are paying for. So the lines all stay and the chrome
|
|
447
|
-
shrank, and a per-invoice problem rides the CHARGE ROW that has it (`warning` on the line
|
|
448
|
-
missing its method), which is smaller than a callout and more precise: it names the offender
|
|
449
|
-
instead of counting offenders.
|
|
549
|
+
select. The lines all stay, and a per-invoice problem rides the CHARGE ROW that has it
|
|
550
|
+
(`warning` on the line missing its method), which is smaller than a callout and more precise.
|
|
450
551
|
The section then CLOSES with a `Ledger` — the money grammar's worked example, and the answer
|
|
451
|
-
the bands cannot give
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
one verifies nothing.
|
|
462
|
-
THE ACTION-GATING LAW is still the worked example: a not-ready Issue is DISABLED and the
|
|
552
|
+
the bands cannot give. THREE sides — `Invoiced`, a one-row `Adjustments` credit, and
|
|
553
|
+
`Received` with its rows negative and `success`-toned — so the total states arithmetic rather
|
|
554
|
+
than a difference, over a `LedgerTotal` carrying `zeroLabel="Paid in full"`. Every row state
|
|
555
|
+
is on screen: a `peek` door on the multi-line invoice (its lookup link INSIDE the popover,
|
|
556
|
+
since a button cannot hold a button), a flat `reference` on the single-line one, `meta`
|
|
557
|
+
qualifying how money came back. `Received` is DERIVED from the charges that carry a payment
|
|
558
|
+
method — not a second flag to keep in step — and the refundable deposit stays OUT, because a
|
|
559
|
+
ledger earns trust by summing exactly what its label claims. The fixture is deliberately
|
|
560
|
+
uneven (an unpriced charge, one issued invoice, a one-row group) because a tidy one verifies
|
|
561
|
+
nothing. THE ACTION-GATING LAW is the worked example: a not-ready Issue is DISABLED and the
|
|
463
562
|
reason is a co-located `Callout` at the gate's SCOPE, once — an invoice's own inconsistency (a
|
|
464
563
|
charged line with no method) on that line, a broken record premise (no customer / invalid tax
|
|
465
564
|
ID) at the section top; self-evident empties stay silent, and NEVER prose beside a CTA.
|
|
466
565
|
Issuing gates on record premises only — never silently on stage. It is also the worked example
|
|
467
|
-
of **an irreversible action taking an ID, not a captured object** — the confirm re-resolves
|
|
468
|
-
invoice from its key, so the quoted total is the one that will be billed even when the
|
|
469
|
-
was held for a charge cell's write (data_entry.md § Inline edit).
|
|
470
|
-
- **Document set — the OUTPUT desk, the last WORK section** (the composition rules' output
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
consumers ("printed on the delivery note"). The picker rows ride a
|
|
566
|
+
of **an irreversible action taking an ID, not a captured object** — the confirm re-resolves
|
|
567
|
+
the invoice from its key, so the quoted total is the one that will be billed even when the
|
|
568
|
+
press was held for a charge cell's write (data_entry.md § Inline edit).
|
|
569
|
+
- **Document set — the OUTPUT desk, the last WORK section** (the composition rules' output law
|
|
570
|
+
worked on the record surface: the top is intake, the bottom produces on demand). Forms group
|
|
571
|
+
PER PARTY on the `SubsectionStack` beat — each party a `SubsectionHeading` + plain
|
|
572
|
+
`CheckboxInput` rows, the common ones PRE-CHECKED, the long tail folded behind a muted "Show
|
|
573
|
+
all forms (N hidden)" toggle. Between the pickers and the CTA sit the OUTPUT CONFIG fields
|
|
574
|
+
(values printed on the forms — e.g. issuing office, prefill-signing-date boolean), each with
|
|
575
|
+
the input its shape wants. "Create document set" (primary — create IS this scope's job; an
|
|
576
|
+
empty pick self-evidently disables it, no callout) is DETERMINISTIC template fill, NOT AI —
|
|
577
|
+
the CTA carries a brief `loading` state and the whole set appears at once (`AgentRun` is for
|
|
578
|
+
AI work only; dressing deterministic work as an agent run is theater). The produced files list
|
|
579
|
+
under the action row as `FileRow`s (press → preview; ⋯ → Download / Remove) with "Download
|
|
580
|
+
all" BELOW the files it acts on. Regenerating REPLACES the set — derived paperwork, never
|
|
581
|
+
intake; the desk register above keeps what arrived. **READINESS**: a form declares the RECORD
|
|
582
|
+
FIELDS it reads (`needs`); the fields' HOME stays their DATA section (the colocation law) with
|
|
583
|
+
descriptions naming their consumers ("printed on the delivery note"). The picker rows ride a
|
|
486
584
|
`Checklist connected={false} mark="select"` — the SAME compound as Progress, TWO axes apart.
|
|
487
|
-
Unconnected because these forms are a SET the user picks from rather than a sequence
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
**It does not collect them.** The row briefly opened a form of its own here, which made every
|
|
495
|
-
row two things at once (a status and a form) with nothing to say which one the reader was in,
|
|
496
|
-
and put an editor beside a field's real home where its description and its neighbours live. A
|
|
497
|
-
checklist REPORTS; the section that owns a value COLLECTS it. A ready form stays SILENT.
|
|
585
|
+
Unconnected because these forms are a SET the user picks from rather than a sequence; `select`
|
|
586
|
+
because picking a form is a CHOICE. The row NAMES what is missing in a `ChecklistNote` —
|
|
587
|
+
"Needs: …" the actual field labels, muted at rest so gaps show WITHOUT checking, warning once
|
|
588
|
+
checked because it now blocks — and points at the section that owns those fields.
|
|
589
|
+
|
|
590
|
+
**It does not collect them.** A checklist REPORTS; the section that owns a value COLLECTS it.
|
|
591
|
+
A ready form stays SILENT.
|
|
498
592
|
|
|
499
593
|
Missing fields are the gating law's DEGRADED-BUT-VALID case (they print blank, filled by hand later): Create stays ENABLED, a
|
|
500
594
|
co-located warning `Callout` states the consequence, and the press CONFIRMS via `Alert`.
|
|
@@ -506,115 +600,91 @@ billing, and quick-capture templates. Top → bottom:
|
|
|
506
600
|
a label-left capture form (auto-stamped date/receipt-no, record-prefilled fields, the
|
|
507
601
|
right input each) whose CTA and produced `FileRow` ride an empty-label `DetailRow` — the
|
|
508
602
|
form-action alignment law (data_entry.md), worked.
|
|
509
|
-
- **Activity — what was SAID with the other party**, newest first, directly under Progress
|
|
510
|
-
|
|
511
|
-
between us to get it there. It is NOT an audit trail and NOT the handoff — see the next bullet
|
|
512
|
-
for the line, which is what keeps this section from becoming the change log Progress refuses.
|
|
603
|
+
- **Activity — what was SAID with the other party**, newest first, directly under Progress. It
|
|
604
|
+
is NOT an audit trail and NOT the handoff — see the next bullet for the line.
|
|
513
605
|
|
|
514
606
|
**The GIST is what the feed is scanned by** — what came of the exchange, in the reader's own
|
|
515
|
-
words. Building it the other way round
|
|
516
|
-
|
|
517
|
-
chevron, and a column of near-identical labels defeats the scan the feed exists for.
|
|
607
|
+
words. Building it the other way round makes the feed unreadable: every entry renders as a
|
|
608
|
+
pair of enum values ("Outbound Meeting") and the substance sits behind a chevron.
|
|
518
609
|
|
|
519
610
|
**An entry is a COMMENT, not a `Timeline` row.** `Timeline` renders its label inside the row's
|
|
520
611
|
press target, which is right for derived read-only text ("Stage changed to Won") and wrong for
|
|
521
|
-
the one thing on an entry a person WROTE — text inside a button cannot be edited where it
|
|
522
|
-
So the gist is a RESIDENT field and the derived
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
own ground, and the eye lands on the substance even though it reads second.
|
|
612
|
+
the one thing on an entry a person WROTE — text inside a button cannot be edited where it
|
|
613
|
+
sits. So the gist is a RESIDENT field and the derived facts sit above it on a muted `xs`
|
|
614
|
+
**byline** — which way it went, over what, when. Order does not decide the scan; weight does:
|
|
615
|
+
the byline is small and grey, the prose is `sm` on the page's own ground.
|
|
526
616
|
|
|
527
617
|
**ONE anatomy, a body that VARIES.** An entry can be a call, a recorded demo, an email, a
|
|
528
618
|
message or a note typed between meetings — and those differ in what they CARRY, not in what
|
|
529
619
|
they are: all of them answer what-came-of-it, which way, over what, and when. So the four
|
|
530
|
-
invariants are the row, and the body is a SET OF BLOCKS rather than a shape per kind.
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
legal**, because what an entry carries is independent of what it is: a call may arrive as a
|
|
534
|
-
recording alone, gain a transcript minutes later and a summary after that.
|
|
620
|
+
invariants are the row, and the body is a SET OF BLOCKS rather than a shape per kind. **Every
|
|
621
|
+
block is optional and any combination is legal**: a call may arrive as a recording alone, gain
|
|
622
|
+
a transcript minutes later and a summary after that.
|
|
535
623
|
|
|
536
624
|
The blocks, in the order they render (`ActivityBody` in `tpl_record` is the worked one):
|
|
537
625
|
|
|
538
626
|
| Block | What it is | How it renders |
|
|
539
627
|
|---|---|---|
|
|
540
|
-
| Participants | ONLY what cannot be derived — a third party who joined, an email's From/To
|
|
628
|
+
| Participants | ONLY what cannot be derived — a third party who joined, an email's From/To | muted line |
|
|
541
629
|
| Subject | the one thing an email has that nothing else does | `sm` medium |
|
|
542
630
|
| Media | the exchange itself, recorded | `MediaPlayer` — **give it a box**, it fills its parent and collapses to nothing without one |
|
|
543
631
|
| Transcript | VERBATIM and long; the source a summary was made from | a muted underlined `TextLink` in your own `Pressable`, revealing PLAIN text — see the disclosure exception in composition.md §"Commit & feedback surfaces" |
|
|
544
|
-
| Prose | what the COUNTERPARTY sent — an email body. Not a second field of ours:
|
|
545
|
-
| AI prose | what a MODEL wrote | ONE short label INSIDE the `Inset` ("AI summary")
|
|
632
|
+
| Prose | what the COUNTERPARTY sent — an email body. Not a second field of ours: the gist auto-grows, so it already carries whatever length someone wants | `Markdown variant="embedded"` on the page's own ground |
|
|
633
|
+
| AI prose | what a MODEL wrote | ONE short label INSIDE the `Inset` ("AI summary"), never floating above it. Name the evidence only where it is NOT on screen. Let the label replace the block's own `## Heading` rather than sit above it |
|
|
546
634
|
| Files | what came with it — EVIDENCE, so it is writable | one `InlineFiles` (`onRemove`, no `onAdd`), mixed pdf/image, previewing in place |
|
|
547
635
|
| Source | where it happened, if openable | `Link` |
|
|
548
636
|
|
|
549
|
-
**An entry's files are WRITABLE, on both paths.**
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
so the
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
conversation belongs to that conversation; routing it to the record's Documents intake instead
|
|
569
|
-
leaves the reader to relate the two by hand. Nesting is sound rather than lucky — the component
|
|
570
|
-
stops propagation on every drag/drop event, so the innermost region wins, the outer one neither
|
|
571
|
-
fires nor lights, and the file is added once. Pass a plain child so the target paints its own
|
|
572
|
-
drag affordance: with two live regions the drag has to say WHICH one it is about to land in.
|
|
573
|
-
Both paths — the menu verb and the drop — call ONE append function, because two implementations
|
|
574
|
-
of "add to this list" is how one of them quietly becomes a replace. A section earns its own
|
|
575
|
-
target when the drop MEANS something different there; "the same thing, closer" does not.
|
|
637
|
+
**An entry's files are WRITABLE, on both paths.** Two places, because there are two moments:
|
|
638
|
+
the section's `Composer` stages them on the entry being logged (`actionsButton` + the `files`
|
|
639
|
+
slot + `sendDisabled`, the three kit slots the comment thread already used), and the entry's
|
|
640
|
+
own menu attaches to one that already exists.
|
|
641
|
+
|
|
642
|
+
**`sendDisabled` is not optional once a composer can attach.** The default blocks send on
|
|
643
|
+
empty text, which is wrong the moment an attachment is the whole point. Pass `false` while
|
|
644
|
+
files are staged, `undefined` otherwise so the default returns.
|
|
645
|
+
|
|
646
|
+
**Attaching lives on the MENU, not on the file list.** The list's own CTA cannot work on an
|
|
647
|
+
entry with nothing attached, so a menu verb that works in every state is the single path, and
|
|
648
|
+
the list takes `onRemove` alone. Removing CONFIRMS.
|
|
649
|
+
|
|
650
|
+
**An entry is its own `FileDropTarget`**, nested inside the record-level one. Nesting is sound
|
|
651
|
+
rather than lucky — the component stops propagation on every drag/drop event, so the innermost
|
|
652
|
+
region wins, the outer one neither fires nor lights, and the file is added once. Pass a plain
|
|
653
|
+
child so the target paints its own drag affordance. Both paths — the menu verb and the drop —
|
|
654
|
+
call ONE append function. A section earns its own target when the drop MEANS something
|
|
655
|
+
different there; "the same thing, closer" does not.
|
|
576
656
|
|
|
577
657
|
**A MESSAGE's attachments are tiles — staged or filed.** The composer's `files` slot takes
|
|
578
658
|
`<FileGrid files={…} itemSize={THUMBNAIL_SIZE} onDisplayRemove={…}>` — the exact component and
|
|
579
|
-
size the product's own chat composer passes
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
`
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
gallery, and press-to-open.
|
|
599
|
-
|
|
600
|
-
**Give it no children unless a toolbar earns its place.** `FilesEditor` renders its bar from its
|
|
601
|
-
children and nothing without them, and select mode is reachable ONLY from that bar — so omitting
|
|
602
|
-
it removes the mode rather than stranding it. A feed of eight entries does not want eight
|
|
603
|
-
toolbars.
|
|
659
|
+
size the product's own chat composer passes. A staged file is not yet a document in a list:
|
|
660
|
+
the control that unstages it belongs on the tile.
|
|
661
|
+
|
|
662
|
+
Filing it does not turn it into a row — it turns it into a `FilesEditor`. Tiles either way,
|
|
663
|
+
the same as `chat_user_message` renders a posted message's attachments and the same as this
|
|
664
|
+
record's Files section, because evidence is recognised by LOOKING. The rows belong to a
|
|
665
|
+
different object: `InlineFiles` is a labelled FIELD in a `DetailRow` ("Signed contract",
|
|
666
|
+
"Supplier original"), where the document set has a name, a cardinality and a `blockedReason`.
|
|
667
|
+
|
|
668
|
+
**A FILED attachment must be hard to lose; a STAGED one is not yet anything.** A bare
|
|
669
|
+
`FileGrid` draws a ✕ on every tile, one click from gone — right in a composer, wrong on a
|
|
670
|
+
record's history. `FilesEditor`'s default view never draws one at all: removal means opening
|
|
671
|
+
the file and doing it from the gallery, and it still asks, naming the file. Reach for the
|
|
672
|
+
editor and three things you would otherwise hand-roll come with it — the confirmation, the
|
|
673
|
+
full-screen gallery, and press-to-open.
|
|
674
|
+
|
|
675
|
+
**Give it no children unless a toolbar earns its place.** `FilesEditor` renders its bar from
|
|
676
|
+
its children and nothing without them, and select mode is reachable ONLY from that bar — so
|
|
677
|
+
omitting it removes the mode rather than stranding it.
|
|
604
678
|
|
|
605
679
|
**Never write a tile size as a number.** The kit exports the two that exist — `THUMBNAIL_SIZE`
|
|
606
|
-
(a composer attachment, a file tile you are meant to recognise) and `COMPACT_THUMBNAIL_SIZE`
|
|
607
|
-
badge on a list row) — and the product uses nothing else.
|
|
608
|
-
|
|
609
|
-
the complaint arrives as "why is it smaller here", which is not a question a constant can raise.
|
|
610
|
-
For a grid that should FILL its container instead, omit `itemSize` and let `minItemWidth` derive
|
|
611
|
-
the columns — that is a different decision from the tile's size, not a smaller version of it.
|
|
680
|
+
(a composer attachment, a file tile you are meant to recognise) and `COMPACT_THUMBNAIL_SIZE`
|
|
681
|
+
(the badge on a list row) — and the product uses nothing else. For a grid that should FILL its
|
|
682
|
+
container instead, omit `itemSize` and let `minItemWidth` derive the columns.
|
|
612
683
|
|
|
613
684
|
**Copy the component, not the layout mode.** Chat's POSTED attachments use `columns={3}
|
|
614
|
-
partialRowAlign="end"`; the `partialRowAlign` is there because a chat bubble is right-aligned,
|
|
615
|
-
a short last row hugging the end is what lines up. A comment on a record is a left-aligned
|
|
616
|
-
and inherits neither.
|
|
617
|
-
transplanting a decision that belongs to a bubble.
|
|
685
|
+
partialRowAlign="end"`; the `partialRowAlign` is there because a chat bubble is right-aligned,
|
|
686
|
+
so a short last row hugging the end is what lines up. A comment on a record is a left-aligned
|
|
687
|
+
block and inherits neither.
|
|
618
688
|
|
|
619
689
|
**One list, not the documents/images split.** That split — documents as rows because a name
|
|
620
690
|
identifies them, images as tiles because content does — holds where files are the SUBJECT (a
|
|
@@ -622,53 +692,41 @@ billing, and quick-capture templates. Top → bottom:
|
|
|
622
692
|
identified by what they are, which is the shape the fee ledger's "Supplier original" already
|
|
623
693
|
uses: one `InlineFiles` taking `application/pdf,image/*`.
|
|
624
694
|
|
|
625
|
-
**Delete is the only verb on the entry itself, and it sits in an `ActionMenu` at the TOP
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
SIBLING of the content, so nothing nests a button inside another.
|
|
695
|
+
**Delete is the only verb on the entry itself, and it sits in an `ActionMenu` at the TOP
|
|
696
|
+
RIGHT** — where every comment surface puts it, beside the byline rather than in a footer a
|
|
697
|
+
scroll below the sentence it acts on. The menu is a SIBLING of the content, so nothing nests a
|
|
698
|
+
button inside another.
|
|
630
699
|
|
|
631
|
-
Delete belongs there DESPITE the feed being a record
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
garbage it also asks you to trust. Name what leaves with the entry in the confirmation: a
|
|
635
|
-
count somewhere else is the part nobody expects.
|
|
700
|
+
Delete belongs there DESPITE the feed being a record: an extraction filing against the wrong
|
|
701
|
+
contact and an automation double-filing a recording are not history. Name what leaves with the
|
|
702
|
+
entry in the confirmation.
|
|
636
703
|
|
|
637
704
|
Only the GIST is editable — everything else is derived, verbatim, an artifact or a model's
|
|
638
|
-
output, and a counterparty's own email body is a record of what THEY said
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
**The
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
call, the feed folds its own tail, and the byline and gist stay at the top of every entry, so the
|
|
657
|
-
scan survives a tall one.
|
|
658
|
-
|
|
659
|
-
**The TRANSCRIPT keeps its disclosure, and it is the only one.** It is the one block that is
|
|
660
|
-
unbounded by nature — a machine's verbatim record of half an hour — and the one nobody reads
|
|
661
|
-
straight through: you open it to check a claim the summary above made. That is what a disclosure
|
|
662
|
-
is for. An email BODY is prose of unknown length too and still shows, because it is human-written,
|
|
663
|
-
ordinarily short, and it IS the message rather than the source behind one.
|
|
705
|
+
output, and a counterparty's own email body is a record of what THEY said. It is resident,
|
|
706
|
+
with no mode and no verb, and it follows the value: `bare` once written up, `framed` while
|
|
707
|
+
empty. Both halves are in data_entry.md §"A value rendered TWICE is the bug".
|
|
708
|
+
|
|
709
|
+
**NO AUTHOR on an entry.** A byline reading "Recording bot" names the plumbing and fabricates
|
|
710
|
+
an author for somebody else's conversation. Showing an author on the entries that happen to
|
|
711
|
+
have one is worse than showing none. The record has an Owner; a customer's history does not
|
|
712
|
+
restate it once per entry. What a machine actually TOUCHED stays visible where it changes how
|
|
713
|
+
you read something: the `Inset` names an "AI summary", a transcript is labelled verbatim.
|
|
714
|
+
|
|
715
|
+
**The blocks SHOW; they are not folded behind a disclosure.** A video is the honest
|
|
716
|
+
counter-case at 358px and it still shows — the feed folds its own tail, and the byline and
|
|
717
|
+
gist stay at the top of every entry, so the scan survives a tall one.
|
|
718
|
+
|
|
719
|
+
**The TRANSCRIPT keeps its disclosure, and it is the only one.** It is the one block unbounded
|
|
720
|
+
by nature — a machine's verbatim record of half an hour — and the one nobody reads straight
|
|
721
|
+
through: you open it to check a claim the summary above made. An email BODY is prose of
|
|
722
|
+
unknown length too and still shows: it IS the message rather than the source behind one.
|
|
664
723
|
|
|
665
724
|
**A block earns its place by carrying what nothing else on the row does.** The failure is not
|
|
666
725
|
a missing block, it is three blocks saying one thing: a participants line, a rep's gist and a
|
|
667
|
-
machine summary all reporting
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
directly above it.
|
|
726
|
+
machine summary all reporting the same absence. An entry with no note is the normal case
|
|
727
|
+
rather than an incomplete one. Cut on sight: a label above a block that already labels itself,
|
|
728
|
+
a fact the record or the row already states, and any block whose content the reader can see
|
|
729
|
+
rendered directly above it.
|
|
672
730
|
|
|
673
731
|
**Two of those rows are the whole point of the section on a modern record.** A feed fills from
|
|
674
732
|
more than one direction — a person, a chat agent, an extraction off an upload, an automation
|
|
@@ -678,50 +736,43 @@ billing, and quick-capture templates. Top → bottom:
|
|
|
678
736
|
plus a recessed ground — never by a weight nudge. The reasoning and the failure modes are in
|
|
679
737
|
ai_patterns.md §After the run.
|
|
680
738
|
|
|
681
|
-
**The transcript folds, and it reveals in place
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
compare. Reveal it whole rather than into a scroll box; a scroller inside a scrolling drawer
|
|
685
|
-
traps the wheel, and a reader who pressed it asked for the length.
|
|
739
|
+
**The transcript folds, and it reveals in place** — a dialog is wrong: it takes the claim off
|
|
740
|
+
screen at the moment they want to compare. Reveal it whole rather than into a scroll box; a
|
|
741
|
+
scroller inside a scrolling drawer traps the wheel.
|
|
686
742
|
|
|
687
743
|
**The toggle is underlined text, and that is a deliberate exception** (the in-prose disclosure,
|
|
688
744
|
composition.md §"Commit & feedback surfaces"): a muted underlined `TextLink` in your own
|
|
689
745
|
`Pressable` — visible at rest, on the column's own left edge, and carrying no more weight than
|
|
690
746
|
the words. The ink keeps underline honest: blue leaves the surface, muted stays on it.
|
|
691
747
|
|
|
692
|
-
**Reveal it PLAIN, with no tinted well
|
|
693
|
-
is
|
|
694
|
-
verbatim record and a machine's reading of it. Keep the `Inset` for the machine's.
|
|
748
|
+
**Reveal it PLAIN, with no tinted well** — the toggle directly above already says what the
|
|
749
|
+
text is. Keep the `Inset` for the machine's.
|
|
695
750
|
|
|
696
751
|
**The capture sits AT the feed**, as a `Composer` — compact until typed into. Logging an entry
|
|
697
752
|
is this section's input, not a distinct AREA of the record, and a section of its own costs a
|
|
698
|
-
rail entry
|
|
753
|
+
rail entry.
|
|
699
754
|
|
|
700
|
-
**The tail FOLDS, it does not page.**
|
|
701
|
-
|
|
702
|
-
few with a toggle naming how many are behind it — a register pages, a record's feed folds.
|
|
755
|
+
**The tail FOLDS, it does not page.** An unbounded feed makes every section below it
|
|
756
|
+
unreachable by scroll. Show the recent few with a toggle naming how many are behind it.
|
|
703
757
|
|
|
704
|
-
The toggle is a `TextDisclosure`, the same control as the transcript's
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
the rail has ended, so nothing visible occupies that indent. Same for any other fold on the
|
|
708
|
-
surface — a document set's "show the rest" is this control too.
|
|
758
|
+
The toggle is a `TextDisclosure`, the same control as the transcript's. It sits on the
|
|
759
|
+
SECTION's left edge, not the rows'. Same for any other fold on the surface — a document set's
|
|
760
|
+
"show the rest" is this control too.
|
|
709
761
|
- **No Handoff section.** Progress owns it: it says where the record SITS and carries the act
|
|
710
|
-
that moves it
|
|
711
|
-
|
|
712
|
-
surface. It belongs on the Progress stage that produced it if an app needs it back.
|
|
762
|
+
that moves it. The sibling record a handoff creates has no home on this surface; it belongs on
|
|
763
|
+
the Progress stage that produced it if an app needs it back.
|
|
713
764
|
|
|
714
765
|
**Activity is not that trail, and the line is the SUBJECT.** Activity carries what a person
|
|
715
766
|
said or sent to a counterparty; the handoff trail carries where the record moved and which
|
|
716
767
|
field changed. Put desk transitions in Activity and it becomes the audit log this template
|
|
717
|
-
declined to build
|
|
718
|
-
|
|
719
|
-
Recall is the undo: confirmed, it withdraws the sibling, clears the custody stamp that
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
Recall.** Undoing a superseded one would leave the record behind a sibling that still exists
|
|
724
|
-
|
|
768
|
+
declined to build.
|
|
769
|
+
|
|
770
|
+
Recall is the undo: confirmed, it withdraws the sibling, clears the custody stamp that handoff
|
|
771
|
+
wrote, and returns the record to the desk BEFORE it — one step, never a jump to the first
|
|
772
|
+
desk, which would silently drop the desks between. History is NEVER erased (the trail keeps
|
|
773
|
+
both marks) and the CTA returns, so redo is possible. **Only the LATEST handoff offers
|
|
774
|
+
Recall.** Undoing a superseded one would leave the record behind a sibling that still exists,
|
|
775
|
+
so a walk-back is one handoff at a time.
|
|
725
776
|
- **Danger zone** — with the full section anatomy (heading + description) like every other
|
|
726
777
|
section; the fenced red `DangerZone` card is the section's BODY.
|
|
727
778
|
- A LEFT OUTLINE RAIL (`MenuButton` + `useSectionNav`) — **the rail is the page's COMPLETE
|
|
@@ -731,25 +782,26 @@ billing, and quick-capture templates. Top → bottom:
|
|
|
731
782
|
current-section bar opening a section-picker POPOVER, anchored to that bar's own button, whose
|
|
732
783
|
items carry the same `current`. Anchored, not a `Modal`: a modal escapes its container, so the
|
|
733
784
|
same surface hosted in a drawer would take the whole window over the register behind it.
|
|
734
|
-
- **The page layout is the DOCS layout — the reading column is CENTRED IN THE VIEWPORT, and
|
|
735
|
-
|
|
736
|
-
`CONTENT_MAX`, or the scroll container becomes the column and the gutters go wheel-dead**
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
blocks are plain `width: "100%"`, never a
|
|
785
|
+
- **The page layout is the DOCS layout — the reading column is CENTRED IN THE VIEWPORT, and the
|
|
786
|
+
rail sits in its left gutter.** The `ScrollView` is FULL-WIDTH — **never cap it at
|
|
787
|
+
`CONTENT_MAX`, or the scroll container becomes the column and the gutters go wheel-dead** (a
|
|
788
|
+
capped scroller leaves the page either side of it ignoring the wheel). Inside it sits one
|
|
789
|
+
centred row (`width: "100%"`, `maxWidth: GUTTER + CONTENT_MAX + PANEL_GUTTER`, `alignSelf:
|
|
790
|
+
"center"`, `flexDirection: "row"`) of three parts: a gutter RESERVED for the rail (`width:
|
|
791
|
+
GUTTER` — the rail's own `RAIL_W` PLUS `RAIL_GAP`, the docs' 3rem of breathing room, so the
|
|
792
|
+
rail never butts against the column), the reading column (`flex: 1, minWidth: 0`), and a right
|
|
793
|
+
gutter of `PANEL_GUTTER` (`PANEL_W` + the same `RAIL_GAP`) that BALANCES the rail — without it
|
|
794
|
+
the column sits half a rail right of centre — and that the discussion panel then floats over,
|
|
795
|
+
exactly as the rail floats over its own. The rail is NOT window chrome: no full-height border,
|
|
796
|
+
not glued to the window edge — it FLOATS over its reserved gutter (`position: "absolute"`,
|
|
797
|
+
`left` mirroring the row's centring maths), so it stays put while the record scrolls
|
|
798
|
+
**without** web-only `position: sticky` (which RN does not type) and without stealing the
|
|
799
|
+
page's scrollable width. Both gutters earn their place only while the reading column still
|
|
800
|
+
clears its FLOOR beside them (`pageWidth >= PAGE_PAD * 2 + GUTTER + PANEL_GUTTER +
|
|
801
|
+
CONTENT_FLOOR` — every term the row spends, `PAGE_PAD` included, or the check passes while the
|
|
802
|
+
column sits under the floor the same code declares); below that they collapse onto the pinned
|
|
803
|
+
bar. Sizing the column is the SHELL's job — inner blocks are plain `width: "100%"`, never a
|
|
804
|
+
second `maxWidth` + `alignSelf: "center"`.
|
|
753
805
|
|
|
754
806
|
### Anything that ADVANCES a record must be able to walk it back
|
|
755
807
|
|
|
@@ -757,18 +809,15 @@ This holds wherever the progression is rendered — a stage CTA on a register ro
|
|
|
757
809
|
drawer's checklist, a bulk action over a selection, the handoff compound below. **Every control
|
|
758
810
|
that moves a record forward ships its inverse, in reach of the person who pressed it.**
|
|
759
811
|
|
|
760
|
-
A one-way control makes a misclick permanent, and stage CTAs invite the misclick:
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
no obvious place it went. The reader is then hunting through tabs for a record they did not mean
|
|
764
|
-
to move, which is a worse outcome than the one the CTA saved them.
|
|
812
|
+
A one-way control makes a misclick permanent, and stage CTAs invite the misclick: the row leaves
|
|
813
|
+
the tab the moment it is pressed, so the feedback for "that was wrong" is the record vanishing,
|
|
814
|
+
with no obvious way back.
|
|
765
815
|
|
|
766
|
-
The undo belongs **beside the forward act, as its secondary** — the
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
undo — it is a confirmation *before* the act, and copy that says why it cannot be taken back.
|
|
816
|
+
The undo belongs **beside the forward act, as its secondary** — the same pairing the kit already
|
|
817
|
+
uses for a commit and the control that undoes it. Do not bury it in an `ActionMenu`. Where a
|
|
818
|
+
stage is genuinely irreversible (money has moved, a document has been filed, something left the
|
|
819
|
+
building), the honest answer is not a missing undo — it is a confirmation *before* the act, and
|
|
820
|
+
copy that says why it cannot be taken back.
|
|
772
821
|
|
|
773
822
|
### The handoff shape — a CONNECTED checklist, and where it lives
|
|
774
823
|
|
|
@@ -780,57 +829,47 @@ It is NOT its own template. A handoff is something a RECORD does, so it renders
|
|
|
780
829
|
that record — see `tpl_record` § Progress, where the desks (Sales → Operations → Accounting) are
|
|
781
830
|
`ChecklistGroup`s and the milestones they produce are the rows.
|
|
782
831
|
|
|
783
|
-
**Two levels, because a desk and a milestone are not peers.**
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
shape instead.
|
|
832
|
+
**Two levels, because a desk and a milestone are not peers.** One flat run — "Operations" beside
|
|
833
|
+
"Collected" — asks the reader to know which name is a PLACE and which is an EVENT; grouping the
|
|
834
|
+
milestones under the desk that produces them says it in the shape instead.
|
|
787
835
|
|
|
788
|
-
**A group is a name and nothing else.** No control: a phase owns no completion of its own,
|
|
789
|
-
ring
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
reader working out which of the rows below it they were about.
|
|
836
|
+
**A group is a name and nothing else.** No control: a phase owns no completion of its own, and a
|
|
837
|
+
ring identical to the pressable rings beneath it invites the press it refuses. No BODY either: a
|
|
838
|
+
condition or an act hung off a heading leaves the reader working out which of the rows below it
|
|
839
|
+
they were about.
|
|
793
840
|
|
|
794
|
-
**Rows tick ONE at a time, and position is the highest stamp
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
"when" and stays one click from being filled.
|
|
841
|
+
**Rows tick ONE at a time, and position is the highest stamp**: ticking `Delivered` puts the
|
|
842
|
+
record there and the rungs below it read done immediately. The alternative — a parent tick that
|
|
843
|
+
fills its children — marks the same rows but INVENTS their dates, writing today onto milestones
|
|
844
|
+
that happened on days nobody recorded. A rung the record passed without a recorded day shows a
|
|
845
|
+
BLANK date, one click from being filled.
|
|
800
846
|
|
|
801
847
|
**A checklist REPORTS.** It does not collect and it does not preview: a field some section owns is
|
|
802
848
|
NAMED as a gap by a `ChecklistNote` whose `action` jumps there, never edited in the row and never
|
|
803
849
|
rendered as a value. The row's OWN stamp (`trailing`) and a value with no other home
|
|
804
850
|
(`ChecklistField`) are the two exceptions.
|
|
805
851
|
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
divorced from the state that gated it. A connected run says the position outright, and the act
|
|
810
|
-
rides the row that owes it. `connected={false}` is for rows that are genuinely N independent
|
|
811
|
-
items; a record's own progress asserts sequence.
|
|
852
|
+
A connected run says the position outright, and the act rides the row that owes it.
|
|
853
|
+
`connected={false}` is for rows that are genuinely N independent items; a record's own progress
|
|
854
|
+
asserts sequence.
|
|
812
855
|
|
|
813
856
|
Built on `checklist` (`Checklist`, `ChecklistGroup`, `ChecklistItem`, `ChecklistNote`,
|
|
814
857
|
`ChecklistActions`, `ChecklistField`), which composes over `Stepper` — `Stepper` renders POSITION,
|
|
815
858
|
this adds the anatomy that turns a position into a workspace.
|
|
816
859
|
|
|
817
|
-
|
|
860
|
+
Rules the anatomy holds:
|
|
818
861
|
|
|
819
862
|
- **Only the OWED row carries an act.** A control on an unreached row invites acting out of
|
|
820
863
|
order — and where position is derived from the highest stamp, one stray press jumps the record
|
|
821
864
|
past the desk that owns it. On a passed row it re-offers something already done.
|
|
822
865
|
- **A condition belongs to its row.** "Sent back", "rejected", "waiting on the customer" are
|
|
823
|
-
facts about ONE position, so they render inside it (`ChecklistNote`) rather than as a
|
|
824
|
-
above the run, which
|
|
825
|
-
control that answers it — and nowhere near enough to say WHICH row it is about.
|
|
866
|
+
facts about ONE position, so they render inside it (`ChecklistNote`) rather than as a
|
|
867
|
+
`Callout` above the run, which sits nowhere near the control that answers it.
|
|
826
868
|
- **A passed row stays correctable** — its stamp is editable, which is how a date recorded wrong
|
|
827
|
-
gets fixed and how a record walks BACK.
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
own fields — coming up blank re-asks a question just answered. Finishing has no receiving
|
|
832
|
-
desk, so it asks for confirmation and nothing else; routing it through the same dialog is how
|
|
833
|
-
a template ends up asking "Assignee at Closed" and then confirming nothing.
|
|
869
|
+
gets fixed and how a record walks BACK.
|
|
870
|
+
- **The stage that OPENS gets stamped by the act that opened it**, and the TERMINAL act is not a
|
|
871
|
+
handoff. Handing off asks whom (a dialog) and writes that answer into the receiving stage's
|
|
872
|
+
own fields. Finishing has no receiving desk, so it asks for confirmation and nothing else.
|
|
834
873
|
|
|
835
874
|
**Altitude rule**: the pipeline is ONE record's progress. N records each at their own stage is
|
|
836
875
|
a REGISTER (`tpl_item_list`) with a derived stage column — the same ladder module feeding both,
|
|
@@ -857,8 +896,7 @@ grouped sections, and the aligned rows. It demonstrates:
|
|
|
857
896
|
- A trailing `ActionMenu` (the `⋯` overflow — a `danger` Remove behind a confirm `Alert`).
|
|
858
897
|
- CUSTOM pressable cells (files, the action link) hover with the **background-tint WASH**
|
|
859
898
|
(`zinc-100`) — the ROW's own hover language, distinct from the field frame a `bare` editor
|
|
860
|
-
fades in on its own hover.
|
|
861
|
-
hover languages, one per surface.)
|
|
899
|
+
fades in on its own hover.
|
|
862
900
|
- Per-group add rows that pre-set the group's field (align with the exported `gridRowStyle`),
|
|
863
901
|
and a Draft-from-notes CTA.
|
|
864
902
|
|
|
@@ -944,10 +982,10 @@ proposes parameters. The panel carries the single Download.
|
|
|
944
982
|
Look-up-and-explain (classification, tariff/fee lookup, policy Q&A, a spec/compliance desk).
|
|
945
983
|
Describe the subject on the LEFT (`Composer` + `AgentRun`), the agent RANKS the matching
|
|
946
984
|
candidates (nearest matches, the top one wearing a Recommended `Badge`), you pick one and its
|
|
947
|
-
structured ANSWER pins on the RIGHT — the code, the exact breakdown as hand-composed spec
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
985
|
+
structured ANSWER pins on the RIGHT — the code, the exact breakdown as hand-composed spec rows,
|
|
986
|
+
policy `Callout`s, `Confidence`, and `Sources`. The flow is input → matches → pick, not a single
|
|
987
|
+
confident verdict: the alternatives are first-class. Refine the description in a follow-up and
|
|
988
|
+
the ranking updates.
|
|
951
989
|
|
|
952
990
|
### The document desk — in `tpl_record`
|
|
953
991
|
|
|
@@ -962,17 +1000,15 @@ so review opens at the top):
|
|
|
962
1000
|
- **Extract** (read the documents, fill the record) — a `DetailTable` of proposed values, each
|
|
963
1001
|
row a label plus a `DiffValue` and its own Keep/Drop, with `useChangeSet` holding the
|
|
964
1002
|
verdicts. **Editing IS the review**; the four shapes are ONE component — an add omits
|
|
965
|
-
`before`, a removal omits `after` (the struck value IS the change), an update passes both,
|
|
966
|
-
|
|
1003
|
+
`before`, a removal omits `after` (the struck value IS the change), an update passes both, and
|
|
1004
|
+
a conflict is a `DiffValue` on its placeholder over `CardSelectItem` candidates plus a
|
|
967
1005
|
type-another-value option, Keep gated until one is picked. Proposed line items arrive as a
|
|
968
1006
|
`Table` with `DiffMark` leading each row — a set of lines reads like the ledger it is about.
|
|
969
1007
|
The line table also works the ATTACHMENT case: a dropped invoice's proposal is WHICH LINE it
|
|
970
1008
|
belongs to, so the Document column is a `DiffValue` whose sides are file chips, and a file
|
|
971
|
-
replacing one already filed shows both with the superseded one struck. Its untouched lines
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
among the blanks without reading the column.
|
|
975
|
-
Keep-all + one outcome-named Apply commit.
|
|
1009
|
+
replacing one already filed shows both with the superseded one struck. Its untouched lines are
|
|
1010
|
+
deliberate — they are why an untouched row draws NO mark. Keep-all + one outcome-named Apply
|
|
1011
|
+
commit.
|
|
976
1012
|
- **Cross-check** (compare the documents against the record and each other) — ranked,
|
|
977
1013
|
display-only `Finding`s the human acts on, closing in a recorded verdict.
|
|
978
1014
|
- **Edit with AI** — the `askAi` handoff: dialogue-shaped file iteration happens in the
|