@lotics/ui 11.4.0 → 11.6.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 +41 -1100
- package/docs/ai_patterns.md +374 -0
- package/docs/catalog.md +920 -0
- package/docs/composition.md +439 -0
- package/docs/data_entry.md +398 -0
- package/docs/templates.md +357 -0
- package/examples/tpl_item_list.tsx +2 -6
- package/package.json +3 -2
- package/src/table.test.ts +90 -0
- package/src/table.tsx +200 -43
- package/src/table_fit.ts +88 -0
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
# AI patterns — AI proposes, the human decides
|
|
2
|
+
|
|
3
|
+
The AI surfaces of `@lotics/ui`: the command composer, the live run feed, the review-before-apply
|
|
4
|
+
family, findings, clarification, provenance, confidence — how to compose them into a loop, which
|
|
5
|
+
surface fits which outcome, the proven screen shapes, and the visual vocabulary AI output uses.
|
|
6
|
+
Read this before building any AI-driven app screen. **This doc is the UI half of a pair**: the SDK
|
|
7
|
+
half — declaring and running agents (`useAgentRun`, `askAi`, sessions, the authority model) — is
|
|
8
|
+
[`@lotics/app-sdk` docs/ai.md](../../app-sdk/docs/ai.md); that doc feeds the data, this one renders
|
|
9
|
+
it. Exact prop APIs are the shipped sources — `../src/<name>.tsx` (never guess a prop; open the
|
|
10
|
+
file). Every component here is in the [catalog](./catalog.md) inventory.
|
|
11
|
+
|
|
12
|
+
## The one law
|
|
13
|
+
|
|
14
|
+
The AI surfaces share ONE law: the agent never commits — it **proposes**, the human
|
|
15
|
+
accepts / edits / dismisses, the deterministic app applies. The agent owns judgment
|
|
16
|
+
(recognition, estimation, intent→parameters); the app owns geometry, math, and the write.
|
|
17
|
+
Compose the surfaces as a loop, and reach for the right one by job.
|
|
18
|
+
|
|
19
|
+
## Which AI surface — decide by the OUTCOME's shape, not the task's topic
|
|
20
|
+
|
|
21
|
+
Three shapes:
|
|
22
|
+
|
|
23
|
+
1. **Field writes** (extract, match, rank, classify → the record changes) → **in-app agent**
|
|
24
|
+
(`useAgentRun`) + **`ChangeReview`**. The commits must obey the diff law (Keep/Drop per
|
|
25
|
+
change, app-workflow writes, bounded app authority), and the operator wants one button +
|
|
26
|
+
a review — never a prompt box.
|
|
27
|
+
2. **Evidence for an in-app decision** (cross-check, audit, tie-out → nothing is written; the
|
|
28
|
+
operator acts on what was found) → **in-app agent** + **`Finding`**. Still bounded and
|
|
29
|
+
prompt-free (an optional instructions brief at most): the brief is fixed, the output is
|
|
30
|
+
structured display-only findings read IN the record's context, and there is no conversation
|
|
31
|
+
to have — the loop closes when the operator acts in the app. Chat would add prompting and
|
|
32
|
+
tear the findings away from the record they judge.
|
|
33
|
+
3. **A file or an open-ended answer** (edit this document, draft from context, explain) →
|
|
34
|
+
**hand off to the chat agent** (`askAi` in `@lotics/app-sdk` — see
|
|
35
|
+
[the SDK's fields-vs-file razor](../../app-sdk/docs/ai.md)). The loop is multi-turn with
|
|
36
|
+
no output schema, judged by looking — and the chat harness already owns it: preview beside
|
|
37
|
+
the thread, version chains, branching, session memory. An in-app "edit chat" would
|
|
38
|
+
re-implement all of that inside every app.
|
|
39
|
+
|
|
40
|
+
The Document desk's Use-AI fork IS this table as UI: Extract data (1) · Cross-check (2) ·
|
|
41
|
+
Edit with AI (3) — one entry point, three outcome shapes
|
|
42
|
+
([`tpl_documents`](../examples/tpl_documents.tsx)).
|
|
43
|
+
|
|
44
|
+
## Command / compose — `Composer`
|
|
45
|
+
|
|
46
|
+
`Composer` (`@lotics/ui/composer`) is the adaptive command surface. Empty + minimal (no
|
|
47
|
+
`footerRight` model picker, no `pills`, no `files`, no `children`), it's a COMPACT PILL: an
|
|
48
|
+
optional `actionsButton` (attach) left, the input, a send arrow right (Enter sends, Shift+Enter
|
|
49
|
+
for a newline) — the same geometry `AgentProgress` morphs into. Type past one line, or add
|
|
50
|
+
files/pills/footer chrome, and it EXPANDS: the input lifts onto its own row, the `files` slot
|
|
51
|
+
(`FileThumbnail`s, each `onRemove`-able) and `pills` stack above, and the buttons drop to a
|
|
52
|
+
footer row. The expansion latches until the text is fully cleared — blank lines (Shift+Enter)
|
|
53
|
+
keep it expanded; Send still gates on non-whitespace text.
|
|
54
|
+
|
|
55
|
+
Key props (full API: `../src/composer.tsx`):
|
|
56
|
+
|
|
57
|
+
| Prop | What it does |
|
|
58
|
+
|---|---|
|
|
59
|
+
| `onSend(text)` | Fires on Enter / the send button; clears the input. The host holds any uploaded file id itself |
|
|
60
|
+
| `onStop` | With `disabled`, swaps the send button for a Stop button |
|
|
61
|
+
| `sendDisabled` | Overrides the default block-on-empty-text gate — set it to allow a files-only send, or to require an attachment |
|
|
62
|
+
| `value` / `onChangeText` | Optional controlled text; omit for internal state |
|
|
63
|
+
| `pills` / `files` / `children` | Slots stacked above the input; any of them forces the expanded layout |
|
|
64
|
+
| `actionsButton` | Attach/actions button — inline-left in the pill, footer-left expanded |
|
|
65
|
+
| `footerRight` | Footer-right content before Send (a model picker); forces expanded |
|
|
66
|
+
| `maxLines` | Expanded growth cap before the input scrolls (default 10) |
|
|
67
|
+
| `sendLabel` / `stopLabel` | Accessible/tooltip labels (default English "Send"/"Stop" — pass translations) |
|
|
68
|
+
| `textInputProps` | Extra `TextInput` props; a consumer `onKeyPress` runs before Enter-to-send and can `preventDefault()` to suppress it |
|
|
69
|
+
|
|
70
|
+
For a run that needs a FILE + a prompt together (attach a photo, review/remove it, add a note,
|
|
71
|
+
THEN send), fill `actionsButton` + the `files` slot and set `sendDisabled` so Send fires with an
|
|
72
|
+
attachment and/or text. **Never auto-run an agent on attach** — attaching HOLDS the file for
|
|
73
|
+
review; the human presses Send. The canvas template
|
|
74
|
+
([`tpl_dieline`](../examples/tpl_dieline.tsx)) uses it this way; while running the host swaps
|
|
75
|
+
`Composer` for `AgentProgress` (same pill geometry, so it reads as a morph).
|
|
76
|
+
|
|
77
|
+
## Show the work — `AgentRun`
|
|
78
|
+
|
|
79
|
+
`AgentRun` (`@lotics/ui/agent_run`) is a live feed of the agent's work as a TIMELINE. The prop is
|
|
80
|
+
an ORDERED `items` array (`AgentRunItem`):
|
|
81
|
+
|
|
82
|
+
| Item | Shape | Renders as |
|
|
83
|
+
|---|---|---|
|
|
84
|
+
| `{ type: "text", id, text }` | Answer prose | `Markdown`, a trailing ▍ caret on the last segment while streaming |
|
|
85
|
+
| `{ type: "reasoning", id, text }` | Thinking | COLLAPSED — a muted "Thinking" row; press to reveal the Markdown |
|
|
86
|
+
| `{ type: "step", id, label, status, kind?, input?, output?, errorText?, detail?, peek? }` | A tool/step call | An activity row with a status dot |
|
|
87
|
+
|
|
88
|
+
Items appear in the order they happened — a real run is think → text → a burst of calls → more
|
|
89
|
+
text, NOT all text on top of a flat step list. Transparent work, NEVER a bare spinner.
|
|
90
|
+
|
|
91
|
+
**Progressive disclosure.** The feed shows the label + state; the detail is revealed on demand.
|
|
92
|
+
A `step`'s `input`/`output` are hidden in the row but open in a press-to-reveal **peek**
|
|
93
|
+
(auto-built Input / Output `JsonPanel`s). On `status: "error"` the row's dot goes amber, the
|
|
94
|
+
`detail` line reads in the danger ink, and `errorText` shows in the peek's Error panel. Pass an
|
|
95
|
+
explicit `peek` node to render that reveal yourself (it overrides the auto panels). `detail` is
|
|
96
|
+
OPTIONAL — a short human summary (a count), never invented prose or raw I/O (that's
|
|
97
|
+
`input`/`output`).
|
|
98
|
+
|
|
99
|
+
**Activity grouping.** Consecutive `step` items fold into ONE group. While the agent is mid-tools
|
|
100
|
+
the tail group is a SINGLE pulsing row whose label swaps in place as each call fires (no growing
|
|
101
|
+
stack of dots); once prose resumes the group settles: a single call becomes one done row, and a
|
|
102
|
+
multi-call group becomes a persistent "{final action} · {n} steps" HEADER (a `complete` terminal
|
|
103
|
+
dot — distinct from the filled `done` step dots; amber `warning` if any call errored) that STAYS
|
|
104
|
+
PUT and rolls the calls out BELOW it on press. The run ALWAYS ends on the agent's text — there is
|
|
105
|
+
no global terminal node.
|
|
106
|
+
|
|
107
|
+
**Tool labels.** A `step` with `kind: "tool"` carries the RAW tool name (`update_records`),
|
|
108
|
+
resolved via a built-in map of the platform record/document tools + an optional `labelForTool`
|
|
109
|
+
override (localize THERE — the kit stays English); unknown names fall back to a prettified form.
|
|
110
|
+
`stepsLabel` localizes the "{n} steps" suffix. `state` (`"streaming" | "done" | "error"`)
|
|
111
|
+
defaults to `streaming` while any step is running, else `done`.
|
|
112
|
+
|
|
113
|
+
Fed natively by `@lotics/app-sdk`'s `useAgentRun().items` (reasoning + per-tool I/O + state come
|
|
114
|
+
for free — no hand-assembly): `<AgentRun items={run.items} state={…} />` — see
|
|
115
|
+
[the SDK doc](../../app-sdk/docs/ai.md) for the hook.
|
|
116
|
+
|
|
117
|
+
**Limitation:** the collapsed reasoning row's "Thinking" label and the auto peek's
|
|
118
|
+
"Input"/"Output"/"Error" panel titles are currently English-only (not on the locale provider and
|
|
119
|
+
not prop-overridable); `labelForTool`/`stepsLabel` localize everything else in the feed.
|
|
120
|
+
|
|
121
|
+
### `AgentProgress` — the floating pill
|
|
122
|
+
|
|
123
|
+
On a canvas/composer app reach for `AgentProgress` (`@lotics/ui/agent_progress`) — `AgentRun`
|
|
124
|
+
collapsed into a floating pill (an animated `WaveAvatar` + the current step's label) that EXPANDS
|
|
125
|
+
on press to the full feed in a capped scroll panel. It takes the same `items`/`state`/
|
|
126
|
+
`labelForTool`/`stepsLabel`, plus `label` (override the collapsed text — defaults to the running
|
|
127
|
+
step's label, else "Working…" / "Done" / "Stopped"; pass it to localize — note it's one static
|
|
128
|
+
string, so it no longer tracks the running step) and `defaultExpanded`. Its pill matches
|
|
129
|
+
`Composer`'s compact geometry, so the composer morphs into it while running and reveals again
|
|
130
|
+
when done.
|
|
131
|
+
|
|
132
|
+
**Limitation:** the pill's expand/collapse accessibility labels ("Show the agent's steps" /
|
|
133
|
+
"Hide the agent's steps") are currently hardcoded English.
|
|
134
|
+
|
|
135
|
+
## Review before apply — ONE surface, the compound `ChangeReview` family
|
|
136
|
+
|
|
137
|
+
Every "the agent proposes → the human accepts / edits / dismisses → nothing auto-applies" surface
|
|
138
|
+
composes from twelve pieces in three groups (all in `@lotics/ui/change_review`; worked example:
|
|
139
|
+
[`tpl_documents`](../examples/tpl_documents.tsx)).
|
|
140
|
+
|
|
141
|
+
**Frame**
|
|
142
|
+
|
|
143
|
+
- `ChangeReview` — context provider + stack; hairline dividers between consecutive `Change`
|
|
144
|
+
sections (and `ChangeFields` rules between fields). Changes are OPEN sections — spacing and
|
|
145
|
+
rules, never card boxes.
|
|
146
|
+
- `ChangeReviewHeader` — the SECTION heading (md semibold; default title localizes to "Suggested
|
|
147
|
+
edits"). Keep it lean: never repeat the record the dialog is already about. The counter does
|
|
148
|
+
NOT live here.
|
|
149
|
+
- `ChangeReviewActions` — the commit bar; pin it in the `DialogFooter`/`DrawerFooter` (or inline
|
|
150
|
+
under the list). **Keep all** sits bottom-left and presses every pending decision's own
|
|
151
|
+
`onAccept` — pass `onAcceptAll` when decisions live in host state the registry can't see
|
|
152
|
+
(field-level Keep/Drop, unresolved conflicts); it then replaces the derived handler entirely.
|
|
153
|
+
The "N of M kept" counter reads HERE beside it, never in the header (the counter renders only
|
|
154
|
+
when at least one registered entry carries `onAccept`; Keep-all renders when one does OR when
|
|
155
|
+
you pass `onAcceptAll`; `showAcceptAll={false}` hides Keep-all). Apply
|
|
156
|
+
disables below `minKept` kept (default 1 when any registered entry carries `onAccept`, else 0)
|
|
157
|
+
or via `applyDisabled` (host-level gate, ORed in); `applyLoading` marks the async write.
|
|
158
|
+
`onDiscard` puts the Discard button beside Apply. Apply is the only outcome-named button
|
|
159
|
+
(`applyLabel`).
|
|
160
|
+
|
|
161
|
+
**Sections**
|
|
162
|
+
|
|
163
|
+
- `Change` — one proposed change: `id` (registers in the review context — the counter, Keep-all
|
|
164
|
+
and the apply gate derive from registered ids), controlled `status`
|
|
165
|
+
(`"pending" | "accepted" | "rejected"`), `onAccept`/`onReject`/`onUndo`, heading + free body +
|
|
166
|
+
optional verbs + collapse. Omit both callbacks for a **display-only** entry (a finding inside a
|
|
167
|
+
review). Decided → collapses to the `ChangeSummary` child (falls back to the body) behind a
|
|
168
|
+
status mark + Undo. `acceptDisabled` gates Keep on a precondition.
|
|
169
|
+
- `ChangeLabel` — the change's SUBJECT heading; a stack of changes skims by subject.
|
|
170
|
+
- `ChangeSummary` — the collapsed row's one-line content after a decision.
|
|
171
|
+
- `ChangeReasoning` — the agent's quiet hairline-ruled aside (muted text behind a left rule) —
|
|
172
|
+
only when the change is not self-explanatory.
|
|
173
|
+
|
|
174
|
+
**The grammar**
|
|
175
|
+
|
|
176
|
+
- **`ChangeFields` + `ChangeField`** — THE field unit. `ChangeFields` is the OPEN form (the
|
|
177
|
+
section IS the record — an extract dialog's one order: no card chrome), stacking `ChangeField`s
|
|
178
|
+
with hairline rules. A diff-form `ChangeField` renders, top to bottom: the label (sm medium,
|
|
179
|
+
**full colour** — it names the subject of a decision, never `DetailRow`'s muted ink) · the
|
|
180
|
+
field's `reasoning` directly under the label · the `−` band when replacing (`before`) · the
|
|
181
|
+
value — `ChangeValueInput` when `onChangeText` is given (the green `+` band; pressing it edits
|
|
182
|
+
IN THE BAND — a borderless input with identical type metrics, so nothing shifts), the read-only
|
|
183
|
+
`+` band when `valueReadOnly` or no `onChangeText` (muted `placeholder` while empty), or any
|
|
184
|
+
input via `children` — compose `<ChangeValueInput unit="pcs" …>` there yourself for a unit
|
|
185
|
+
suffix (`unit` fixes the suffix outside the editable core — type the number, never the unit;
|
|
186
|
+
the built-in default doesn't take it) · conflict `candidates` as full-width decision rows
|
|
187
|
+
(picked via `onPickCandidate`) + the localized "Type another value" third option
|
|
188
|
+
(`customValue`/`customSelected`/`onCustomValue`/`onCustomSelect`; the outcome band is read-only
|
|
189
|
+
via `valueReadOnly` — the decision comes from picking, never from editing the band; gate with
|
|
190
|
+
`keepDisabled`) · per-field **Keep/Drop** bottom-right
|
|
191
|
+
(`status: "pending" | "kept" | "dropped"`, `onKeep`/`onDrop`/`onUndo`). A pure REMOVAL is
|
|
192
|
+
`before` with no value — the `−` band alone. A decided field collapses to the compact one-row
|
|
193
|
+
card (mark · label → `summary` · Undo). A field with no diff and no decision is the plain
|
|
194
|
+
inline label · value row — this form borrows `DetailRow`'s *metrics* (130px label column /
|
|
195
|
+
40px row) — press-to-edit when `onChangeText` is given.
|
|
196
|
+
|
|
197
|
+
**Limitation:** `ChangeFieldCandidate.source` and `.description` are accepted by the type but
|
|
198
|
+
the built-in candidate row currently renders only the `value` (plus `selected`). Fold a short
|
|
199
|
+
qualifier into the value string, or compose your own rows; provenance belongs in `Sources` at
|
|
200
|
+
the review's bottom anyway.
|
|
201
|
+
|
|
202
|
+
- **`ChangeRecord`** — THE item card for SETS of records (order lines): `id` (registers in the
|
|
203
|
+
review context like a `Change`), `tone` `"add" | "edit" | "remove"`, the tinted header band
|
|
204
|
+
with the localized op word (Add / Edit / Delete — the `changeReview` locale slice) + `title`,
|
|
205
|
+
body = its `ChangeField`s, required `summary` for the collapsed row. **The verb level follows
|
|
206
|
+
the decision level**: add/remove carry ONE card Keep/Drop (fields none — editable parts, one
|
|
207
|
+
write); edit carries NO card verbs (only its changed fields, each deciding for itself —
|
|
208
|
+
dropping a field narrows the update diff).
|
|
209
|
+
- **`ChangeBand`** — the raw diff band (aligned `+`/`−` marker column, light emerald/red, dark
|
|
210
|
+
text) every piece builds from; reach for it directly for custom strokes (a removed record's
|
|
211
|
+
one-line summary). String content is UNCLAMPED by default (`numberOfLines` to clamp) — a
|
|
212
|
+
review must show the whole value being decided.
|
|
213
|
+
- **`ChangeValueInput`** — the proposed value as a proper diff at rest (the green `+` line) that
|
|
214
|
+
becomes a real input on press; rest and edit share one height, so nothing shifts.
|
|
215
|
+
|
|
216
|
+
**The scaling boundary (known, deliberate)**: `ChangeFields` + `ChangeRecord` cards cover one
|
|
217
|
+
record through ~10; a BULK review (30+ imported rows — a bank statement, a spreadsheet import)
|
|
218
|
+
needs a dense form that does not exist yet — design it against the first real migration, not
|
|
219
|
+
speculatively.
|
|
220
|
+
|
|
221
|
+
**The laws**: ONE verb pair everywhere — Keep/Drop, localized through the `changeReview` locale
|
|
222
|
+
slice (en: Keep / Drop / Keep all / Apply / Discard / Undo); never rename per shape (Apply is the
|
|
223
|
+
only outcome-named button; `acceptLabel`/`rejectLabel` exist but are not a license to invent
|
|
224
|
+
verbs). Candidate rows carry the VALUE only; provenance = `Sources` at the VERY bottom of the
|
|
225
|
+
review (after every change; or omit it — lean beats decorated). The HOST owns every decision in
|
|
226
|
+
plain `useState` (controlled `status` everywhere); the family owns the mechanics (collapse +
|
|
227
|
+
Undo, the counter, Keep-all, apply gating). Never apply a field with no value — gate unresolved
|
|
228
|
+
conflicts (`keepDisabled`, `applyDisabled`). Editing IS the review.
|
|
229
|
+
|
|
230
|
+
## Ask back — `Clarify`
|
|
231
|
+
|
|
232
|
+
`Clarify` (`@lotics/ui/clarify`): when the agent is unsure, it asks a question with quick-reply
|
|
233
|
+
options and PAUSES, instead of guessing wrong. Human-in-the-loop input mid-run: `question`,
|
|
234
|
+
`options` (`{ label, value, description? }` — a `ChoiceList`), `onAnswer(value)` resumes the run,
|
|
235
|
+
and the controlled `answer` keeps the pick switchable until committed.
|
|
236
|
+
|
|
237
|
+
**Limitation:** the "Question" eyebrow above the prompt is currently hardcoded English (no label
|
|
238
|
+
prop, not on the locale provider).
|
|
239
|
+
|
|
240
|
+
## Provenance — `Sources`
|
|
241
|
+
|
|
242
|
+
`Sources` (`@lotics/ui/sources`): openable chips saying where the output came FROM, under any
|
|
243
|
+
answer / summary / extracted value. Makes AI output verifiable — show it on anything produced
|
|
244
|
+
from data the agent read. Each `SourceRef` is `{ id, label, kind?, detail? }` — `detail` is a
|
|
245
|
+
locator after the label ("page 2", a record code). Kinds carry recognizable glyph + colour:
|
|
246
|
+
|
|
247
|
+
| `kind` | Glyph | Colour |
|
|
248
|
+
|---|---|---|
|
|
249
|
+
| `document` (the default) | file-text | red |
|
|
250
|
+
| `table` | table-2 | emerald |
|
|
251
|
+
| `record` | box | blue |
|
|
252
|
+
| `knowledge` | book-open | amber |
|
|
253
|
+
| `web` | globe | sky |
|
|
254
|
+
|
|
255
|
+
`label` sets the eyebrow above the chips (default "Sources" — English; pass a translation), or
|
|
256
|
+
`label={null}` for chips only — the form used INSIDE a `Change` or a `Finding`. Pass `onOpen` to
|
|
257
|
+
make each chip pressable (hover wash + an open glyph); the host does the navigation. An empty
|
|
258
|
+
list renders nothing.
|
|
259
|
+
|
|
260
|
+
## Confidence — `Confidence`
|
|
261
|
+
|
|
262
|
+
`Confidence` (`@lotics/ui/confidence`): how sure the AI is — a 3-tick meter + the full level
|
|
263
|
+
phrase ("High confidence" / "Medium confidence" / "Low confidence"), emerald / amber / zinc by
|
|
264
|
+
level (low is *unsure*, not an error). Pass `level` (`"high" | "medium" | "low"`) or a 0–1
|
|
265
|
+
`score` (≥ 0.8 high · ≥ 0.5 medium · else low; neither given defaults to medium). Phrases resolve
|
|
266
|
+
prop `labels` → the `confidence` locale slice → English — one phrase per level, so it translates
|
|
267
|
+
cleanly. The human weights an AI proposal by it; pair with `ChangeReview`.
|
|
268
|
+
|
|
269
|
+
## Findings — evidence, not writes
|
|
270
|
+
|
|
271
|
+
`Finding` (`@lotics/ui/finding`) is one ranked insight from an AI check — a cross-check
|
|
272
|
+
discrepancy, an audit observation, a briefing item: severity dot-badge + localized word ·
|
|
273
|
+
`title` · `detail` · the PROMINENT `metric` (lg semibold, never a side note) + `metricCaption` ·
|
|
274
|
+
`Sources` chips at the bottom (`sources`/`onOpenSource`), with a `children` slot between body and
|
|
275
|
+
sources. Severity → colour: `critical` red · `warning` amber · `info` zinc ("Note") · `positive`
|
|
276
|
+
emerald ("On track"); words come from the `finding` locale slice. **Display-only**: a finding
|
|
277
|
+
informs the action the human takes in the app; it decides nothing itself — no phantom "record
|
|
278
|
+
verdict" write (a persisted check-status goes stale on the next edit). Stack several most-severe
|
|
279
|
+
first — inside a `ChangeReview` wrap each in a display-only `Change` (the family's dividers
|
|
280
|
+
apply). `FindingComparison` is the expected-vs-actual body: each disagreeing side a labeled row
|
|
281
|
+
(source · value), a hairline, then the DELTA emphasized — compose it as the finding's children
|
|
282
|
+
for any one-value-disagrees insight (quantities, totals, dates).
|
|
283
|
+
|
|
284
|
+
## Session, not chat
|
|
285
|
+
|
|
286
|
+
The outputs accrue as a history the user can CLEAR ("New session"); the APP owns the evolving
|
|
287
|
+
state, each run is a discrete task. (The agent may re-read the session for "make it a bit less",
|
|
288
|
+
but it's a run LOG, not a conversation transcript — that's why it's not a chat.) The session key
|
|
289
|
+
and history reads are the SDK's: `sessionId` on each run, `useAgentRuns` for the history — see
|
|
290
|
+
[the SDK doc](../../app-sdk/docs/ai.md).
|
|
291
|
+
|
|
292
|
+
## Five proven shapes
|
|
293
|
+
|
|
294
|
+
Five shapes prove the range. **One PRODUCES** — a living artifact the user shapes over time.
|
|
295
|
+
(The other producing outcomes — a conversation, generated prose, an edited document — are the
|
|
296
|
+
chat handoff, shape 3 of
|
|
297
|
+
[the decision table](#which-ai-surface--decide-by-the-outcomes-shape-not-the-tasks-topic);
|
|
298
|
+
the chat harness owns them.)
|
|
299
|
+
|
|
300
|
+
- **Canvas** ([`tpl_dieline`](../examples/tpl_dieline.tsx)) — the page IS the design on a
|
|
301
|
+
pannable/zoomable surface: it stays CENTRED at every zoom (a floating zoom pill bottom-left),
|
|
302
|
+
the FLOATING composer at the bottom (a `Composer`: attach a photo → review/remove it → add a
|
|
303
|
+
note → send, with `sendDisabled` allowing a file-and/or-text submit — NOT auto-run on attach)
|
|
304
|
+
morphs into `AgentProgress` while running, and a pinned PARAMS PANEL (a label/value field-card
|
|
305
|
+
in live-edit mode, carrying the single Download; minimizes to a pill) floats centre-right.
|
|
306
|
+
Panel, zoom pill and composer float ON TOP — they never shift the design. (Floating layers use
|
|
307
|
+
a `pointerEvents: "none"` wrapper with the interactive child set `"auto"`; RN-Web ignores
|
|
308
|
+
`"box-none"` in style, so a full-width wrapper would otherwise eat clicks on the canvas behind
|
|
309
|
+
it.) Change the design by prompt ("5 mm taller") OR by editing a param directly — either
|
|
310
|
+
re-flows it in place. For a design/document the user shapes over time.
|
|
311
|
+
|
|
312
|
+
The other four output STRUCTURE — when the answer is a panel of facts, a queue of decisions, or a
|
|
313
|
+
ranked set, don't cram it into chat prose:
|
|
314
|
+
|
|
315
|
+
- **Answer desk** ([`tpl_lookup`](../examples/tpl_lookup.tsx)) — describe the goods on the LEFT →
|
|
316
|
+
the agent RANKS the matching codes (nearest matches, the top one Recommended) → pick one and
|
|
317
|
+
its STRUCTURED answer pins on the RIGHT (a verdict header + an exact breakdown + the policies +
|
|
318
|
+
sources). Input → matches → pick, NOT a single confident verdict: classification is ambiguous,
|
|
319
|
+
so the alternatives are first-class and picking a different one changes the outcome; refining
|
|
320
|
+
the description re-ranks. For look-up-and-explain: tariff/classification codes, fee lookup,
|
|
321
|
+
policy Q&A, a spec/compliance desk. (NOT a chat with the answer in a bubble.)
|
|
322
|
+
- **Document desk** ([`tpl_documents`](../examples/tpl_documents.tsx) — THE go-to for
|
|
323
|
+
document-driven records) — the record's files block feeds ONE "Use AI" entry that FORKS into
|
|
324
|
+
the two document tasks, each a specialized run with a task-pure result: **Extract** (files
|
|
325
|
+
read → fields already matching fold into one quiet line → every add / update / conflict a
|
|
326
|
+
`ChangeField` (the `−` band · the editable value · candidate rows), the record's current value
|
|
327
|
+
a first-class choice — plus proposed new lines as record-body `Change`s → one outcome-named
|
|
328
|
+
`ChangeReviewActions` commit) and **Cross-check** (documents compared against the record and
|
|
329
|
+
each other → ranked `Finding`s — severity · title · the prominent metric · sources — separated
|
|
330
|
+
by hairlines; the findings ARE the outcome the human acts on). The fork carries an OPTIONAL
|
|
331
|
+
instructions field — the user steers what the agent checks or extracts, so `Finding` serves ANY
|
|
332
|
+
file-based AI request, not just the stock cross-check. Every file list in the flow opens the
|
|
333
|
+
full-page `FileGalleryModal`. Plus **Create documents**: a readiness checklist (unchecked by
|
|
334
|
+
default, missing inputs called out) → generate → the files land back on the record. Never merge
|
|
335
|
+
the two AI tasks into one mixed output — the fork is the design.
|
|
336
|
+
- **Triage** — an inbox the agent classified + routed is a `ChangeReview` of `Change`s (body: the
|
|
337
|
+
item + the agent's call; the standard Keep/Drop verbs; Keep-all covers the high-confidence
|
|
338
|
+
sweep). Leads, tickets, documents, emails.
|
|
339
|
+
- **Compare / ranked pick** — one `ChangeField` whose `candidates` carry the ranked options; the
|
|
340
|
+
human picks one. Quotes, carriers, suppliers, plans. (Fold the score/reason into each
|
|
341
|
+
candidate's value string — see the candidate-row limitation above.)
|
|
342
|
+
|
|
343
|
+
## The visual vocabulary — functional colour, no gimmicks
|
|
344
|
+
|
|
345
|
+
The AI vocabulary has **no purple accent and no gimmick glyphs** (no sparkles) — but it is NOT
|
|
346
|
+
monochrome: **colour is used where it carries meaning, not for decoration.** What the violet
|
|
347
|
+
sparkle used to carry now reads structurally — **provenance** is a quiet sentence-case microlabel
|
|
348
|
+
naming the artifact ("Proposed" · "Match" · "Mismatch" · "Suggested edit" · "Question" — xs,
|
|
349
|
+
muted, medium; all-caps is banned kit-wide), and the agent's **reasoning** is a left-ruled margin
|
|
350
|
+
note (a hairline rule + muted text — `ChangeReasoning`), quoted apart from the facts and the
|
|
351
|
+
human's controls — while **status/severity/diffs use functional colour** the way the rest of the
|
|
352
|
+
kit does:
|
|
353
|
+
|
|
354
|
+
- **Confidence** — the 3-tick meter + the full phrase, emerald / amber / zinc by level.
|
|
355
|
+
- **Stepper / AgentRun nodes** — progress dots on a spine: `current` a **pulsing accent ring**
|
|
356
|
+
(white centre — pulses only when live), `done` a filled accent dot + **white check**,
|
|
357
|
+
`upcoming` a faint **grey** ring, the terminal `complete` a **blackish ring + black check**,
|
|
358
|
+
`warning` **amber**. Default accent is neutral ink (`Stepper`'s `color` prop themes it);
|
|
359
|
+
`AgentRun` keeps the neutral default — an active group's tail pulses (`current`), a settled
|
|
360
|
+
group is a `done`/`warning` row, and there is no global terminal node (the run ends on text).
|
|
361
|
+
- **ChangeBand** — the removed value on the light **red** band with the `−` marker, the incoming
|
|
362
|
+
value on the light **emerald** band with `+` (the GitHub-diff idiom, markers in one aligned
|
|
363
|
+
column); everything else in a review stays neutral — a decided row reads a single emerald
|
|
364
|
+
check, an add/remove record card wears the quiet tone wash (50 body · 100 header · 200 border).
|
|
365
|
+
Colour marks the change, never the chrome.
|
|
366
|
+
- **Finding severity** — the coloured dot badge (red / amber / zinc / emerald), most severe
|
|
367
|
+
first.
|
|
368
|
+
|
|
369
|
+
Card chrome (borders, microlabels) stays neutral — colour marks the *state*, never the container.
|
|
370
|
+
The **composer keeps its icons**: `Composer` compact is a single-row pill with an optional
|
|
371
|
+
circular attach `actionsButton` + a circular send button; expanded it adds a `FileThumbnail`
|
|
372
|
+
attachment row (each `onRemove`-able) above the input; `AgentProgress` is the `WaveAvatar` pill.
|
|
373
|
+
"No icons" was only ever about the review surfaces' sparkles/severity glyphs, not functional
|
|
374
|
+
affordances.
|