altitude-design 0.3.2 → 0.4.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.
Files changed (136) hide show
  1. package/README.md +1 -0
  2. package/dist/cli/altitude.mjs +120 -20
  3. package/dist/guidance/README.md +4 -0
  4. package/dist/guidance/agent-setup.md +3 -2
  5. package/dist/guidance/components/AILabel/README.md +25 -0
  6. package/dist/guidance/components/Accordion/README.md +7 -0
  7. package/dist/guidance/components/AgentFleet/README.md +34 -0
  8. package/dist/guidance/components/AgentLanes/README.md +21 -0
  9. package/dist/guidance/components/AgentRequests/README.md +29 -0
  10. package/dist/guidance/components/Alert/README.md +22 -0
  11. package/dist/guidance/components/AltitudeProvider/README.md +48 -0
  12. package/dist/guidance/components/AppShell/README.md +51 -0
  13. package/dist/guidance/components/AreaChart/README.md +43 -0
  14. package/dist/guidance/components/Artifact/README.md +30 -0
  15. package/dist/guidance/components/Avatar/README.md +34 -0
  16. package/dist/guidance/components/Await/README.md +80 -0
  17. package/dist/guidance/components/Badge/README.md +22 -0
  18. package/dist/guidance/components/BarChart/README.md +47 -0
  19. package/dist/guidance/components/BarList/README.md +29 -0
  20. package/dist/guidance/components/Board/README.md +31 -0
  21. package/dist/guidance/components/Breadcrumb/README.md +35 -0
  22. package/dist/guidance/components/Button/README.md +17 -0
  23. package/dist/guidance/components/Card/README.md +14 -0
  24. package/dist/guidance/components/ChartBrush/README.md +41 -0
  25. package/dist/guidance/components/ChartContainer/README.md +152 -0
  26. package/dist/guidance/components/ChartLegend/README.md +35 -0
  27. package/dist/guidance/components/ChartReference/README.md +41 -0
  28. package/dist/guidance/components/ChartTooltip/README.md +38 -0
  29. package/dist/guidance/components/Checkbox/README.md +11 -0
  30. package/dist/guidance/components/Checklist/README.md +71 -0
  31. package/dist/guidance/components/CodeBlock/README.md +28 -0
  32. package/dist/guidance/components/ColorPicker/README.md +30 -0
  33. package/dist/guidance/components/Combobox/README.md +16 -0
  34. package/dist/guidance/components/CommandPalette/README.md +27 -0
  35. package/dist/guidance/components/CommentThread/README.md +30 -0
  36. package/dist/guidance/components/Confirmation/README.md +34 -0
  37. package/dist/guidance/components/ContextMenu/README.md +30 -0
  38. package/dist/guidance/components/Conversation/README.md +45 -0
  39. package/dist/guidance/components/CopyButton/README.md +23 -0
  40. package/dist/guidance/components/DataGrid/README.md +82 -0
  41. package/dist/guidance/components/DatePicker/README.md +67 -0
  42. package/dist/guidance/components/Dialog/README.md +29 -0
  43. package/dist/guidance/components/DiffView/README.md +30 -0
  44. package/dist/guidance/components/DotPlot/README.md +38 -0
  45. package/dist/guidance/components/Drilldown/README.md +15 -0
  46. package/dist/guidance/components/DropZone/README.md +21 -0
  47. package/dist/guidance/components/DropdownMenu/README.md +19 -0
  48. package/dist/guidance/components/EditRegion/README.md +104 -0
  49. package/dist/guidance/components/EmptyState/README.md +17 -0
  50. package/dist/guidance/components/FeatureGate/README.md +28 -0
  51. package/dist/guidance/components/Field/README.md +10 -0
  52. package/dist/guidance/components/FilterBar/README.md +69 -0
  53. package/dist/guidance/components/FormattedNumber/README.md +37 -0
  54. package/dist/guidance/components/Heatmap/README.md +45 -0
  55. package/dist/guidance/components/HoldButton/README.md +16 -0
  56. package/dist/guidance/components/HoverCard/README.md +18 -0
  57. package/dist/guidance/components/InlineCitation/README.md +24 -0
  58. package/dist/guidance/components/InlineEdit/README.md +133 -0
  59. package/dist/guidance/components/Input/README.md +27 -0
  60. package/dist/guidance/components/InputOTP/README.md +30 -0
  61. package/dist/guidance/components/ItemGroup/README.md +93 -0
  62. package/dist/guidance/components/Kbd/README.md +9 -0
  63. package/dist/guidance/components/LineChart/README.md +47 -0
  64. package/dist/guidance/components/Link/README.md +28 -0
  65. package/dist/guidance/components/Markdown/README.md +32 -0
  66. package/dist/guidance/components/Meter/README.md +29 -0
  67. package/dist/guidance/components/NotificationCenter/README.md +28 -0
  68. package/dist/guidance/components/NumberInput/README.md +29 -0
  69. package/dist/guidance/components/PageHeader/README.md +29 -0
  70. package/dist/guidance/components/Pagination/README.md +25 -0
  71. package/dist/guidance/components/PasswordInput/README.md +34 -0
  72. package/dist/guidance/components/PermissionMatrix/README.md +28 -0
  73. package/dist/guidance/components/Pile/README.md +12 -0
  74. package/dist/guidance/components/PlanPicker/README.md +25 -0
  75. package/dist/guidance/components/Popover/README.md +12 -0
  76. package/dist/guidance/components/Progress/README.md +10 -0
  77. package/dist/guidance/components/PromptInput/README.md +40 -0
  78. package/dist/guidance/components/PropertyList/README.md +57 -0
  79. package/dist/guidance/components/Question/README.md +27 -0
  80. package/dist/guidance/components/RadioGroup/README.md +10 -0
  81. package/dist/guidance/components/RelativeTime/README.md +26 -0
  82. package/dist/guidance/components/RemovalReceipt/README.md +34 -0
  83. package/dist/guidance/components/Resizable/README.md +36 -0
  84. package/dist/guidance/components/SaveBar/README.md +27 -0
  85. package/dist/guidance/components/SavedViews/README.md +24 -0
  86. package/dist/guidance/components/ScatterChart/README.md +41 -0
  87. package/dist/guidance/components/Schedule/README.md +76 -0
  88. package/dist/guidance/components/ScrollArea/README.md +7 -0
  89. package/dist/guidance/components/SecretField/README.md +28 -0
  90. package/dist/guidance/components/Select/README.md +27 -0
  91. package/dist/guidance/components/Separator/README.md +26 -0
  92. package/dist/guidance/components/Sheet/README.md +32 -0
  93. package/dist/guidance/components/Skeleton/README.md +14 -0
  94. package/dist/guidance/components/Slider/README.md +25 -0
  95. package/dist/guidance/components/SortableList/README.md +11 -0
  96. package/dist/guidance/components/Sparkline/README.md +29 -0
  97. package/dist/guidance/components/SplitButton/README.md +26 -0
  98. package/dist/guidance/components/Stat/README.md +31 -0
  99. package/dist/guidance/components/Stepper/README.md +29 -0
  100. package/dist/guidance/components/Suggestions/README.md +29 -0
  101. package/dist/guidance/components/Surface/README.md +20 -0
  102. package/dist/guidance/components/Switch/README.md +9 -0
  103. package/dist/guidance/components/SyncIndicator/README.md +24 -0
  104. package/dist/guidance/components/Table/README.md +76 -0
  105. package/dist/guidance/components/Tabs/README.md +18 -0
  106. package/dist/guidance/components/TagInput/README.md +66 -0
  107. package/dist/guidance/components/Terminal/README.md +27 -0
  108. package/dist/guidance/components/ThreadList/README.md +37 -0
  109. package/dist/guidance/components/TimeInput/README.md +26 -0
  110. package/dist/guidance/components/Timeline/README.md +32 -0
  111. package/dist/guidance/components/Toaster/README.md +16 -0
  112. package/dist/guidance/components/Toggle/README.md +33 -0
  113. package/dist/guidance/components/Toolbar/README.md +37 -0
  114. package/dist/guidance/components/Tooltip/README.md +12 -0
  115. package/dist/guidance/components/Tour/README.md +29 -0
  116. package/dist/guidance/components/Trace/README.md +37 -0
  117. package/dist/guidance/components/Tree/README.md +86 -0
  118. package/dist/guidance/evidence.json +18 -0
  119. package/dist/guidance/index.json +811 -7
  120. package/dist/guidance/llms.txt +153 -2
  121. package/dist/guidance/manifest.json +133 -12
  122. package/dist/guidance/patterns/screen-layout.md +1 -1
  123. package/dist/guidance/references/build/checks/agent-setup.mjs +7 -0
  124. package/dist/guidance/references/build/checks/guidance.mjs +8 -2
  125. package/dist/guidance/references/build/checks/source-check.mjs +101 -0
  126. package/dist/guidance/rules.json +155 -0
  127. package/dist/guidance/visual/ActionsInPlace.md +463 -0
  128. package/dist/guidance/visual/Charts.md +119 -0
  129. package/dist/guidance/visual/Distinctiveness.md +106 -0
  130. package/dist/guidance/visual/Elevation.md +1 -1
  131. package/dist/guidance/visual/Mobile.md +64 -0
  132. package/dist/guidance/visual/Motion.md +225 -0
  133. package/dist/guidance/visual/README.md +98 -0
  134. package/dist/guidance/visual/Research.md +1 -1
  135. package/dist/guidance/visual/Theming.md +349 -0
  136. package/package.json +1 -1
@@ -0,0 +1,66 @@
1
+ # TagInput
2
+
3
+ Edit a set of short values in one field: labels on an issue, recipients, skills, keywords, allowed domains. Type and press Enter or comma to add; Backspace twice takes the last one back; each tag has a remove button; Enter, F2 or a double-click edits a tag in place. `Tag` is the same plate on its own, for showing a set outside a field.
4
+
5
+ ```tsx
6
+ <Field label="Labels">
7
+ <TagInput
8
+ value={labels}
9
+ onValueChange={setLabels}
10
+ suggestions={allLabels} // optional; free entry is allowed unless `restrict`
11
+ max={10}
12
+ normalize={(tag) => tag.trim().toLowerCase()} // duplicates are compared after this
13
+ validate={(tag) => tag.length <= 24 ? undefined : 'Use 24 characters or fewer.'}
14
+ noun={['label', 'labels']}
15
+ />
16
+ </Field>
17
+ ```
18
+
19
+ Without `onAdd` and `onRemove` it's a controlled form value. With them, each tag is persisted on its own: `onAdd(tag)` returns `{ ok: true, value }` with the canonical text or `{ ok: false, error }`, the result contract `InlineEdit` uses. The tag waits, pending, until the result. A rejected tag's text comes back to the input with the error so it can be corrected.
20
+
21
+ ## Elevation
22
+
23
+ - **The field** is an opening, built like `Input`: an empty field shows its back wall (the well). Once it holds a tag its floor rises flush, so the field reads as filled. Invalid fields drop to trench depth on `critical-soft`.
24
+ - **A tag** is a label and part of the record, so it's a Badge plate (ground, level 0) stamped flush into the floor. A new tag is stamped in (down 3px, settling), as a Badge is when its label changes.
25
+ - **Armed** (the first Backspace on an empty input): the last tag lifts one level (`elev-1`) on a `critical-soft` tint, and the status says “Press Backspace again to remove <tag>”. The second Backspace lifts it off (up 8px, fading), and the gap closes. Typing anything disarms it.
26
+ - **Pending** (waiting on `onAdd` or `onRemove`): after `dur-grace` the tag hovers at level 2 (`surface-lifted`, `elev-2`), with `aria-busy`. **Rejected**: the tag drops to a trench with a critical rim; the message is shown and announced.
27
+ - **Being edited**: the plate becomes a small well with an input labelled “Edit <tag>”.
28
+ - **Suggestions** are the `Combobox` list at level 3, with its ranking and gliding highlight.
29
+
30
+ Without height: tags are text in a list named by their count; the armed tag is announced; pending tags are `aria-busy`; errors are text wired to the input's `aria-describedby`. Under forced colours tags are 1px outlined, the armed tag is outlined in Highlight, pending dotted, failed thick dashed, and an empty field dashed. Under `contrast="more"` the armed tag gets a 2px ink slab.
31
+
32
+ ## Keyboard
33
+
34
+ - **In the input** (a `combobox`): Enter or a separator adds what's typed (Enter on an active suggestion adds it); ↓/↑ move through suggestions; Backspace on an empty input arms the last tag, and a second removes it; ← from the start moves to the last tag; Escape closes the list or disarms. Nothing acts during IME composition, and a separator typed as text (a soft keyboard's comma) adds too. A paste containing separators or newlines adds one tag per entry, up to `max`; what doesn't fit stays in the input.
35
+ - **On a tag**: the tags are one Tab stop (roving `tabindex`); ←/→ move between them and → from the last returns to the input; Home/End go to the ends. Delete or Backspace removes the tag and focuses the next tag, else the previous, else the input. Enter or F2 edits it in place (Enter saves, Escape cancels); editing is off when `onAdd` or `onRemove` persist tags, because a rename would be two writes.
36
+ - **At `max`** the input stays rendered, focused and read-only, and the description says “Limit reached (10).” Focus is never lost.
37
+
38
+ The remove buttons are for pointers (`tabIndex={-1}`, named “Remove <tag>”); the keyboard path is Delete on the focused tag. One polite status announces “Added design”, “Removed design”, “design is already added.” and errors. The input is described by the count and the separators (“3 labels. Press comma or Enter to add.”).
39
+
40
+ ## Props
41
+
42
+ | Prop | |
43
+ |---|---|
44
+ | `value`, `defaultValue`, `onValueChange` | the tags, a `string[]` |
45
+ | `suggestions`, `restrict` | strings or `SelectOption`s offered as you type; with `restrict` only they can be added |
46
+ | `separators` | default `[',', 'Enter']`; single characters also split a paste |
47
+ | `max`, `validate`, `normalize` | a limit; a per-tag error; applied before validating and comparing (default trim) |
48
+ | `onAdd`, `onRemove` | persist one tag at a time (see above) |
49
+ | `renderTag`, `noun` | draw a tag's contents; words for the count (default `['tag', 'tags']`) |
50
+ | `placeholder`, `id`, `name`, `disabled`, `required`, `invalid`, `size` | as `Combobox`; `name` submits each tag through a hidden input |
51
+
52
+ `Tag`: `children`, optional `onRemove` (adds a remove button) and `label` (names it when the children aren't text).
53
+
54
+ ## You provide
55
+
56
+ The set, its policy (what's allowed, the limit, normalizing), and any persistence. TagInput doesn't fetch suggestions; pass the current list.
57
+
58
+ ## Do and don't
59
+
60
+ - **Do** use it for sets of short, free values. For one value from a list, use `Combobox`; for several from a short fixed list, use `Checkbox`es.
61
+ - **Do** normalize (case, spaces) so duplicates are caught.
62
+ - **Don't** put long text in tags; a tag is cut at 24 characters with an ellipsis.
63
+
64
+ ## Demo and verification
65
+
66
+ The `TagInput` section of `build/previews.txt` shows labels (suggestions, a limit of 6, coloured dots), recipients persisted one at a time (a fictional invite service rejects other domains), a disabled set and standalone `Tag`s. `node checks/tag-input.mjs` (from `build/`; `ALT_FIXTURE_ROOT` selects React 19 fixtures) covers adding, suggestions, duplicates, double Backspace, focus after removal, editing, paste splitting, the limit, IME, pending and rejected adds, removal, and 320/960px layouts in both schemes under reduced motion, forced colours and `contrast="more"`.
@@ -0,0 +1,27 @@
1
+ # Terminal
2
+
3
+ A command's output as it runs, with how it ended. Coding agents run `npm test`, `git status` or a migration; the person watches the output arrive, then reads the exit. `CodeBlock streaming` is for code being written; a process also has stderr, an exit status, a duration and Stop.
4
+
5
+ **Elevation: CodeBlock's frame, with a process's states.**
6
+ - **The frame** is flush on the ground with a 1px `line` rim, mono, no shadow, named by its command (`$ npm test`).
7
+ - **Running** isn't the record yet: a dashed `line-control` rule down the left, "Running", and Stop (secondary, level 1).
8
+ - **Finished**, the rule goes solid with one afterglow and the exit is said in words: "Exited with 0 · 4.2s" with a check, "Exited with 1 · 4.2s" with a critical mark (the edge stays neutral; Distinctiveness, CM3), or "Stopped by you".
9
+ - **stderr lines** carry a critical mark in the gutter and say "stderr" to screen readers.
10
+ - **"Jump to latest output"** floats at level 3 when you scroll up while it runs, as in `Conversation`.
11
+
12
+ **Accessibility.** A `figure` with a focusable `log` region that isn't live, so output isn't read line by line; completion is announced once ("npm test exited with 1 after 4.2 seconds."). Tab reaches the output, Stop and Copy; arrows scroll; Escape in the output stops a running command when `stopOnEscape` is set.
13
+
14
+ **ANSI.** With `ansi` (default), red, yellow and green map to `critical`, `caution` and `positive`, blue and cyan to `accent-ink`, dim to `ink-muted`, bold to 600; everything else is stripped. The status inks clear 4.5:1 as text on `surface-ground` in both schemes (lowest: `positive` in daylight, 5.31). A carriage return overwrites its line, so progress bars settle on their last state.
15
+
16
+ **Props:** `command`, `output` (a string, or `{ text, stream?: 'stdout' | 'stderr' }[]`), `status` (`'running' | 'exited' | 'stopped'`), `exitCode`, `duration`, `onStop`, `stopOnEscape`, `maxLines` (default 400; earlier lines behind "Show N earlier lines"), `ansi`, `copyable`.
17
+
18
+ **You provide:** the chunks as they arrive, the status, and the exit code and duration when it ends.
19
+
20
+ ```tsx
21
+ <Terminal command="npm test" output={chunks} status={run.status} exitCode={run.code} duration={run.ms}
22
+ onStop={run.status === 'running' ? stop : undefined} />
23
+ ```
24
+
25
+ - Do keep stderr marked as stderr; it's how a failing line is found.
26
+ - Do say the exit, even when it's 0.
27
+ - Don't use it for code the agent is writing (that's `CodeBlock streaming`).
@@ -0,0 +1,37 @@
1
+ # ThreadList
2
+
3
+ Conversations with an assistant, beside the thread: new conversation, grouped by date, search, rename, delete, and for agents that run in the background, which ones are still working, which finished while you were away and which are waiting for you. It's built from `Sidebar`'s parts.
4
+
5
+ **Elevation: a record of conversations, with live rows.**
6
+ - **Rows** are destinations: flush at rest, lifting one level when pointed at; the current thread sits on the Sidebar's raised pill (`aria-current="page"`).
7
+ - **Working** isn't pending (nothing you sent is in flight), so it never hovers: a plate with a steady accent lamp says "Working". No spinner.
8
+ - **Needs you** ("Needs approval", "Has a question") is a caution plate with a lamp. A row can't lift into a card inside a nav without breaking the list, so the word carries it, and it's announced once.
9
+ - **Finished, unread** is a dot and the title in 600. **Failed** and **stopped** are plates in words.
10
+ - **Renaming** opens a field (a well) in the row. **Deleting** leaves a `RemovalReceipt` in the row's slot with Undo.
11
+ - **New conversation** is secondary: the composer's Send keeps the view's one primary.
12
+
13
+ **Accessibility.** A landmark (the Sidebar's `aria-label`, "Conversations") holding lists of links. Each row's name includes its state: "Q3 pricing comparison, Needs approval, 2 minutes ago". F2 renames the focused row (Enter saves, Escape cancels, focus returns to it); Delete deletes it; the row menu is named "More actions for Q3 pricing comparison". ⌘K / Ctrl+K focuses search when `searchable`. A background thread that starts needing you is announced politely once; finishing isn't announced.
14
+
15
+ **Parts:** `ThreadList` (`searchable`, `onSearch`, `searchLabel`); `ThreadListGroup` (`label`); `ThreadListItem` (`href`, `title`, `updatedAt`, `status`: `'idle' | 'working' | 'needs-input' | 'failed' | 'stopped'`, `needs`: `'approval' | 'question'`, `unread`, `current`, `onSelect`, `onRename`, `onDelete`, `onUndoDelete`, `onDeleted`, `onArchive`); `ThreadListNew`. Helper: `groupByDate(threads, getDate, now?, locale?)` → Today, Yesterday, Previous 7 days, Previous 30 days, then months.
16
+
17
+ **You provide:** the threads and their states, what selecting, renaming and deleting do. Keep a deleted thread until `onDeleted` (when its receipt closes), so Undo can bring it back.
18
+
19
+ ```tsx
20
+ <Sidebar aria-label="Conversations">
21
+ <SidebarHeader><ThreadListNew onClick={newThread} /></SidebarHeader>
22
+ <SidebarContent>
23
+ <ThreadList searchable onSearch={setQuery}>
24
+ {groupByDate(threads, (t) => t.updatedAt).map((g) => (
25
+ <ThreadListGroup key={g.label} label={g.label}>
26
+ {g.threads.map((t) => <ThreadListItem key={t.id} href={`/c/${t.id}`} current={t.id === id} title={t.title} updatedAt={t.updatedAt}
27
+ status={t.status} unread={t.unread} onRename={(name) => rename(t.id, name)} onDelete={() => remove(t.id)} onUndoDelete={() => restore(t.id)} onDeleted={() => drop(t.id)} />)}
28
+ </ThreadListGroup>
29
+ ))}
30
+ </ThreadList>
31
+ </SidebarContent>
32
+ </Sidebar>
33
+ ```
34
+
35
+ - Do say every state in words; the lamp only repeats it.
36
+ - Do keep deleted threads until the receipt closes.
37
+ - Don't show a spinner or make a working row hover.
@@ -0,0 +1,26 @@
1
+ # TimeInput
2
+
3
+ A time of day: when a scheduled report is sent, quiet hours, business hours, a maintenance window. It pairs with `DatePicker` when you need both.
4
+
5
+ **Elevation.** It's an opening, built like `DatePicker`'s typed field: the same segments in the same well. It follows the DatePicker rule for completeness: **it fills only when every segment holds a value**. A partial time ("09:––") is still a hole with its back wall showing, and the floor rises flush once the whole time is there. A time outside `min` and `max` (or `invalid`) drops it to a trench on `critical-soft`. A time zone's short name ("GMT+1") sits after the time in `ink-muted`.
6
+
7
+ **Typing.** Type digits and they move on by themselves once no further digit could fit ("9" in the hour moves to the minutes, while "1" waits for a second digit). Up and Down change a segment (minutes by `step`), Left and Right move between segments, Backspace clears, and ":" moves on. On a 12-hour clock, "a" or "p" sets AM or PM, and Up or Down flips it. The clock follows the locale unless `hourCycle` says otherwise.
8
+
9
+ **Value.** Always "HH:mm" on a 24-hour clock ("15:45"), whatever is shown, or null until the time is complete. `timeZone` only labels the time; it doesn't convert it.
10
+
11
+ **Accessibility.** Segmented [spinbuttons](https://www.w3.org/WAI/ARIA/apg/patterns/spinbutton/) ("Hour", "Minute", "AM/PM", each with its value or "Empty") in a `role="group"`, as in `DatePicker`. The time zone describes each segment. Under forced colours it's dashed while empty or partial and solid when complete. Under `contrast="more"` it gets the well's hard inner edge.
12
+
13
+ **Props:** `value`, `defaultValue`, `onValueChange` (`"HH:mm" | null`); `step` (minutes, default 1); `hourCycle` (`'auto' | 'h12' | 'h23'`); `min`, `max` ("HH:mm"); `timeZone`; `locale`; `invalid`; `disabled`; `required`; `size`; `name` (submits the value with a form).
14
+
15
+ **You provide:** a label (with `Field`), the allowed range, and the time zone when people in several zones see it.
16
+
17
+ ```tsx
18
+ <Field label="Send the report at">
19
+ <TimeInput value="09:30" onValueChange={setTime} step={15} hourCycle="auto" timeZone="Europe/Berlin" />
20
+ </Field>
21
+ ```
22
+
23
+ - Do show the time zone whenever the time isn't in the viewer's own.
24
+ - Do set `step` to the granularity you actually schedule at (15 minutes, not 1).
25
+ - Don't use `<input type="time">`; it looks different in every browser and ignores the scheme.
26
+ - Don't offer a long Select of times when people know the time they want.
@@ -0,0 +1,32 @@
1
+ # Timeline
2
+
3
+ Events in order: activity on a record ("Maya changed status to Done"), workspace activity, version history, webhook delivery attempts, an order's history, background agent runs.
4
+
5
+ **Elevation: the ground is the record, literally.** The events stand on a ground line turned upright, the same 1px `ink-muted` rule that `Stepper`, the `Drilldown` staircase and a chart's baseline stand on. Each event is a flush entry stamped onto it, with its icon on a flush plate on the line. Past events are never raised cards: a raised card claims to be live.
6
+ - **New events** arrive lifted and settle with the afterglow, at most four starting at once. "2 new events" is announced.
7
+ - **An event you caused** (`status="pending"`) hovers ("Sending…") until it lands and joins the record. A failed one drops to a trench with "Couldn't send" and Retry.
8
+ - **An event that links** (`href`) lifts when pointed at.
9
+ - **Detail** (a `DiffView`, a payload) opens behind a Details disclosure as a raised card, as an `Accordion` lid does.
10
+
11
+ With `groupBy="day"` (the default), each day starts with a heading ("Today", "Yesterday", "Monday", "March 4").
12
+
13
+ **Accessibility.** A `section` named by `aria-label`, holding an ordered list. Each event is one sentence with its `<time>` (a `RelativeTime` with its exact time in a tooltip). The line and markers are hidden from assistive technology, and day headings are real headings (`headingLevel`, default 3). Details is a disclosure button. Under forced colours the line is `CanvasText`, a pending event is dotted, and a failed one has a thick dash. Under `contrast="more"` the line is 2px.
14
+
15
+ **Props.** `Timeline`: `groupBy` (`'day' | 'none'`), `headingLevel`, `locale`, `aria-label`, and `TimelineItem` children (give each a key). `TimelineItem`: `time`, `icon`, `status` (`'pending' | 'committed' | 'failed'`), `onRetry`, `detail`, `href`, and the sentence as children.
16
+
17
+ **You provide:** the events, each as a sentence (who did what to what), with its time.
18
+
19
+ ```tsx
20
+ <Timeline aria-label="Activity">
21
+ {events.map((e) => (
22
+ <TimelineItem key={e.id} time={e.at} icon={iconFor(e)} status={e.status} detail={e.diff && <DiffView … />}>
23
+ <strong>{e.actor}</strong> changed status to <Badge>Done</Badge>
24
+ </TimelineItem>
25
+ ))}
26
+ </Timeline>
27
+ ```
28
+
29
+ - Do write each event as a sentence a screen reader can read on its own.
30
+ - Do keep keys stable, so only new events glow.
31
+ - Don't raise past events into cards.
32
+ - Don't use it for steps still to come; that's a `Stepper`.
@@ -0,0 +1,16 @@
1
+ # Toaster
2
+
3
+ Confirms what just happened, in a depth stack: the newest toast floats in front at level 4 and older ones sink behind it.
4
+
5
+ Call `toast()` from anywhere; render `Toaster` once inside `AltitudeProvider`. A new toast rises 24px into the front while the others each step back: 10px up, 5% smaller, more transparent and one shadow level lower, with their text hidden. Hovering or focusing the stack fans it out to full height and pauses every timer. Dismissed toasts sink and fade. `critical` toasts use `role="alert"`.
6
+
7
+ **On touch.** A tap on the stack fans it out and pauses the timers until a tap outside folds it, as hover does for a mouse. A horizontal swipe dismisses a toast.
8
+
9
+ **You provide:** `toast({ title, description?, tone?, duration?, action?, announce? })` or just `toast('Changes saved')`. `duration` defaults to 5000ms; 0 keeps it until dismissed. Returns an id for `toast.dismiss(id)`. `announce: false` shows a toast without announcing it, for words a screen reader has already heard where they happened: `ItemGroup`'s Undo toast repeats what the receipt in the list already announced.
10
+
11
+ - Do write the title as the outcome in past tense: "Domain verified".
12
+ - Don't use toasts for errors the person must fix in place; show those in the form with Field.
13
+
14
+ While a modal owns the document, toasts retain their visual position above the scrim, but their controls are inert and expiry timers pause with their remaining lifetime intact. Notification text is routed to a noninteractive live region inside the active modal, and the external live region is suppressed to avoid duplicate announcements. Timers and controls resume after final release. Essential modal errors belong inside that modal.
15
+
16
+ When an item leaves a list, its result is shown first in the item's own slot ([RemovalReceipt](../RemovalReceipt/README.md)); once that slot has closed, a toast carries Undo, as in the [Actions in place recipe](../../visual/ActionsInPlace.md). A timed toast may supplement recovery, but must never be its only route: keep a durable Archived/Restore path after a brief Undo opportunity expires. Withdraw a toast's Undo (`toast.dismiss(id)`) as soon as a newer revision makes it invalid.
@@ -0,0 +1,33 @@
1
+ # Toggle and ToggleGroup
2
+
3
+ Buttons that stay on: bold and italic in a formatting bar, a list/board/calendar view switch, "Show archived", thumbs up or down on an AI answer.
4
+
5
+ **Elevation: on is a choice, not a held press.** Pressing a button returns it to the ground, and loading holds it there, so an "on" button drawn at ground level would look like it's loading. A toggle that's on is a choice, so it's built like the Switch: its face is cut into a shallow well (`well`) with `accent-soft` poured in and its label in `accent-ink`. Off is the slab it started as: a ghost (ground) or `secondary` (level 1). Turning on sinks on `ease-sink`, and turning off rises on `ease-rise`. Pointing at an on toggle previews off (feedforward): the well lightens toward the raised face it would become. This is the one exception to "never style a button as sunk" (see [Elevation](../../visual/Elevation.md), "Rules").
6
+
7
+ **ToggleGroup.**
8
+ - `type="single"` is one choice: segmented `Tabs` without panels, built with the same classes. The options sit in a well and one raised pill glides between them. It's a radio group, so arrow keys move and choose.
9
+ - `type="multiple"` is a row of Toggles, each with `aria-pressed`. Arrow keys move and Space toggles.
10
+
11
+ Either way the group is **one tab stop**, and returning to it lands on the item you left. Inside a `Toolbar`, a ToggleGroup leaves the arrow keys to the toolbar, so the whole bar is one tab stop.
12
+
13
+ **Accessibility.** A Toggle is a [toggle button](https://www.w3.org/WAI/ARIA/apg/patterns/button/): its state is `aria-pressed`, and **its name never changes with state** (no "Bold" → "Unbold"). Icon-only toggles need an `aria-label`. A single-choice group is a [radio group](https://www.w3.org/WAI/ARIA/apg/patterns/radio/) with `aria-checked`. Under forced colours an on toggle is a dashed border with a `Highlight` fill, and an off `secondary` toggle has a 1px border. Under `contrast="more"` the on well gets the well's hard inner edge. Under reduced motion nothing travels: the pill appears in place and the fill still changes.
14
+
15
+ **Props.** `Toggle`: `pressed`, `defaultPressed`, `onPressedChange`, `variant` (`'ghost' | 'secondary'`), and the `Button` props (`size`, `square`, `loading`, …). `ToggleGroup`: `type` (`'single' | 'multiple'`), `value`, `defaultValue`, `onValueChange`, `variant` and `size` (for `multiple`), `orientation`, `disabled`, `aria-label`. `ToggleGroupItem`: `value`.
16
+
17
+ **You provide:** a label that names the thing being toggled ("Bold", "Show archived"), not the action, and an `aria-label` on each group.
18
+
19
+ ```tsx
20
+ <Toggle aria-label="Bold" pressed={bold} onPressedChange={setBold} square><BoldIcon /></Toggle>
21
+
22
+ <ToggleGroup type="single" value={view} onValueChange={setView} aria-label="View">
23
+ <ToggleGroupItem value="list">List</ToggleGroupItem>
24
+ <ToggleGroupItem value="board">Board</ToggleGroupItem>
25
+ </ToggleGroup>
26
+
27
+ <ToggleGroup type="multiple" value={marks} onValueChange={setMarks} aria-label="Text style" />
28
+ ```
29
+
30
+ - Do use a Toggle for a tool or a view that stays on; use a `Switch` for a setting that's saved.
31
+ - Do use `type="single"` for mutually exclusive views; use `Tabs` when each option has its own panel.
32
+ - Don't change a toggle's label when it turns on.
33
+ - Don't use a Toggle for an action that happens once; that's a `Button`.
@@ -0,0 +1,37 @@
1
+ # Toolbar
2
+
3
+ A row of small actions with one tab stop: a formatting bar, a table's tools, a comment composer, a canvas's tools.
4
+
5
+ **Elevation.** The bar is settled: it lies on the ground, with a `line` rim when `bordered`. Its tools are live: ghost buttons that lift one level when pointed at and press to the ground. Latched tools (bold, a quote block) are `Toggle`s, so on is a well with the accent poured in. Groups are parted by decorative `Separator`s.
6
+
7
+ **One tab stop.** Tab reaches the bar once, Left and Right (Up and Down when vertical) move between tools, Home and End jump, and returning to the bar lands on the tool you left. A `ToggleGroup` inside a Toolbar leaves the arrow keys to the bar, so a 12-tool bar costs one Tab, not twelve.
8
+
9
+ **Overflow.** `overflow="wrap"` (default) wraps tools onto another row. `overflow="menu"` hides the tools that don't fit, from the end, and offers them in a "More tools" menu (`DropdownMenu`, level 3). There, toggles keep their state as checkable items (`menuitemcheckbox`). A separator never ends the visible row. Tools moving into the menu don't animate.
10
+
11
+ **Accessibility.** The [toolbar pattern](https://www.w3.org/WAI/ARIA/apg/patterns/toolbar/): `role="toolbar"` with an `aria-label` (required) and `aria-orientation`. Icon-only tools need an `aria-label`: a development warning names any tool without one. Under forced colours a hovered or focused tool gets a 1px border and a latched one is dashed (the Toggle's fallback).
12
+
13
+ **Parts.**
14
+ - `Toolbar`: `aria-label`, `orientation` (`'horizontal' | 'vertical'`), `overflow` (`'wrap' | 'menu'`), `bordered`.
15
+ - `ToolbarGroup`: tools that belong together (`role="group"`; give it an `aria-label` if the group has a name).
16
+ - `ToolbarSeparator`: a decorative line between groups.
17
+ - `ToolbarButton`: a ghost `Button`.
18
+ - `ToolbarToggle`: a ghost `Toggle`.
19
+ - A `ToggleGroup` works inside it as it is.
20
+
21
+ **You provide:** a label for the bar and for every icon-only tool, and the actions.
22
+
23
+ ```tsx
24
+ <Toolbar aria-label="Formatting" overflow="menu">
25
+ <ToolbarGroup>
26
+ <ToolbarToggle aria-label="Bold" pressed={b} onPressedChange={setB} square><Bold /></ToolbarToggle>
27
+ <ToolbarToggle aria-label="Italic" pressed={i} onPressedChange={setI} square><Italic /></ToolbarToggle>
28
+ </ToolbarGroup>
29
+ <ToolbarSeparator />
30
+ <ToolbarButton aria-label="Insert link" onClick={link} square><Link2 /></ToolbarButton>
31
+ </Toolbar>
32
+ ```
33
+
34
+ - Do put the tools used most first: overflow takes them from the end.
35
+ - Do use a Toolbar for a set of small, related tools; a page's main actions are separate `Button`s.
36
+ - Don't put text fields inside a Toolbar: arrow keys would stop moving the caret.
37
+ - Don't give tools a raised (`secondary` or `primary`) variant; in a toolbar they're ghosts.
@@ -0,0 +1,12 @@
1
+ # Tooltip
2
+
3
+ Names an icon-only control or adds a short hint; it floats at level 3 above its anchor.
4
+
5
+ It opens after `delay` (450ms) on hover or immediately on keyboard focus, rising 4px from the anchor while its shadow grows from `elev-1` to `elev-3` (`dur-travel-3`). After one tooltip closes, neighbours open instantly for 400ms, so moving along a toolbar doesn't make you wait twice. Escape closes it.
6
+
7
+ **On a narrow screen.** It opens where its `side` and `align` put it, then shifts sideways to stay 8px inside the screen, and it's never wider than the screen less 16px.
8
+
9
+ **You provide:** `content` (a few words, optionally a shortcut), one focusable child, optionally `side="bottom"`.
10
+
11
+ - Do use `side="right"` for a rail of icons (a collapsed `Sidebar`), where a tooltip above would cover the next icon.
12
+ - Don't put anything interactive in a tooltip, and don't hide essential information in one.
@@ -0,0 +1,29 @@
1
+ # Tour
2
+
3
+ A few steps pointing at parts of the interface, for onboarding or a feature launch: "Projects moved here", "New: filter by owner". Keep it short and skippable.
4
+
5
+ **Elevation: a note in the air, not a summit.**
6
+ - **One step at a time floats at level 3**, a `Popover`-built card beside its target, with "Step 2 of 4", a heading, the body, progress dots, Skip tour, Back and Next (Done on the last step).
7
+ - **The target takes a 2px `accent-ink` ring**, as `DataGrid`'s active cell does. It's a ring, not a lift, because the target may not be something you press.
8
+ - **It never dims the page.** Dimming means the page is receding, which is the summit's privilege (level 5, and only one thing sits there). Tours that dim with a cut-out spotlight block the very thing they point at.
9
+ - **It isn't modal**: the target stays usable.
10
+
11
+ **Motion.** Each step rises at its target on `ease-rise`. Moving on lifts the step off and the next one rises at its own target, so nothing flies across the screen. Under reduced motion the steps cross-fade.
12
+
13
+ **Accessibility.** A non-modal [dialog](https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/) (`role="dialog"`, `aria-modal="false"`) named by the step's heading. The target is `aria-describedby` the step's body while it shows. Focus moves to the step when it opens. Tab reaches the target and back. Escape ends the tour. When the tour ends, focus returns to where it was. A target that's off screen is scrolled into view. Under forced colours the card has a 2px border and the ring is `Highlight`. Under `contrast="more"` the card gets a hard slab.
14
+
15
+ **Props:** `steps` (`{ target, title, body? }[]`, where `target` is a selector or an element); `open`, `defaultOpen`, `onOpenChange`; `onFinish(how)` (`'done'` or `'skipped'`); `onStepChange`.
16
+
17
+ **You provide:** the steps, when to start it, and remembering it's been seen (in `onFinish`).
18
+
19
+ ```tsx
20
+ <Tour open={!seen} onOpenChange={…} onFinish={markSeen} steps={[
21
+ { target: '#nav-projects', title: 'Projects moved here', body: 'All your projects are now in the sidebar.' },
22
+ { target: '[data-tour=filter-owner]', title: 'Filter by owner', body: 'Type a name to see only their projects.' },
23
+ ]} />
24
+ ```
25
+
26
+ - Do keep a tour to a few steps, and always let people skip it.
27
+ - Do remember when it's been finished or skipped.
28
+ - Don't dim the page or block the target.
29
+ - Don't use a tour to explain an interface that should explain itself.
@@ -0,0 +1,37 @@
1
+ # Trace
2
+
3
+ The steps an agent takes (searching, calling tools, reasoning, waiting for approval), so people can see what it did, what it's doing, what it plans, and where it failed.
4
+
5
+ **Elevation: a process, drawn as a vertical `Stepper`.** Stepper's rule, verbatim: "done steps are flush in the record with a stamped check, the current one stands raised on its wall, steps to come are imprints."
6
+ - **Done:** flush, stamped check, "Done · 820ms".
7
+ - **Running:** raised on its wall, "Running", `aria-current="step"`. **No spinner:** loading in Altitude is a held press or an imprint, never a spinner. The mark holds a small steady lamp.
8
+ - **Planned:** an imprint, "Planned". If the agent can't know its plan, leave planned steps out: steps then appear as they start, with the afterglow (at most four at once).
9
+ - **Failed:** drops to a trench with a `critical` rim, the reason, and Retry.
10
+ - **Waiting for approval:** raised like a running step, holding a `Confirmation`, the "Proposed" reading.
11
+ - **Detail** sits under its step behind `Accordion` lids: `TraceToolCall` (the tool's name; input and output in `CodeBlock`s) and `TraceReasoning` ("Thought for 2.1s", closed by default).
12
+
13
+ **Accessibility.** An ordered list (as `Stepper`), each step's state in words. Step changes are announced politely and throttled ("Done: Read 3 articles"), because an agent can finish several steps a second. Failures and approval requests are announced at once. Under forced colours a failed step is dashed thick.
14
+
15
+ **Parts:** `Trace` (`aria-label`); `TraceStep` (`title`, `status` (`'planned' | 'running' | 'done' | 'failed' | 'awaiting-approval'`), `duration` (ms), `error`, `onRetry`); `TraceToolCall` (`name`, `input`, `output`, `defaultOpen`); `TraceReasoning` (`duration`).
16
+
17
+ **Subagents.** A `Trace` inside a `TraceStep` is a nested run: a nested list behind a lid that says how it's going ("6 steps · 1 running"), open while something in it is running, waiting or failed, closed when it's all done. `TraceParallel label="Subagents"` groups steps that run at once, with a bracket and "3 in parallel · 2 of 3 done · 1 failed"; several can be raised at once, each because it's in play. `status="auto"` derives a parent from its children: running while any run, failed once the rest stop, done only when all are. Nested steps announce through the outer Trace with their run's name.
18
+
19
+ **Reasoning as it streams.** `TraceReasoning status="streaming"` says "Thinking…" on its lid; opened, the text follows its end as it arrives, behind a dashed rule (not the record yet), until the person scrolls up. Complete, it's "Thought for 12s"; `status="redacted"` says "Reasoning hidden by the provider" with nothing to open. No shimmer, pulsing dots or ticking counter.
20
+
21
+ **You provide:** the steps and their status from your agent's events.
22
+
23
+ ```tsx
24
+ <Trace aria-label="Agent run">
25
+ {run.steps.map((s) => (
26
+ <TraceStep key={s.id} title={s.title} status={s.status} duration={s.ms}
27
+ onRetry={s.status === 'failed' ? () => retry(s.id) : undefined}>
28
+ {s.tool && <TraceToolCall name={s.tool.name} input={s.tool.input} output={s.tool.output} />}
29
+ </TraceStep>
30
+ ))}
31
+ </Trace>
32
+ ```
33
+
34
+ - Do say why a step failed.
35
+ - Do put an approval request in the step that's waiting.
36
+ - Don't draw spinners or nested cards.
37
+ - Don't invent planned steps the agent hasn't planned.
@@ -0,0 +1,86 @@
1
+ # Tree
2
+
3
+ A hierarchy you see all at once and change in place: rename a node (F2), add a sibling or a child, carry a node to a new place (Space, or drag), collapse and expand, remove. Page trees, file explorers, nested categories, outlines.
4
+
5
+ Use `Drilldown` instead when people work one level at a time and don't need the siblings of ancestors in view (settings, a mobile hierarchy); use `Accordion` for one level of disclosure.
6
+
7
+ ```tsx
8
+ const tree = React.useRef<TreeHandle>(null);
9
+
10
+ <Tree<Page>
11
+ ref={tree}
12
+ aria-label="Pages"
13
+ items={pages}
14
+ getKey={(p) => p.id}
15
+ getLabel={(p) => p.title}
16
+ getChildren={(p) => p.children}
17
+ hasChildren={(p) => p.kind === 'folder'}
18
+ expanded={expanded} onExpandedChange={setExpanded}
19
+ selected={selected} onSelectedChange={setSelected}
20
+ renderItem={(p) => <><PageIcon />{p.title}</>}
21
+ onRename={async (p, title) => { const r = await api.rename(p.id, title, p.revision); if (r.ok) setPages(r.pages); return r; }}
22
+ onMove={async ({ id, to }) => { const r = await api.move(id, to.parentId, to.index); if (r.ok) setPages(r.pages); return r; }}
23
+ canDrop={({ to }) => to.parentId === null || isFolder(to.parentId)}
24
+ onCreate={async ({ parentId, index, name }) => { const r = await api.create(parentId, index, name); if (r.ok) setPages(r.pages); return r.ok ? { ok: true, value: r.page } : r; }}
25
+ onRemove={(p) => archiveWithUndo(p)}
26
+ />
27
+ <Button onClick={() => tree.current?.startCreate(null)}>New page</Button>
28
+ ```
29
+
30
+ The tree is controlled by `items`. Each write follows the `InlineEdit` contract: return `{ ok: true }` (for a create, `{ ok: true, value }` with the new item) after `items` reflects the change, or `{ ok: false, error }` for a known failure. A throw or malformed result shows a generic retry message. One write runs at a time.
31
+
32
+ ## Elevation
33
+
34
+ - **Nodes** are the record: flush rows on the ground, indented `space-4` per level, like `Table` rows. A node lifts one level on hover; the **selected** node is tinted `accent-soft` and raised one level.
35
+ - **Expanding** is disclosure of existing records, so it's a chevron turn (`ease-settle`), not a rise. A node whose children are loading shows an imprint row (`loadChildren`).
36
+ - **Renaming** sinks the label into a well (InlineEdit's field); an invalid or rejected name drops to a trench on `critical-soft`. A **pending** write hovers at level 2 after `dur-grace`; a **failed** one drops to a trench with a critical rim.
37
+ - **Carried** (keyboard): the node floats at level 3, tilted, and is shown where it will land. **Carried** (pointer): a floating copy follows the pointer with the count of what it carries; the top or bottom quarter of a row shows a **slot**, a thin well at the target's depth; the middle of a row opens that node into a **trench with an accent rim** (a critical rim when `canDrop` refuses it). Hovering a closed node for 600ms opens it (spring-loaded).
38
+ - **A new node** is named in a well at its insertion point; once added it settles with an afterglow.
39
+
40
+ Without height: `aria-level`, `aria-setsize`, `aria-posinset`, `aria-expanded`, `aria-selected` and `aria-busy` on every node (a carried node is announced through the live region, since `aria-pressed` isn't allowed on a tree item); moves are announced with their depth (“Moved Logo into Brand, position 1 of 3”). Forced colours: selected 1px Highlight, carried 2px, slots dashed, drop-into 3px dashed, pending dotted, failed thick dashed. `contrast="more"`: the carried node gets a 3px slab.
41
+
42
+ ## Keyboard
43
+
44
+ The APG tree view, plus editing:
45
+
46
+ | Key | |
47
+ |---|---|
48
+ | ↑ / ↓ | previous / next visible node |
49
+ | → / ← | expand, or go to the first child / collapse, or go to the parent (mirrored in RTL) |
50
+ | Home / End | first / last visible node |
51
+ | letters | type-ahead by label |
52
+ | `*` | expand every sibling |
53
+ | Enter | `onAction`, or select |
54
+ | F2 | rename (Enter saves, Escape cancels; IME composition is left alone) |
55
+ | Ctrl/Cmd+Enter, Ctrl/Cmd+Shift+Enter | add a sibling after / a child inside |
56
+ | Space | pick up (announced): ↑/↓ move between slots, → into the node above, ← out of the parent, Space or Enter drop, Escape cancel. Without `onMove`, Space selects |
57
+ | Alt+↑ / Alt+↓ | move one step without picking up |
58
+ | Delete | `onRemove`; focus moves to the next node, else the previous |
59
+
60
+ Only one node is in the Tab sequence. `TreeHandle.startCreate(parentId, index?)` and `startRename(id)` give pointer users the same actions from a toolbar.
61
+
62
+ ## Props
63
+
64
+ | Prop | |
65
+ |---|---|
66
+ | `items`, `getKey`, `getLabel`, `getChildren`, `hasChildren`, `loadChildren` | the nested data; `hasChildren` shows a chevron before children are loaded; `loadChildren` is called on the first expand |
67
+ | `expanded`, `defaultExpanded`, `onExpandedChange` | open nodes, by key |
68
+ | `selectionMode`, `selected`, `defaultSelected`, `onSelectedChange` | `'single'` (default) or `'none'` |
69
+ | `onAction`, `renderItem` | Enter's action; draw a node's label |
70
+ | `onRename`, `onMove`, `canDrop`, `onCreate`, `onRemove` | the writes, as above. `TreeTarget.index` is the place among the children once the node has left its old one |
71
+
72
+ ## You provide
73
+
74
+ The data, the writes and their concurrency guards, and the removal policy (recovery or confirmation, as in Actions in place). Multi-node carrying isn't supported.
75
+
76
+ ## Do and don't
77
+
78
+ - **Do** give pointer users a visible “New” and “Rename” (through `TreeHandle`); the keyboard shortcuts aren't discoverable on their own.
79
+ - **Do** use `canDrop` to keep pages out of pages that can't hold them.
80
+ - **Don't** use a tree for a flat list, or for more than a few levels people need to see at once.
81
+
82
+ ## Demo and verification
83
+
84
+ The `Tree` section of `build/previews.txt` is a page tree with a toolbar; the demo service rejects names over 40 characters and moves out of Archive. `node checks/tree.mjs` (from `build/`; `ALT_FIXTURE_ROOT` selects React 19 fixtures) covers the APG keys and RTL, rename (cancel, rejection, save), create (sibling and child), keyboard carry (up, down, indent, outdent, cancel, drop, a rejected move, `canDrop`), Alt+↓, Delete with focus repair, pointer carry into a closed node (spring-loaded) and onto a slot, and 320/960px layouts in both schemes under reduced motion, forced colours and `contrast="more"`.
85
+ **On touch.** With `onMove`, a swipe on a row scrolls; a press-and-hold picks it up, then a drag moves it.
86
+
@@ -90,6 +90,24 @@
90
90
  "status": "research-preprint",
91
91
  "applicability": "Ecommerce and sign-up components generated by language models; prevalence findings, not a complete taxonomy.",
92
92
  "reviewed": "2026-11-01"
93
+ },
94
+ {
95
+ "id": "altitude-design-guide",
96
+ "authority": "altitude-policy",
97
+ "title": "Altitude design guide",
98
+ "url": "visual/README.md",
99
+ "status": "project-policy",
100
+ "applicability": "Every screen built with Altitude: the laws, content, colour and component use.",
101
+ "reviewed": "2026-11-02"
102
+ },
103
+ {
104
+ "id": "altitude-charts",
105
+ "authority": "altitude-policy",
106
+ "title": "Altitude Charts",
107
+ "url": "visual/Charts.md",
108
+ "status": "project-policy",
109
+ "applicability": "Every chart, stat and meter.",
110
+ "reviewed": "2026-11-02"
93
111
  }
94
112
  ]
95
113
  }