altitude-design 0.3.2 → 0.4.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.
Files changed (294) hide show
  1. package/README.md +2 -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 +60 -0
  6. package/dist/guidance/components/Accordion/README.md +32 -0
  7. package/dist/guidance/components/AgentFleet/README.md +81 -0
  8. package/dist/guidance/components/AgentLanes/README.md +53 -0
  9. package/dist/guidance/components/AgentRequests/README.md +76 -0
  10. package/dist/guidance/components/Alert/README.md +71 -0
  11. package/dist/guidance/components/AltitudeProvider/README.md +111 -0
  12. package/dist/guidance/components/AppShell/README.md +121 -0
  13. package/dist/guidance/components/AreaChart/README.md +101 -0
  14. package/dist/guidance/components/Artifact/README.md +99 -0
  15. package/dist/guidance/components/Avatar/README.md +75 -0
  16. package/dist/guidance/components/Await/README.md +128 -0
  17. package/dist/guidance/components/Badge/README.md +66 -0
  18. package/dist/guidance/components/BarChart/README.md +109 -0
  19. package/dist/guidance/components/BarList/README.md +87 -0
  20. package/dist/guidance/components/Board/README.md +98 -0
  21. package/dist/guidance/components/Breadcrumb/README.md +66 -0
  22. package/dist/guidance/components/Button/README.md +58 -0
  23. package/dist/guidance/components/Card/README.md +52 -0
  24. package/dist/guidance/components/ChartBrush/README.md +98 -0
  25. package/dist/guidance/components/ChartContainer/README.md +229 -0
  26. package/dist/guidance/components/ChartLegend/README.md +78 -0
  27. package/dist/guidance/components/ChartReference/README.md +115 -0
  28. package/dist/guidance/components/ChartTooltip/README.md +101 -0
  29. package/dist/guidance/components/Checkbox/README.md +46 -0
  30. package/dist/guidance/components/Checklist/README.md +137 -0
  31. package/dist/guidance/components/CodeBlock/README.md +72 -0
  32. package/dist/guidance/components/ColorPicker/README.md +69 -0
  33. package/dist/guidance/components/Combobox/README.md +51 -0
  34. package/dist/guidance/components/CommandPalette/README.md +204 -0
  35. package/dist/guidance/components/CommentThread/README.md +81 -0
  36. package/dist/guidance/components/Confirmation/README.md +94 -0
  37. package/dist/guidance/components/ContextMenu/README.md +81 -0
  38. package/dist/guidance/components/Conversation/README.md +169 -0
  39. package/dist/guidance/components/CopyButton/README.md +54 -0
  40. package/dist/guidance/components/DataGrid/README.md +157 -0
  41. package/dist/guidance/components/DatePicker/README.md +116 -0
  42. package/dist/guidance/components/Dialog/README.md +204 -0
  43. package/dist/guidance/components/DiffView/README.md +107 -0
  44. package/dist/guidance/components/DotPlot/README.md +82 -0
  45. package/dist/guidance/components/Drilldown/README.md +43 -0
  46. package/dist/guidance/components/DropZone/README.md +71 -0
  47. package/dist/guidance/components/DropdownMenu/README.md +63 -0
  48. package/dist/guidance/components/EditRegion/README.md +263 -0
  49. package/dist/guidance/components/EmptyState/README.md +66 -0
  50. package/dist/guidance/components/FeatureGate/README.md +67 -0
  51. package/dist/guidance/components/Field/README.md +46 -0
  52. package/dist/guidance/components/FilterBar/README.md +132 -0
  53. package/dist/guidance/components/FormattedNumber/README.md +81 -0
  54. package/dist/guidance/components/Heatmap/README.md +116 -0
  55. package/dist/guidance/components/HoldButton/README.md +48 -0
  56. package/dist/guidance/components/HoverCard/README.md +71 -0
  57. package/dist/guidance/components/InlineCitation/README.md +56 -0
  58. package/dist/guidance/components/InlineEdit/README.md +379 -0
  59. package/dist/guidance/components/Input/README.md +63 -0
  60. package/dist/guidance/components/InputOTP/README.md +76 -0
  61. package/dist/guidance/components/ItemGroup/README.md +193 -0
  62. package/dist/guidance/components/Kbd/README.md +36 -0
  63. package/dist/guidance/components/LineChart/README.md +109 -0
  64. package/dist/guidance/components/Link/README.md +69 -0
  65. package/dist/guidance/components/Markdown/README.md +113 -0
  66. package/dist/guidance/components/Meter/README.md +81 -0
  67. package/dist/guidance/components/NotificationCenter/README.md +67 -0
  68. package/dist/guidance/components/NumberInput/README.md +66 -0
  69. package/dist/guidance/components/PageHeader/README.md +74 -0
  70. package/dist/guidance/components/Pagination/README.md +58 -0
  71. package/dist/guidance/components/PasswordInput/README.md +70 -0
  72. package/dist/guidance/components/PermissionMatrix/README.md +68 -0
  73. package/dist/guidance/components/Pile/README.md +54 -0
  74. package/dist/guidance/components/PlanPicker/README.md +62 -0
  75. package/dist/guidance/components/Popover/README.md +48 -0
  76. package/dist/guidance/components/Progress/README.md +41 -0
  77. package/dist/guidance/components/PromptInput/README.md +138 -0
  78. package/dist/guidance/components/PropertyList/README.md +137 -0
  79. package/dist/guidance/components/Question/README.md +81 -0
  80. package/dist/guidance/components/RadioGroup/README.md +47 -0
  81. package/dist/guidance/components/RelativeTime/README.md +73 -0
  82. package/dist/guidance/components/RemovalReceipt/README.md +111 -0
  83. package/dist/guidance/components/Resizable/README.md +65 -0
  84. package/dist/guidance/components/SaveBar/README.md +65 -0
  85. package/dist/guidance/components/SavedViews/README.md +82 -0
  86. package/dist/guidance/components/ScatterChart/README.md +104 -0
  87. package/dist/guidance/components/Schedule/README.md +142 -0
  88. package/dist/guidance/components/ScrollArea/README.md +35 -0
  89. package/dist/guidance/components/SecretField/README.md +74 -0
  90. package/dist/guidance/components/Select/README.md +97 -0
  91. package/dist/guidance/components/Separator/README.md +70 -0
  92. package/dist/guidance/components/Sheet/README.md +126 -0
  93. package/dist/guidance/components/Skeleton/README.md +50 -0
  94. package/dist/guidance/components/Slider/README.md +60 -0
  95. package/dist/guidance/components/SortableList/README.md +45 -0
  96. package/dist/guidance/components/Sparkline/README.md +87 -0
  97. package/dist/guidance/components/SplitButton/README.md +65 -0
  98. package/dist/guidance/components/Stat/README.md +92 -0
  99. package/dist/guidance/components/Stepper/README.md +90 -0
  100. package/dist/guidance/components/Suggestions/README.md +87 -0
  101. package/dist/guidance/components/Surface/README.md +48 -0
  102. package/dist/guidance/components/Switch/README.md +38 -0
  103. package/dist/guidance/components/SyncIndicator/README.md +78 -0
  104. package/dist/guidance/components/Table/README.md +138 -0
  105. package/dist/guidance/components/Tabs/README.md +73 -0
  106. package/dist/guidance/components/TagInput/README.md +116 -0
  107. package/dist/guidance/components/Terminal/README.md +69 -0
  108. package/dist/guidance/components/ThreadList/README.md +110 -0
  109. package/dist/guidance/components/TimeInput/README.md +63 -0
  110. package/dist/guidance/components/Timeline/README.md +87 -0
  111. package/dist/guidance/components/Toaster/README.md +54 -0
  112. package/dist/guidance/components/Toggle/README.md +85 -0
  113. package/dist/guidance/components/Toolbar/README.md +106 -0
  114. package/dist/guidance/components/Tooltip/README.md +41 -0
  115. package/dist/guidance/components/Tour/README.md +73 -0
  116. package/dist/guidance/components/Trace/README.md +129 -0
  117. package/dist/guidance/components/Tree/README.md +171 -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/dist/types/agent-fleet.d.ts +28 -0
  137. package/dist/types/ai-label.d.ts +7 -0
  138. package/dist/types/app-shell.d.ts +23 -0
  139. package/dist/types/area-chart.d.ts +8 -0
  140. package/dist/types/artifact.d.ts +14 -0
  141. package/dist/types/bar-chart.d.ts +6 -0
  142. package/dist/types/bar-list.d.ts +6 -0
  143. package/dist/types/breadcrumb.d.ts +6 -0
  144. package/dist/types/chart-brush.d.ts +6 -0
  145. package/dist/types/chart.d.ts +29 -1
  146. package/dist/types/checklist.d.ts +6 -0
  147. package/dist/types/code-block.d.ts +6 -0
  148. package/dist/types/collections.d.ts +15 -0
  149. package/dist/types/color-picker.d.ts +6 -0
  150. package/dist/types/command.d.ts +13 -0
  151. package/dist/types/comment-thread.d.ts +11 -0
  152. package/dist/types/confirmation.d.ts +8 -0
  153. package/dist/types/context-menu.d.ts +11 -0
  154. package/dist/types/controls.d.ts +174 -10
  155. package/dist/types/conversation.d.ts +21 -0
  156. package/dist/types/copy-button.d.ts +6 -0
  157. package/dist/types/core.d.ts +31 -0
  158. package/dist/types/data-grid.d.ts +25 -0
  159. package/dist/types/date-picker.d.ts +6 -0
  160. package/dist/types/diff-view.d.ts +8 -0
  161. package/dist/types/dot-plot.d.ts +6 -0
  162. package/dist/types/edit-region.d.ts +23 -0
  163. package/dist/types/empty.d.ts +9 -0
  164. package/dist/types/feature-gate.d.ts +8 -0
  165. package/dist/types/filter-bar.d.ts +8 -0
  166. package/dist/types/formatted-number.d.ts +6 -0
  167. package/dist/types/heatmap.d.ts +6 -0
  168. package/dist/types/inline-citation.d.ts +6 -0
  169. package/dist/types/inline-edit.d.ts +7 -0
  170. package/dist/types/input-otp.d.ts +11 -0
  171. package/dist/types/item-group.d.ts +29 -0
  172. package/dist/types/line-chart.d.ts +6 -0
  173. package/dist/types/link.d.ts +8 -1
  174. package/dist/types/markdown.d.ts +6 -0
  175. package/dist/types/meter.d.ts +6 -0
  176. package/dist/types/nav.d.ts +6 -0
  177. package/dist/types/notifications.d.ts +6 -0
  178. package/dist/types/number-input.d.ts +6 -0
  179. package/dist/types/overlays.d.ts +61 -0
  180. package/dist/types/page-header.d.ts +14 -0
  181. package/dist/types/pagination.d.ts +6 -0
  182. package/dist/types/password-input.d.ts +6 -0
  183. package/dist/types/permission-matrix.d.ts +7 -0
  184. package/dist/types/pile.d.ts +6 -0
  185. package/dist/types/plan-picker.d.ts +6 -0
  186. package/dist/types/prompt-input.d.ts +11 -0
  187. package/dist/types/property-list.d.ts +17 -0
  188. package/dist/types/question.d.ts +11 -0
  189. package/dist/types/relative-time.d.ts +8 -1
  190. package/dist/types/removal-receipt.d.ts +18 -1
  191. package/dist/types/save-bar.d.ts +6 -0
  192. package/dist/types/saved-views.d.ts +12 -0
  193. package/dist/types/scatter-chart.d.ts +8 -0
  194. package/dist/types/schedule.d.ts +21 -0
  195. package/dist/types/secret-field.d.ts +6 -0
  196. package/dist/types/select.d.ts +12 -0
  197. package/dist/types/separator.d.ts +8 -1
  198. package/dist/types/sheet.d.ts +19 -0
  199. package/dist/types/sparkline.d.ts +6 -0
  200. package/dist/types/split-button.d.ts +7 -0
  201. package/dist/types/stat.d.ts +7 -0
  202. package/dist/types/stepper.d.ts +12 -0
  203. package/dist/types/suggestions.d.ts +10 -0
  204. package/dist/types/sync-indicator.d.ts +8 -0
  205. package/dist/types/tag-input.d.ts +8 -0
  206. package/dist/types/terminal.d.ts +6 -0
  207. package/dist/types/thread-list.d.ts +16 -0
  208. package/dist/types/time-input.d.ts +6 -0
  209. package/dist/types/timeline.d.ts +9 -0
  210. package/dist/types/toggle.d.ts +6 -0
  211. package/dist/types/toolbar.d.ts +20 -0
  212. package/dist/types/tour.d.ts +10 -0
  213. package/dist/types/trace.d.ts +16 -0
  214. package/dist/types/tree.d.ts +21 -0
  215. package/dist/types-cjs/agent-fleet.d.cts +28 -0
  216. package/dist/types-cjs/ai-label.d.cts +7 -0
  217. package/dist/types-cjs/app-shell.d.cts +23 -0
  218. package/dist/types-cjs/area-chart.d.cts +8 -0
  219. package/dist/types-cjs/artifact.d.cts +14 -0
  220. package/dist/types-cjs/bar-chart.d.cts +6 -0
  221. package/dist/types-cjs/bar-list.d.cts +6 -0
  222. package/dist/types-cjs/breadcrumb.d.cts +6 -0
  223. package/dist/types-cjs/chart-brush.d.cts +6 -0
  224. package/dist/types-cjs/chart.d.cts +29 -1
  225. package/dist/types-cjs/checklist.d.cts +6 -0
  226. package/dist/types-cjs/code-block.d.cts +6 -0
  227. package/dist/types-cjs/collections.d.cts +15 -0
  228. package/dist/types-cjs/color-picker.d.cts +6 -0
  229. package/dist/types-cjs/command.d.cts +13 -0
  230. package/dist/types-cjs/comment-thread.d.cts +11 -0
  231. package/dist/types-cjs/confirmation.d.cts +8 -0
  232. package/dist/types-cjs/context-menu.d.cts +11 -0
  233. package/dist/types-cjs/controls.d.cts +174 -10
  234. package/dist/types-cjs/conversation.d.cts +21 -0
  235. package/dist/types-cjs/copy-button.d.cts +6 -0
  236. package/dist/types-cjs/core.d.cts +31 -0
  237. package/dist/types-cjs/data-grid.d.cts +25 -0
  238. package/dist/types-cjs/date-picker.d.cts +6 -0
  239. package/dist/types-cjs/diff-view.d.cts +8 -0
  240. package/dist/types-cjs/dot-plot.d.cts +6 -0
  241. package/dist/types-cjs/edit-region.d.cts +23 -0
  242. package/dist/types-cjs/empty.d.cts +9 -0
  243. package/dist/types-cjs/feature-gate.d.cts +8 -0
  244. package/dist/types-cjs/filter-bar.d.cts +8 -0
  245. package/dist/types-cjs/formatted-number.d.cts +6 -0
  246. package/dist/types-cjs/heatmap.d.cts +6 -0
  247. package/dist/types-cjs/inline-citation.d.cts +6 -0
  248. package/dist/types-cjs/inline-edit.d.cts +7 -0
  249. package/dist/types-cjs/input-otp.d.cts +11 -0
  250. package/dist/types-cjs/item-group.d.cts +29 -0
  251. package/dist/types-cjs/line-chart.d.cts +6 -0
  252. package/dist/types-cjs/link.d.cts +8 -1
  253. package/dist/types-cjs/markdown.d.cts +6 -0
  254. package/dist/types-cjs/meter.d.cts +6 -0
  255. package/dist/types-cjs/nav.d.cts +6 -0
  256. package/dist/types-cjs/notifications.d.cts +6 -0
  257. package/dist/types-cjs/number-input.d.cts +6 -0
  258. package/dist/types-cjs/overlays.d.cts +61 -0
  259. package/dist/types-cjs/page-header.d.cts +14 -0
  260. package/dist/types-cjs/pagination.d.cts +6 -0
  261. package/dist/types-cjs/password-input.d.cts +6 -0
  262. package/dist/types-cjs/permission-matrix.d.cts +7 -0
  263. package/dist/types-cjs/pile.d.cts +6 -0
  264. package/dist/types-cjs/plan-picker.d.cts +6 -0
  265. package/dist/types-cjs/prompt-input.d.cts +11 -0
  266. package/dist/types-cjs/property-list.d.cts +17 -0
  267. package/dist/types-cjs/question.d.cts +11 -0
  268. package/dist/types-cjs/relative-time.d.cts +8 -1
  269. package/dist/types-cjs/removal-receipt.d.cts +18 -1
  270. package/dist/types-cjs/save-bar.d.cts +6 -0
  271. package/dist/types-cjs/saved-views.d.cts +12 -0
  272. package/dist/types-cjs/scatter-chart.d.cts +8 -0
  273. package/dist/types-cjs/schedule.d.cts +21 -0
  274. package/dist/types-cjs/secret-field.d.cts +6 -0
  275. package/dist/types-cjs/select.d.cts +12 -0
  276. package/dist/types-cjs/separator.d.cts +8 -1
  277. package/dist/types-cjs/sheet.d.cts +19 -0
  278. package/dist/types-cjs/sparkline.d.cts +6 -0
  279. package/dist/types-cjs/split-button.d.cts +7 -0
  280. package/dist/types-cjs/stat.d.cts +7 -0
  281. package/dist/types-cjs/stepper.d.cts +12 -0
  282. package/dist/types-cjs/suggestions.d.cts +10 -0
  283. package/dist/types-cjs/sync-indicator.d.cts +8 -0
  284. package/dist/types-cjs/tag-input.d.cts +8 -0
  285. package/dist/types-cjs/terminal.d.cts +6 -0
  286. package/dist/types-cjs/thread-list.d.cts +16 -0
  287. package/dist/types-cjs/time-input.d.cts +6 -0
  288. package/dist/types-cjs/timeline.d.cts +9 -0
  289. package/dist/types-cjs/toggle.d.cts +6 -0
  290. package/dist/types-cjs/toolbar.d.cts +20 -0
  291. package/dist/types-cjs/tour.d.cts +10 -0
  292. package/dist/types-cjs/trace.d.cts +16 -0
  293. package/dist/types-cjs/tree.d.cts +21 -0
  294. package/package.json +1 -1
@@ -0,0 +1,204 @@
1
+ # CommandPalette
2
+
3
+ Finds and runs anything in the product from one field: the summit, at level 5, opened with ⌘K or Ctrl+K.
4
+
5
+ **Why the summit.** A command palette takes over the screen for a moment: whatever you were doing waits until you've chosen. That is what level 5 means (focus of the moment), so it sits where Dialog sits and uses the same machinery: the page recedes 1.5% and desaturates, the `scrim` veils it, and the palette rises 16px from 96% scale while its shadow grows from `elev-1` to `elev-5` (`dur-travel-5`). It's anchored 12vh from the top rather than centred, so the field stays put while the results below it grow and shrink. There is one summit: ⌘K does nothing while a Dialog is open, and choosing an item closes the palette before its action runs, so an action may open a Dialog.
6
+
7
+ **The field is an opening.** It's a real `Input`. An empty query is a hole in the palette with its back wall showing (focus never leaves it, so it never needs the rim a field you tab to gets); the first character raises its floor flush with the palette. Here that floor is `surface-modal`, the surface it sits on, rather than Input's usual `surface-raised`, which in dark schemes is darker than level 5 and would leave a filled query looking sunk.
8
+
9
+ **Results.** Groups with labels; each row has an icon, a label, an optional description and its shortcut as `Kbd` keys. The letters that matched are drawn in `accent-ink`. One `accent-soft` highlight (`critical-soft` on a destructive item) is carried to the active row on `ease-settle`, as in DropdownMenu.
10
+
11
+ - **Best match on top.** Items rank by score within their group and groups rank by their best item, so the best match is always the first row and is already active: Enter runs it.
12
+ - **Carried, not reshuffled.** When the results change, rows that stay travel to their new places (FLIP, `dur-travel-2`, `ease-glide`) instead of blinking, and the list's height glides to fit. New rows rise 4px into place, but rises spend attention: only rows near the top rise, at most four begin together (the rest follow 90ms apart), and everything further down cross-fades.
13
+ - **Predicted first.** On opening, items marked `predicted` (or a whole group, such as Recent) are there at once while the rest fade in over `dur-onset` (500ms): ephemeral adaptation, as in DropdownMenu. Once you type, everything shows at once.
14
+
15
+ **Carrying a query in.** By default the field is empty each time the palette opens. Pass `defaultQuery` to open it on a query (the page's current filter, a deep link), or control it with `query` and `onQueryChange` when something else already holds the text: a persistent search field on the page can open the palette on its first keystroke and hand over what was typed, and a controlled query isn't cleared on opening. Either way the caret lands after the carried text, so typing continues it and the results filter as usual. Focus moves to the palette's field as it rises; keep the outer field controlled by the same state so nothing typed in between is lost. An IME composition in progress can't move between fields, so it ends in the field where it started.
16
+
17
+ **Keyboard and screen readers.** The field is a `combobox` that controls a `listbox` through `aria-activedescendant` (the WAI-ARIA combobox pattern), so focus stays in the field. ↑ and ↓ move and wrap, Enter chooses, Escape closes, and ⌘K / Ctrl+K toggles. Groups are named by their labels, the active option is `aria-selected`, disabled items are skipped, and the number of results is announced once typing pauses. Focus goes back where it was when the palette closes. Under reduced motion nothing travels; rows fade.
18
+
19
+ **On touch.** `hints="auto"` (the default) shows the footer, the item shortcuts and the field's esc where there's likely a keyboard, and hides them on a touch-only device until a key is pressed, so a phone's palette isn't crowded with keys it can't press. `true` or `false` forces them. The shortcuts still work either way.
20
+
21
+ **You provide:** `items`, a list of groups (`label`, `items`, optional `predicted`), each item with a `label` and usually an `icon`, `onSelect`, `shortcut` (display only: bind the key yourself), `keywords` and `description`; `open`/`onOpenChange` when you also open it from a button or a field; `query`/`onQueryChange` or `defaultQuery` when it should open on a query. Optionally `filter` (return 0 to hide, higher to rank nearer the top; `commandScore` is the default), `empty`, `placeholder`, and `shortcut` (another letter, or `false`).
22
+
23
+ - Do start actions with a verb ("Invite people") and name places plainly ("Northwind").
24
+ - Do mark items `predicted` only when you'd be right most of the time; a wrong prediction costs a moment.
25
+ - Don't render two palettes, and don't open a Sheet from one: one summit, and a level-4 sheet can't sit on it.
26
+
27
+ The palette and Dialog share one summit per document, including across providers. Its shortcut cannot take an occupied summit. A simultaneous controlled request is rejected with a development warning; set it to `false` before trying again. Rejected requests do not queue. A selected command can close the palette and open a Dialog synchronously: callback ordering is unchanged, the exiting palette retires, and scroll/isolation ownership transfers without unlocking the page. Only the active modal has `aria-modal`; composition Enter and Escape remain with the input method.
28
+
29
+ <!-- example: generated from build/previews.txt by build/previews.mjs. Edit the preview, not this section. -->
30
+ ## Example
31
+
32
+ `CommandPalette` as first used in its preview:
33
+
34
+ ```tsx
35
+ import { CommandPalette } from 'altitude-design';
36
+
37
+ <CommandPalette
38
+ open={open}
39
+ onOpenChange={onOpenChange}
40
+ query={text}
41
+ onQueryChange={setText}
42
+ items={groups}
43
+ onSelect={(item) => setLast(item.label)}
44
+ />
45
+ ```
46
+
47
+ The full preview, as rendered in Storybook and checked in CI (`app`, `bar`, `find`, `grid`, `cap` are the preview page's own layout classes, not Altitude's):
48
+
49
+ ```tsx
50
+ import * as React from 'react';
51
+ import { Badge, Card, CommandPalette, Input, Kbd } from 'altitude-design';
52
+
53
+ const I = {
54
+ up: <svg
55
+ width="16"
56
+ height="16"
57
+ viewBox="0 0 24 24"
58
+ fill="none"
59
+ stroke="currentColor"
60
+ strokeWidth="1.75"
61
+ strokeLinecap="round"
62
+ strokeLinejoin="round"
63
+ >
64
+ <path d="M7 17L17 7M9 7h8v8" />
65
+ </svg>,
66
+ folder: <svg
67
+ width="16"
68
+ height="16"
69
+ viewBox="0 0 24 24"
70
+ fill="none"
71
+ stroke="currentColor"
72
+ strokeWidth="1.75"
73
+ strokeLinejoin="round"
74
+ >
75
+ <path d="M3 7a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" />
76
+ </svg>,
77
+ plus: <svg
78
+ width="16"
79
+ height="16"
80
+ viewBox="0 0 24 24"
81
+ fill="none"
82
+ stroke="currentColor"
83
+ strokeWidth="1.75"
84
+ strokeLinecap="round"
85
+ >
86
+ <path d="M12 5v14M5 12h14" />
87
+ </svg>,
88
+ user: <svg
89
+ width="16"
90
+ height="16"
91
+ viewBox="0 0 24 24"
92
+ fill="none"
93
+ stroke="currentColor"
94
+ strokeWidth="1.75"
95
+ strokeLinecap="round"
96
+ >
97
+ <circle cx="12" cy="8" r="4" />
98
+ <path d="M4 20c1.5-4 4.5-6 8-6s6.5 2 8 6" />
99
+ </svg>,
100
+ key: <svg
101
+ width="16"
102
+ height="16"
103
+ viewBox="0 0 24 24"
104
+ fill="none"
105
+ stroke="currentColor"
106
+ strokeWidth="1.75"
107
+ strokeLinecap="round"
108
+ strokeLinejoin="round"
109
+ >
110
+ <circle cx="8" cy="15" r="4" />
111
+ <path d="M11 12l9-9M17 6l3 3" />
112
+ </svg>,
113
+ trash: <svg
114
+ width="16"
115
+ height="16"
116
+ viewBox="0 0 24 24"
117
+ fill="none"
118
+ stroke="currentColor"
119
+ strokeWidth="1.75"
120
+ strokeLinecap="round"
121
+ strokeLinejoin="round"
122
+ >
123
+ <path d="M4 7h16M9 7V4h6v3M6 7l1 13h10l1-13" />
124
+ </svg>
125
+ };
126
+ const groups = [
127
+ { label: "Recent", predicted: true, items: [
128
+ { label: "Deploy to production", icon: I.up, shortcut: ["\u2318", "D"], keywords: ["ship", "release"] },
129
+ { label: "Open Northwind", icon: I.folder, description: "Marketing site" }
130
+ ] },
131
+ { label: "Actions", items: [
132
+ { label: "New project", icon: I.plus, shortcut: ["\u2318", "N"] },
133
+ { label: "Invite people", icon: I.user, keywords: ["team", "member"] },
134
+ { label: "Deploy preview", icon: I.up, description: "main@8dc01b2" },
135
+ { label: "Delete project\u2026", icon: I.trash, tone: "critical" }
136
+ ] },
137
+ { label: "Projects", items: [
138
+ { label: "Northwind", icon: I.folder, description: "acme.dev" },
139
+ { label: "Beacon", icon: I.folder, description: "Internal dashboard" },
140
+ { label: "Cinder", icon: I.folder, description: "API gateway" }
141
+ ] },
142
+ { label: "Settings", items: [
143
+ { label: "Environment variables", icon: I.key, keywords: ["env", "secrets", "dependencies"] },
144
+ { label: "Billing", icon: I.key, disabled: true }
145
+ ] }
146
+ ];
147
+ const searchIcon = <svg
148
+ width="16"
149
+ height="16"
150
+ viewBox="0 0 24 24"
151
+ fill="none"
152
+ stroke="currentColor"
153
+ strokeWidth="1.75"
154
+ strokeLinecap="round"
155
+ aria-hidden="true"
156
+ >
157
+ <circle cx="11" cy="11" r="7" />
158
+ <path d="M20 20l-3.5-3.5" />
159
+ </svg>;
160
+ export default function App() {
161
+ const [open, setOpen] = React.useState(true);
162
+ const [text, setText] = React.useState("");
163
+ const [last, setLast] = React.useState(null);
164
+ const onOpenChange = (v) => {
165
+ setOpen(v);
166
+ if (!v) setText("");
167
+ };
168
+ return <div className="app">
169
+ <div className="bar">
170
+ <b>Northwind</b>
171
+ <Badge tone="positive">Operational</Badge>
172
+ <Input
173
+ className="find"
174
+ aria-label="Search or run a command"
175
+ placeholder="Search or run a command"
176
+ value={text}
177
+ leading={searchIcon}
178
+ trailing={<span aria-hidden="true"><Kbd>⌘</Kbd><Kbd>K</Kbd></span>}
179
+ onChange={(e) => {
180
+ setText(e.target.value);
181
+ if (e.target.value) setOpen(true);
182
+ }}
183
+ />
184
+ </div>
185
+ <div className="grid" data-tells-ignore="LA4 placeholder page behind the overlay">
186
+ <Card />
187
+ <Card />
188
+ <Card />
189
+ </div>
190
+ <p className="cap">
191
+ {last ? `Chose \u201C${last}\u201D.` : "Type \u201Cdep\u201D: the best match moves to the top and the rows that stay travel there. Close it and type in the search field: the palette opens with what you typed."}
192
+ </p>
193
+ <CommandPalette
194
+ open={open}
195
+ onOpenChange={onOpenChange}
196
+ query={text}
197
+ onQueryChange={setText}
198
+ items={groups}
199
+ onSelect={(item) => setLast(item.label)}
200
+ />
201
+ </div>;
202
+ }
203
+ ```
204
+ <!-- /example -->
@@ -0,0 +1,81 @@
1
+ # CommentThread
2
+
3
+ Comments on a task, a document, a design or a deal: write with @mentions, post, retry a failed post, delete your own, and resolve the thread.
4
+
5
+ **Elevation.**
6
+ - **Posted comments are the record**, flush on the ground, each with its author, `RelativeTime` and body. Mentions are flush `accent-soft` plates in the text.
7
+ - **The composer is an opening**, an `Input` well that fills as you write.
8
+ - **A comment being posted is a pending write**: it hovers ("Posting…") until it lands and settles. If it fails, it drops to a trench ("Couldn't post"), keeping its text, with Retry.
9
+ - **"Done is pressed into the record": a resolved thread is stamped.** It's muted and collapsed to one line ("Resolved by Maya"), kept in place rather than removed, like a done `Checklist` item, with Show comments and Reopen.
10
+ - **A deleted comment** leaves its result in its slot ("Comment deleted"), with Undo.
11
+ - **Someone typing** shows their `presence` colour and name. Other people, live, is where presence belongs.
12
+
13
+ **Mentions.** Type `@` and a name: a listbox (level 3) offers people from `mentionable(query)`. Arrows move, Enter or Tab chooses, and Escape closes the list. The body stays plain text with "@Name", and `onPost(body, mentions)` receives the people still mentioned in it.
14
+
15
+ **Accessibility.** Comments are articles in a list. The composer is a [combobox](https://www.w3.org/WAI/ARIA/apg/patterns/combobox/) textarea while mentioning (`aria-activedescendant`). ⌘Enter or Ctrl+Enter posts. New comments from others are announced politely and batched ("2 new comments"). Typing indicators aren't announced. Under forced colours the composer is dashed, a pending comment dotted and a failed one dashed thick. Under reduced motion the typing dots don't pulse.
16
+
17
+ **Props:** `comments` (`{ id, author: { name, avatar? }, body, createdAt, status?, mentions?, mine?, deleted? }[]`); `resolved` (`{ by }` or false); `onPost`; `onResolve`; `onReopen`; `onRetry`; `onDelete`; `onUndoDelete`; `mentionable`; `typing` (names); `aria-label`.
18
+
19
+ **You provide:** the comments and their states (your app adds a pending comment when it posts, and marks it failed or posted), and who can be mentioned. Rich text is out of scope; a rich editor plugs in through the editor adapter.
20
+
21
+ ```tsx
22
+ <CommentThread comments={comments} resolved={thread.resolved}
23
+ onPost={(body, mentions) => post(body, mentions)} onResolve={resolve} onReopen={reopen}
24
+ mentionable={(q) => searchPeople(q)} typing={othersTyping} />
25
+ ```
26
+
27
+ - Do keep a failed comment's text where it was.
28
+ - Do keep a resolved thread in place, stamped.
29
+ - Don't announce typing.
30
+ - Don't post on Enter alone; people write several lines.
31
+
32
+ <!-- example: generated from build/previews.txt by build/previews.mjs. Edit the preview, not this section. -->
33
+ ## Example
34
+
35
+ `CommentThread` as first used in its preview:
36
+
37
+ ```tsx
38
+ import { CommentThread } from 'altitude-design';
39
+
40
+ <CommentThread comments={comments} resolved={resolved ? { by: 'You' } : false} onPost={post}
41
+ onResolve={() => setResolved(true)} onReopen={() => setResolved(false)}
42
+ onRetry={(id) => setComments((xs) => xs.map((x) => (x.id === id ? { ...x, status: undefined } : x)))}
43
+ onDelete={(id) => setComments((xs) => xs.map((x) => (x.id === id ? { ...x, deleted: true } : x)))}
44
+ onUndoDelete={(id) => setComments((xs) => xs.map((x) => (x.id === id ? { ...x, deleted: false } : x)))}
45
+ mentionable={(q) => people.filter((p) => p.name.toLowerCase().includes(q.toLowerCase()))} typing={resolved ? [] : ['Ada Obi']} />
46
+ ```
47
+
48
+ The full preview, as rendered in Storybook and checked in CI (`col`, `ct` are the preview page's own layout classes, not Altitude's):
49
+
50
+ ```tsx
51
+ import * as React from 'react';
52
+ import { CommentThread, Switch } from 'altitude-design';
53
+
54
+ const people = [{ id: 'maya', name: 'Maya Chen' }, { id: 'theo', name: 'Theo Marsh' }, { id: 'ada', name: 'Ada Obi' }];
55
+ const ago = (m) => new Date(Date.now() - m * 60000);
56
+ export default function App() {
57
+ const [comments, setComments] = React.useState([
58
+ { id: 'c1', author: { name: 'Theo Marsh' }, body: 'Can we ship this before the release freeze on Friday?', createdAt: ago(90) },
59
+ { id: 'c2', author: { name: 'Maya Chen' }, body: '@Theo Marsh yes, if QA signs off by Thursday.', mentions: ['Theo Marsh'], createdAt: ago(40) },
60
+ ]);
61
+ const [resolved, setResolved] = React.useState(false);
62
+ const [failNext, setFailNext] = React.useState(false);
63
+ const post = (body, mentions) => {
64
+ const id = 'n' + Date.now(), fail = failNext;
65
+ setComments((xs) => [...xs, { id, author: { name: 'You' }, body, mentions: mentions.map((m) => m.name), createdAt: new Date(), status: 'pending', mine: true }]);
66
+ setTimeout(() => setComments((xs) => xs.map((x) => (x.id === id ? { ...x, status: fail ? 'failed' : undefined } : x))), 900);
67
+ };
68
+ return (
69
+ <div className="col ct">
70
+ <CommentThread comments={comments} resolved={resolved ? { by: 'You' } : false} onPost={post}
71
+ onResolve={() => setResolved(true)} onReopen={() => setResolved(false)}
72
+ onRetry={(id) => setComments((xs) => xs.map((x) => (x.id === id ? { ...x, status: undefined } : x)))}
73
+ onDelete={(id) => setComments((xs) => xs.map((x) => (x.id === id ? { ...x, deleted: true } : x)))}
74
+ onUndoDelete={(id) => setComments((xs) => xs.map((x) => (x.id === id ? { ...x, deleted: false } : x)))}
75
+ mentionable={(q) => people.filter((p) => p.name.toLowerCase().includes(q.toLowerCase()))} typing={resolved ? [] : ['Ada Obi']} />
76
+ <Switch label="Make the next post fail" checked={failNext} onCheckedChange={setFailNext} />
77
+ </div>
78
+ );
79
+ }
80
+ ```
81
+ <!-- /example -->
@@ -0,0 +1,94 @@
1
+ # Confirmation
2
+
3
+ Approve or reject an action an agent proposes, before anything happens: "Send 214 emails to Pro customers", "Delete 3 stale branches". Human-in-the-loop for tool calls (the AI SDK's `approval-requested` state).
4
+
5
+ **Elevation: proposed is raised, not pending.** A pending write hovers because it's already in flight. A proposal hasn't been dispatched, so drawing it hovering would tell the person being asked for permission that it's already happening.
6
+ - **Requested:** a raised card (level 1, `Alert`'s rule for status that asks for action): "Approval needed", what's proposed, **the concrete consequence in words**, optional details (a `DiffView`, a list), "The assistant is asking", and Reject and Approve. Approve names the verb and outcome ("Send 214 emails").
7
+ - **Approved / running:** now it's a write, so it hovers as pending ("Sending…").
8
+ - **Done:** lands with the afterglow ("Sent · approved by you · now").
9
+ - **Failed:** drops to a trench ("Couldn't send"), with Retry.
10
+ - **Rejected / expired:** flush and muted, kept in the record ("Rejected by Maya · 2 minutes ago", "No longer needed").
11
+
12
+ **Escalation.** Consequential proposals use the `consequential-action` contract: `escalate="hold"` makes Approve a `HoldButton`; `escalate="dialog"` asks again in a stationary `Dialog` for irreversible actions.
13
+
14
+ **Accessibility.** A labelled `section` (its title) described by the consequence. Not a dialog unless escalated. **There's no default action**: nothing is approved by an accidental Enter, and nothing is focused automatically. Outcomes are announced ("Send campaign email: Sent."). Under forced colours the card has a 1px border, pending is dotted and failed dashed thick. Under `contrast="more"` the requested card gets a hard slab.
15
+
16
+ **Props:** `state` (`'requested' | 'approved' | 'running' | 'done' | 'failed' | 'rejected' | 'expired'`); `title`; `consequence`; `details`; `approveLabel`, `rejectLabel`, `runningLabel`, `doneLabel`, `failedLabel`; `escalate`; `resolvedBy`, `resolvedAt`; `requestedBy`; `onApprove`, `onReject`, `onRetry`.
17
+
18
+ **Scopes.** For an action that comes up again and again ("Run npm test"), give `scopes`: Approve becomes a SplitButton whose face approves once and whose menu, "More ways to approve", holds the wider scopes ("Allow npm test for this session", "Always allow npm test · Saved to this project's settings"). `onApprove` gets `'once'` or the scope's value; pass it back as `resolvedScope` and the resolved line says it: "Ran · allowed for this session by you". With `escalate`, scopes are ignored: an action that needs a hold or a dialog is never pre-approved.
19
+
20
+ **Editing the proposal.** Give `edit` (fields, e.g. `Field` + `Input`) and the card offers Edit: the fields open in place inside it, as `EditRegion` does, focusing the first. Keep `consequence` and `approveLabel` following the edited values so Approve always names what will happen, and set `edited` when they differ: "Edited" shows by the title, and once done the line reads "Sent · edited and approved by you". Escape or Cancel edit closes the fields and calls `onEditCancel` (put the values back); it doesn't reject the proposal. Reject stays available while editing.
21
+
22
+ **You provide:** the state from your tool call, the consequence in words, and what approving and rejecting do.
23
+
24
+ ```tsx
25
+ <Confirmation state={call.state} title="Send campaign email"
26
+ consequence="214 Pro customers will receive “October product update”."
27
+ approveLabel="Send 214 emails" rejectLabel="Don't send" runningLabel="Sending…" doneLabel="Sent"
28
+ onApprove={() => respond(call.id, true)} onReject={() => respond(call.id, false)} />
29
+ ```
30
+
31
+ - Do state the consequence concretely: who, how many, what.
32
+ - Do name the outcome on Approve.
33
+ - Don't draw a proposal as pending.
34
+ - Don't remove a rejected proposal; keep it in the record.
35
+
36
+ <!-- example: generated from build/previews.txt by build/previews.mjs. Edit the preview, not this section. -->
37
+ ## Example
38
+
39
+ `Confirmation` as first used in its preview:
40
+
41
+ ```tsx
42
+ import { Confirmation } from 'altitude-design';
43
+
44
+ <Confirmation state={state} title={title} consequence={consequence} escalate={escalate} approveLabel={approveLabel} rejectLabel={rejectLabel}
45
+ runningLabel={runningLabel} doneLabel={doneLabel} failedLabel={failedLabel} resolvedBy="you" resolvedAt={at} scopes={scopes} resolvedScope={scope}
46
+ onApprove={approve} onReject={() => { setState('rejected'); setAt(new Date()); }} onRetry={() => { setState('running'); setTimeout(() => { setState('done'); setAt(new Date()); }, 1000); }} />
47
+ ```
48
+
49
+ The full preview, as rendered in Storybook and checked in CI (`col` is the preview page's own layout class, not Altitude's):
50
+
51
+ ```tsx
52
+ import * as React from 'react';
53
+ import { Confirmation, Field, Input, Select } from 'altitude-design';
54
+
55
+ function Proposal({ escalate, title, consequence, approveLabel, rejectLabel, runningLabel, doneLabel, failedLabel, fails, scopes }) {
56
+ const [state, setState] = React.useState('requested');
57
+ const [scope, setScope] = React.useState();
58
+ const [at, setAt] = React.useState();
59
+ const approve = (s) => { setScope(s); setState('running'); setTimeout(() => { setState(fails ? 'failed' : 'done'); setAt(new Date()); }, 1400); };
60
+ return <Confirmation state={state} title={title} consequence={consequence} escalate={escalate} approveLabel={approveLabel} rejectLabel={rejectLabel}
61
+ runningLabel={runningLabel} doneLabel={doneLabel} failedLabel={failedLabel} resolvedBy="you" resolvedAt={at} scopes={scopes} resolvedScope={scope}
62
+ onApprove={approve} onReject={() => { setState('rejected'); setAt(new Date()); }} onRetry={() => { setState('running'); setTimeout(() => { setState('done'); setAt(new Date()); }, 1000); }} />;
63
+ }
64
+ // Edit what's proposed before approving: the consequence and the button follow the edited values.
65
+ function EditableProposal() {
66
+ const original = { subject: 'October product update', segment: 'Pro customers', n: 214 };
67
+ const [v, setV] = React.useState(original);
68
+ const [state, setState] = React.useState('requested');
69
+ const [at, setAt] = React.useState();
70
+ const counts = { 'Pro customers': 214, 'All customers': 1180, 'Trial users': 96 };
71
+ const edited = v.subject !== original.subject || v.segment !== original.segment;
72
+ return <Confirmation state={state} title="Send campaign email" consequence={`${v.n} ${v.segment} will receive “${v.subject}”.`}
73
+ approveLabel={`Send ${v.n} emails`} rejectLabel="Don't send" runningLabel="Sending…" doneLabel="Sent" resolvedBy="you" resolvedAt={at} edited={edited}
74
+ edit={<>
75
+ <Field label="Subject"><Input value={v.subject} onChange={(e) => setV({ ...v, subject: e.target.value })} /></Field>
76
+ <Field label="Segment"><Select value={v.segment} onValueChange={(segment) => setV({ ...v, segment, n: counts[segment] })} items={Object.keys(counts).map((k) => ({ value: k, label: k }))} /></Field>
77
+ </>}
78
+ onEditCancel={() => setV(original)}
79
+ onApprove={() => { setState('running'); setTimeout(() => { setState('done'); setAt(new Date()); }, 1200); }} onReject={() => { setState('rejected'); setAt(new Date()); }} />;
80
+ }
81
+ export default function App() {
82
+ return (
83
+ <div className="col">
84
+ <Proposal title="Send campaign email" consequence="214 Pro customers will receive “October product update”." approveLabel="Send 214 emails" rejectLabel="Don't send" runningLabel="Sending…" doneLabel="Sent" failedLabel="Couldn't send" />
85
+ <Proposal title="Delete 3 stale branches" consequence="release/2.3, spike/maps and fix/old-auth will be deleted from the repository." approveLabel="Delete 3 branches" rejectLabel="Keep them" runningLabel="Deleting…" doneLabel="Deleted" escalate="hold" />
86
+ <Proposal title="Archive the Atlas workspace" consequence="12 people lose access; it can't be restored after 30 days." approveLabel="Archive workspace" rejectLabel="Don't archive" runningLabel="Archiving…" failedLabel="Couldn't archive" escalate="dialog" fails />
87
+ <Proposal title="Run npm test" consequence="Runs the test suite in this workspace." approveLabel="Run once" rejectLabel="Don't run" runningLabel="Running…" doneLabel="Ran"
88
+ scopes={[{ value: 'session', label: 'Allow npm test for this session' }, { value: 'always', label: 'Always allow npm test', description: 'Saved to this project’s settings' }]} />
89
+ <EditableProposal />
90
+ </div>
91
+ );
92
+ }
93
+ ```
94
+ <!-- /example -->
@@ -0,0 +1,81 @@
1
+ # ContextMenu
2
+
3
+ Actions on a thing (a file, a row, a board card, a tree node, a canvas object), opened where you right-click or long-press it, or at it with Shift+F10 or the Menu key.
4
+
5
+ **Elevation.** The menu is `DropdownMenu`'s own (both use one implementation): a floating surface at level 3, with the highlight that glides between items, `predicted` items first, and `critical` items in words. **The thing shows it's the subject with a 2px `accent-ink` outline**, a ring and never a height, so a flush table row doesn't lift out of its table.
6
+
7
+ **No submenus.** "Don't stack floating on floating": a menu inside a menu loses the reading of height. Put the choices in the menu (a few) or open a `Dialog` ("Move to…").
8
+
9
+ **Every action needs another way.** A right-click is invisible until you know it's there, and long-press is easy to miss. Put the same actions behind a visible button on the thing (a row's "⋯" `DropdownMenu`), as the preview does.
10
+
11
+ **Accessibility.** The [menu pattern](https://www.w3.org/WAI/ARIA/apg/patterns/menu/). The thing gets `aria-haspopup="menu"` and `aria-expanded`, and must be focusable (`tabIndex={0}`) so Shift+F10 and the Menu key can open the menu at it. That opens with the first item active. Arrows, Home, End and Enter work in the menu, and Escape (or choosing) returns focus to the thing. A long press on touch is 500ms without moving more than 10px. The menu stays inside the viewport, flipping left or up at an edge, and closes on scroll, resize or a click outside. Under forced colours the outline is `Highlight`.
12
+
13
+ **Props:** `items` (`DropdownMenu` entries, or a function called each time it opens); `children` (one element: the thing); `aria-label`; `onOpenChange`.
14
+
15
+ **You provide:** the thing (focusable), its actions, and another way to reach them.
16
+
17
+ ```tsx
18
+ <ContextMenu items={() => [
19
+ { label: 'Rename', onSelect: () => rename(file) },
20
+ { type: 'separator' },
21
+ { label: 'Delete file', tone: 'critical', onSelect: () => remove(file) },
22
+ ]}>
23
+ <div role="listitem" tabIndex={0}>{file.name} <RowActions file={file} /></div>
24
+ </ContextMenu>
25
+ ```
26
+
27
+ - Do make the thing focusable, so the keyboard can open its menu.
28
+ - Do offer the same actions in a visible menu.
29
+ - Don't nest menus; open a `Dialog` for a long choice.
30
+ - Don't raise the thing while its menu is open; it's outlined.
31
+
32
+ <!-- example: generated from build/previews.txt by build/previews.mjs. Edit the preview, not this section. -->
33
+ ## Example
34
+
35
+ `ContextMenu` as first used in its preview:
36
+
37
+ ```tsx
38
+ import { Button, ContextMenu, DropdownMenu, Icons } from 'altitude-design';
39
+
40
+ <ContextMenu key={f} items={() => actions(f)}>
41
+ <div className="file" role="listitem" tabIndex={0}>
42
+ <span className="name">{f}</span><span className="meta">Edited 2 days ago</span>
43
+ <DropdownMenu align="end" items={actions(f)} trigger={<Button variant="ghost" size="sm" square aria-label={`Actions for ${f}`}><Icons.More /></Button>} />
44
+ </div>
45
+ </ContextMenu>
46
+ ```
47
+
48
+ The full preview, as rendered in Storybook and checked in CI (`col`, `cm2`, `file`, `name`, `meta`, `cap` are the preview page's own layout classes, not Altitude's):
49
+
50
+ ```tsx
51
+ import * as React from 'react';
52
+ import { Button, ContextMenu, DropdownMenu, Icons } from 'altitude-design';
53
+
54
+ export default function App() {
55
+ const [files, setFiles] = React.useState(['Q3 board deck.pdf', 'Invoices 2026.xlsx', 'Brand guidelines.fig']);
56
+ const [said, setSaid] = React.useState('');
57
+ const actions = (f) => [
58
+ { label: 'Open', onSelect: () => setSaid(`Opened ${f}.`) },
59
+ { label: 'Rename', onSelect: () => setSaid(`Renaming ${f}…`) },
60
+ { label: 'Duplicate', onSelect: () => setSaid(`Duplicated ${f}.`) },
61
+ { type: 'separator' },
62
+ { label: 'Delete file', tone: 'critical', onSelect: () => { setFiles((xs) => xs.filter((x) => x !== f)); setSaid(`Deleted ${f}.`); } },
63
+ ];
64
+ return (
65
+ <div className="col">
66
+ <div className="cm2" role="list" aria-label="Files">
67
+ {files.map((f) => (
68
+ <ContextMenu key={f} items={() => actions(f)}>
69
+ <div className="file" role="listitem" tabIndex={0}>
70
+ <span className="name">{f}</span><span className="meta">Edited 2 days ago</span>
71
+ <DropdownMenu align="end" items={actions(f)} trigger={<Button variant="ghost" size="sm" square aria-label={`Actions for ${f}`}><Icons.More /></Button>} />
72
+ </div>
73
+ </ContextMenu>
74
+ ))}
75
+ </div>
76
+ <p className="cap" role="status">{said || 'Right-click a file (or long-press, or focus it and press Shift+F10). The same actions are behind each row\'s ⋯ button, because a context menu is never the only way.'}</p>
77
+ </div>
78
+ );
79
+ }
80
+ ```
81
+ <!-- /example -->
@@ -0,0 +1,169 @@
1
+ # Conversation
2
+
3
+ The thread between a person and an assistant: their messages, the assistant's answers (often streamed), copy, regenerate, feedback, versions, and "Jump to latest" once you've scrolled up. Pair it with `PromptInput`.
4
+
5
+ **Elevation: how settled is a streamed answer?** Settled messages are the record.
6
+ - **A person's message** is a flush `accent-soft` sheet on the right.
7
+ - **An assistant's complete message** is flush with no bubble and no shadow, with a quiet `line` rule down its left edge.
8
+ - **A streaming answer isn't the record yet**, and it isn't a pending write either: nothing you sent is in flight, so it mustn't hover. It rests on the ground with the treatment charts use for data still being counted: **a dashed `line-control` rule**, "Generating…" and a caret. It's `aria-busy`.
9
+ - **When it completes**, the rule goes solid and the message settles with one afterglow. The arrival signal is held until the answer is whole, because lifting it for the seconds it takes to stream would be a prolonged rise.
10
+ - **Stopped or failed** answers are kept, with "Stopped" or "Couldn't finish" (and a `critical` rule) and Retry.
11
+ - **Message actions** are ghost buttons that lift when pointed at. Feedback is a pair of toggles; regenerated answers are versions ("Version 2 of 2"), because a previous answer is a record and isn't overwritten.
12
+ - **"Jump to latest message"** floats at level 3 once you've scrolled up. The thread follows new text to the bottom until you scroll up, then lets go.
13
+
14
+ **Motion.** Text arrives without per-word animation. Completion: one afterglow. Under reduced motion the caret doesn't blink.
15
+
16
+ **Accessibility.** A `log` region of articles, each headed by who's speaking ("You" visually hidden). **Streamed text isn't announced token by token**: the log's own announcements are off, and each answer is announced once, when it completes ("Assistant: Last week the team shipped…", up to 280 characters). Stopping is announced politely, a failure assertively. The log is focusable so it can be scrolled by keyboard. Under forced colours the streaming rule is dotted and a complete one solid. Under `contrast="more"` the rule is `line-control`.
17
+
18
+ **Parts:** `Conversation` (`aria-label`); `ConversationContent`; `ConversationScrollButton`; `Message` (`from`, `status` (`'streaming' | 'complete' | 'stopped' | 'error'`), `name`, `onRetry`); `MessageAttachments` (`items`: `{ name, type?, size?, url?, alt?, href?, onOpen?, unavailable? }`), before the content: what was sent with the message, images as 64px thumbnails and other files as settled Tag plates with type and size, acting when they have `href` or `onOpen`; `MessageContent`; `MessageActions` (`onCopy`, `onRegenerate`, `onFeedback`, `feedback`, `versions`, `onEdit`); `Message edited`.
19
+
20
+ **Editing a sent message.** Give a person's message `MessageActions onEdit`: Edit opens it in place (the text stays where it is while a Textarea well opens around it), with Cancel and "Send edit"; Escape cancels, Ctrl/⌘+Enter sends. Sending calls `onEdit(text)`: start a new branch from that message, keep the earlier one, and show its `versions` ("Version 2 of 2") and `edited` ("Edited"). Switching versions is announced; render the turns of the branch shown. Nothing is deleted.
21
+
22
+ **The queue.** `ConversationQueue` (`items`, `onSendNow`, `onEdit`, `onRemove`) goes between the thread and the composer and lists messages queued while the agent works (`PromptInput onQueue`), in order, each with Send now (stop the turn and send it), Edit (in place) and Remove. A queued message is written but not dispatched, so it doesn't hover: it's the person's sheet, flush, with a dashed rim and "Queued · 1 of 2". When the turn ends, send the first; it becomes an ordinary message in the log.
23
+
24
+ **Checkpoints.** `ConversationCheckpoint` (`label`, `at`, `consequence`, `onRestore`, `onReturn`) marks a point between messages to go back to: a divider reading "Checkpoint · Before editing 6 files · 2:14 PM" with Restore, described by the consequence. It follows the consequential-action contract: with `onReturn` (real recovery), Restore runs at once and leaves "Return to where you were"; without it, Restore asks in a Dialog. Restoring hovers as pending, settles once when done, and sinks into a trench with Retry if it fails; the outcome is announced. Set the later turns aside as a branch rather than deleting them.
25
+
26
+ **You provide:** the messages and their status as your stream reports it. Code inside an answer goes in `CodeBlock`.
27
+
28
+ ```tsx
29
+ <Conversation aria-label="Assistant">
30
+ <ConversationContent>
31
+ {messages.map((m) => (
32
+ <Message key={m.id} from={m.role} status={m.status}>
33
+ <MessageContent>{m.content}</MessageContent>
34
+ <MessageActions onCopy onRegenerate={() => regen(m.id)} onFeedback={(v) => rate(m.id, v)} />
35
+ </Message>
36
+ ))}
37
+ </ConversationContent>
38
+ <ConversationScrollButton />
39
+ </Conversation>
40
+ ```
41
+
42
+ - Do pass the stream's status, so a finished answer settles and is announced.
43
+ - Do keep earlier versions when regenerating.
44
+ - Don't fade in text word by word or glow a streaming answer.
45
+ - Don't make a streaming answer hover; it isn't a pending write.
46
+
47
+ <!-- example: generated from build/previews.txt by build/previews.mjs. Edit the preview, not this section. -->
48
+ ## Example
49
+
50
+ `Conversation` as first used in its preview:
51
+
52
+ ```tsx
53
+ import { Conversation, ConversationCheckpoint, ConversationContent, ConversationScrollButton, Message, MessageActions, MessageAttachments, MessageContent } from 'altitude-design';
54
+
55
+ <Conversation aria-label="Assistant">
56
+ <ConversationContent>
57
+ {msgs.map((m) => m.kind === 'checkpoint' ? (
58
+ <ConversationCheckpoint key={m.id} label={m.label} at={m.at} onRestore={() => restore(m.id)} onReturn={giveBack}
59
+ consequence="The messages after this will be set aside. You can return to them." />
60
+ ) : (
61
+ <Message key={m.id} from={m.from} status={m.status} edited={m.v > 0} onRetry={m.status === 'stopped' ? () => { setStatus('streaming'); stream(m.id); } : undefined}>
62
+ {m.files && <MessageAttachments items={m.files} />}
63
+ <MessageContent>{m.text}</MessageContent>
64
+ {m.from === 'assistant' ? <MessageActions onCopy onRegenerate={() => {}} onFeedback={() => {}} />
65
+ : <MessageActions onEdit={(t) => edit(m.id, t)} versions={m.versions && { index: m.v + 1, total: m.versions.length, onChange: (k) => showVersion(m.id, k - 1) }} />}
66
+ </Message>
67
+ ))}
68
+ </ConversationContent>
69
+ <ConversationScrollButton />
70
+ </Conversation>
71
+ ```
72
+
73
+ The full preview, as rendered in Storybook and checked in CI (`cv` is the preview page's own layout class, not Altitude's):
74
+
75
+ ```tsx
76
+ import * as React from 'react';
77
+ import { Conversation, ConversationCheckpoint, ConversationContent, ConversationQueue, ConversationScrollButton, Message, MessageActions, MessageAttachments, MessageContent, PromptInput, PromptInputSubmit, PromptInputTextarea, PromptInputToolbar } from 'altitude-design';
78
+
79
+ const answer = 'Last week the team shipped 14 changes. Two risks are open: the billing migration is two days behind, and the EU data residency review needs a decision from legal by Friday.';
80
+ export default function App() {
81
+ const [msgs, setMsgs] = React.useState([
82
+ { id: 1, from: 'user', text: 'What changed in the Atlas workspace this month?', files: [
83
+ { name: 'atlas-usage.png', type: 'image/svg+xml', url: "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='64'%3E%3Crect width='120' height='64' fill='%23e8edf5'/%3E%3Cpath d='M8 52 L32 36 L56 42 L80 20 L112 12' stroke='%233b6fd8' stroke-width='3' fill='none'/%3E%3C/svg%3E", onOpen: () => {} },
84
+ { name: 'Atlas changelog.pdf', type: 'application/pdf', size: 482113, href: '#changelog' },
85
+ ] },
86
+ { id: 'cp1', kind: 'checkpoint', label: 'Before the Atlas summary', at: '2026-10-30T14:14:00' },
87
+ { id: 2, from: 'assistant', text: 'Atlas moved to the new permission model on 3 October, and its storage limit was raised to 2 TB. Three people joined: Maya, Theo and Ada.', status: 'complete' },
88
+ ]);
89
+ const [status, setStatus] = React.useState('ready');
90
+ const timer = React.useRef();
91
+ // Messages written while the assistant works wait in the queue; the first is sent when the turn ends.
92
+ const [queued, setQueued] = React.useState([]);
93
+ const queuedRef = React.useRef(queued); queuedRef.current = queued;
94
+ const sendRef = React.useRef();
95
+ const stream = (id) => {
96
+ let n = 0;
97
+ const tick = () => {
98
+ n += 6;
99
+ setMsgs((ms) => ms.map((m) => (m.id === id ? { ...m, text: answer.slice(0, n), status: n >= answer.length ? 'complete' : 'streaming' } : m)));
100
+ if (n < answer.length) timer.current = setTimeout(tick, 60);
101
+ else {
102
+ setStatus('ready');
103
+ const next = queuedRef.current[0];
104
+ if (next) { setQueued((q) => q.slice(1)); setTimeout(() => sendRef.current({ text: next.text }), 0); }
105
+ }
106
+ };
107
+ tick();
108
+ };
109
+ const enqueue = ({ text }) => setQueued((q) => [...q, { id: String(Date.now()), text }]);
110
+ const send = ({ text }) => {
111
+ const id = Date.now();
112
+ setMsgs((ms) => [...ms, { id: id - 1, from: 'user', text }, { id, from: 'assistant', text: '', status: 'streaming' }]);
113
+ setStatus('streaming');
114
+ stream(id);
115
+ };
116
+ // Editing a sent message starts a branch from it; the earlier exchange is kept as version 1.
117
+ const edit = (id, text) => {
118
+ const i = msgs.findIndex((m) => m.id === id), m = msgs[i];
119
+ const versions = (m.versions ?? [{ text: m.text, files: m.files }]).map((v, k) => (k === (m.v ?? 0) ? { ...v, rest: msgs.slice(i + 1) } : v));
120
+ const aid = Date.now();
121
+ setMsgs([...msgs.slice(0, i), { ...m, text, files: undefined, versions: [...versions, { text }], v: versions.length }, { id: aid, from: 'assistant', text: '', status: 'streaming' }]);
122
+ setStatus('streaming');
123
+ stream(aid);
124
+ };
125
+ const showVersion = (id, k) => {
126
+ const i = msgs.findIndex((m) => m.id === id), m = msgs[i];
127
+ const versions = m.versions.map((v, j) => (j === m.v ? { ...v, rest: msgs.slice(i + 1) } : v));
128
+ setMsgs([...msgs.slice(0, i), { ...m, text: versions[k].text, files: versions[k].files, versions, v: k }, ...(versions[k].rest ?? [])]);
129
+ };
130
+ const sendNow = (id) => {
131
+ const q = queued.find((x) => x.id === id);
132
+ setQueued((qs) => qs.filter((x) => x.id !== id));
133
+ stop();
134
+ setTimeout(() => sendRef.current({ text: q.text }), 0);
135
+ };
136
+ // Restoring a checkpoint sets the turns after it aside; returning brings them back.
137
+ const aside = React.useRef([]);
138
+ const restore = (id) => new Promise((done) => setTimeout(() => { setMsgs((ms) => { const i = ms.findIndex((m) => m.id === id); aside.current = ms.slice(i + 1); return ms.slice(0, i + 1); }); done(); }, 900));
139
+ const giveBack = () => { setMsgs((ms) => [...ms, ...aside.current]); aside.current = []; };
140
+ const stop = () => { clearTimeout(timer.current); setStatus('ready'); setMsgs((ms) => ms.map((m) => (m.status === 'streaming' ? { ...m, status: 'stopped' } : m))); };
141
+ sendRef.current = send;
142
+ return (
143
+ <div className="cv">
144
+ <Conversation aria-label="Assistant">
145
+ <ConversationContent>
146
+ {msgs.map((m) => m.kind === 'checkpoint' ? (
147
+ <ConversationCheckpoint key={m.id} label={m.label} at={m.at} onRestore={() => restore(m.id)} onReturn={giveBack}
148
+ consequence="The messages after this will be set aside. You can return to them." />
149
+ ) : (
150
+ <Message key={m.id} from={m.from} status={m.status} edited={m.v > 0} onRetry={m.status === 'stopped' ? () => { setStatus('streaming'); stream(m.id); } : undefined}>
151
+ {m.files && <MessageAttachments items={m.files} />}
152
+ <MessageContent>{m.text}</MessageContent>
153
+ {m.from === 'assistant' ? <MessageActions onCopy onRegenerate={() => {}} onFeedback={() => {}} />
154
+ : <MessageActions onEdit={(t) => edit(m.id, t)} versions={m.versions && { index: m.v + 1, total: m.versions.length, onChange: (k) => showVersion(m.id, k - 1) }} />}
155
+ </Message>
156
+ ))}
157
+ </ConversationContent>
158
+ <ConversationScrollButton />
159
+ </Conversation>
160
+ <ConversationQueue items={queued} onSendNow={sendNow} onEdit={(id, text) => setQueued((qs) => qs.map((q) => (q.id === id ? { ...q, text } : q)))} onRemove={(id) => setQueued((qs) => qs.filter((q) => q.id !== id))} />
161
+ <PromptInput onSubmit={send} status={status} onStop={stop} onQueue={enqueue}>
162
+ <PromptInputTextarea placeholder="Ask about this workspace" />
163
+ <PromptInputToolbar><PromptInputSubmit /></PromptInputToolbar>
164
+ </PromptInput>
165
+ </div>
166
+ );
167
+ }
168
+ ```
169
+ <!-- /example -->