@cueplusplus/ui 0.13.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 (1016) hide show
  1. package/CHANGELOG.md +4028 -0
  2. package/LICENSE +21 -0
  3. package/NOTICE.md +34 -0
  4. package/README.md +237 -0
  5. package/dist/agent-runtime/_glyphs.js +43 -0
  6. package/dist/agent-runtime/branch-picker.d.ts +45 -0
  7. package/dist/agent-runtime/branch-picker.js +87 -0
  8. package/dist/agent-runtime/index.d.ts +4 -0
  9. package/dist/agent-runtime/index.js +4 -0
  10. package/dist/agent-runtime/tool-call-card.d.ts +69 -0
  11. package/dist/agent-runtime/tool-call-card.js +187 -0
  12. package/dist/agent-runtime/use-agent-thread.d.ts +164 -0
  13. package/dist/agent-runtime/use-agent-thread.js +251 -0
  14. package/dist/brand/_chassis.d.ts +54 -0
  15. package/dist/brand/_chassis.js +81 -0
  16. package/dist/brand/_geometry.js +41 -0
  17. package/dist/brand/cue-logotype.d.ts +29 -0
  18. package/dist/brand/cue-logotype.js +60 -0
  19. package/dist/brand/cue-mark.d.ts +30 -0
  20. package/dist/brand/cue-mark.js +61 -0
  21. package/dist/brand/index.d.ts +5 -0
  22. package/dist/brand/plussie.d.ts +67 -0
  23. package/dist/brand/plussie.js +125 -0
  24. package/dist/charts/_token.js +39 -0
  25. package/dist/charts/container.d.ts +58 -0
  26. package/dist/charts/container.js +77 -0
  27. package/dist/charts/index.d.ts +5 -0
  28. package/dist/charts/index.js +5 -0
  29. package/dist/charts/ramps.d.ts +140 -0
  30. package/dist/charts/ramps.js +615 -0
  31. package/dist/charts/theme.d.ts +112 -0
  32. package/dist/charts/theme.js +165 -0
  33. package/dist/charts/tones.d.ts +111 -0
  34. package/dist/charts/tones.js +127 -0
  35. package/dist/chat/_glyphs.js +55 -0
  36. package/dist/chat/agent-color.d.ts +84 -0
  37. package/dist/chat/agent-color.js +101 -0
  38. package/dist/chat/agent-pile.d.ts +55 -0
  39. package/dist/chat/agent-pile.js +121 -0
  40. package/dist/chat/ask-box.d.ts +88 -0
  41. package/dist/chat/ask-box.js +341 -0
  42. package/dist/chat/composer.d.ts +66 -0
  43. package/dist/chat/composer.js +144 -0
  44. package/dist/chat/delegation-card.d.ts +60 -0
  45. package/dist/chat/delegation-card.js +129 -0
  46. package/dist/chat/index.d.ts +8 -0
  47. package/dist/chat/index.js +8 -0
  48. package/dist/chat/message-list.d.ts +45 -0
  49. package/dist/chat/message-list.js +141 -0
  50. package/dist/chat/message.d.ts +47 -0
  51. package/dist/chat/message.js +108 -0
  52. package/dist/chrome/_band.js +54 -0
  53. package/dist/chrome/_drag.js +59 -0
  54. package/dist/chrome/_edge-scroller.d.ts +45 -0
  55. package/dist/chrome/_edge-scroller.js +149 -0
  56. package/dist/chrome/_glyphs.js +119 -0
  57. package/dist/chrome/_status-bar-item.js +85 -0
  58. package/dist/chrome/_status-bar.d.ts +15 -0
  59. package/dist/chrome/_status-bar.js +171 -0
  60. package/dist/chrome/_tabs-scroll.d.ts +67 -0
  61. package/dist/chrome/_tabs-scroll.js +73 -0
  62. package/dist/chrome/_tree.d.ts +75 -0
  63. package/dist/chrome/_tree.js +49 -0
  64. package/dist/chrome/app-bar.d.ts +103 -0
  65. package/dist/chrome/app-bar.js +175 -0
  66. package/dist/chrome/app-shell.d.ts +243 -0
  67. package/dist/chrome/app-shell.js +266 -0
  68. package/dist/chrome/band.d.ts +106 -0
  69. package/dist/chrome/band.js +97 -0
  70. package/dist/chrome/empty-state.d.ts +37 -0
  71. package/dist/chrome/empty-state.js +57 -0
  72. package/dist/chrome/eyebrow.d.ts +29 -0
  73. package/dist/chrome/eyebrow.js +36 -0
  74. package/dist/chrome/footer.d.ts +88 -0
  75. package/dist/chrome/footer.js +83 -0
  76. package/dist/chrome/index.d.ts +24 -0
  77. package/dist/chrome/index.js +22 -0
  78. package/dist/chrome/inspector.d.ts +61 -0
  79. package/dist/chrome/inspector.js +121 -0
  80. package/dist/chrome/navigation-menu.d.ts +80 -0
  81. package/dist/chrome/navigation-menu.js +121 -0
  82. package/dist/chrome/page-shell.d.ts +38 -0
  83. package/dist/chrome/page-shell.js +76 -0
  84. package/dist/chrome/panel.d.ts +55 -0
  85. package/dist/chrome/panel.js +67 -0
  86. package/dist/chrome/row.d.ts +82 -0
  87. package/dist/chrome/row.js +106 -0
  88. package/dist/chrome/scroll-area.d.ts +39 -0
  89. package/dist/chrome/scroll-area.js +70 -0
  90. package/dist/chrome/section-header.d.ts +42 -0
  91. package/dist/chrome/section-header.js +79 -0
  92. package/dist/chrome/segmented-control.d.ts +77 -0
  93. package/dist/chrome/segmented-control.js +95 -0
  94. package/dist/chrome/status-bar.d.ts +129 -0
  95. package/dist/chrome/status-bar.js +120 -0
  96. package/dist/chrome/tabs.d.ts +80 -0
  97. package/dist/chrome/tabs.js +132 -0
  98. package/dist/chrome/title-bar.d.ts +47 -0
  99. package/dist/chrome/title-bar.js +43 -0
  100. package/dist/chrome/toolbar.d.ts +68 -0
  101. package/dist/chrome/toolbar.js +83 -0
  102. package/dist/chrome/tree.d.ts +98 -0
  103. package/dist/chrome/tree.js +248 -0
  104. package/dist/color/_checker.js +14 -0
  105. package/dist/color/_convert.d.ts +52 -0
  106. package/dist/color/_convert.js +66 -0
  107. package/dist/color/_glyphs.js +28 -0
  108. package/dist/color/_thumb.js +15 -0
  109. package/dist/color/_value.js +28 -0
  110. package/dist/color/color-area.d.ts +27 -0
  111. package/dist/color/color-area.js +40 -0
  112. package/dist/color/color-field.d.ts +32 -0
  113. package/dist/color/color-field.js +43 -0
  114. package/dist/color/color-picker.d.ts +71 -0
  115. package/dist/color/color-picker.js +178 -0
  116. package/dist/color/color-slider.d.ts +31 -0
  117. package/dist/color/color-slider.js +51 -0
  118. package/dist/color/color-swatch.d.ts +32 -0
  119. package/dist/color/color-swatch.js +44 -0
  120. package/dist/color/index.d.ts +7 -0
  121. package/dist/color/index.js +7 -0
  122. package/dist/configurator/_export.d.ts +78 -0
  123. package/dist/configurator/_export.js +302 -0
  124. package/dist/configurator/_glyphs.js +92 -0
  125. package/dist/configurator/_overrides.d.ts +266 -0
  126. package/dist/configurator/_overrides.js +541 -0
  127. package/dist/configurator/configurator.d.ts +71 -0
  128. package/dist/configurator/configurator.js +284 -0
  129. package/dist/configurator/export-dialog.d.ts +30 -0
  130. package/dist/configurator/export-dialog.js +121 -0
  131. package/dist/configurator/index.d.ts +8 -0
  132. package/dist/configurator/index.js +8 -0
  133. package/dist/configurator/panel-sections.d.ts +86 -0
  134. package/dist/configurator/panel-sections.js +352 -0
  135. package/dist/configurator/token-editor.d.ts +74 -0
  136. package/dist/configurator/token-editor.js +152 -0
  137. package/dist/configurator/use-zoom.d.ts +87 -0
  138. package/dist/configurator/use-zoom.js +113 -0
  139. package/dist/date/_glyphs.js +34 -0
  140. package/dist/date/_segments.js +101 -0
  141. package/dist/date/_trigger.js +31 -0
  142. package/dist/date/calendar.d.ts +25 -0
  143. package/dist/date/calendar.js +125 -0
  144. package/dist/date/date-field.d.ts +34 -0
  145. package/dist/date/date-field.js +41 -0
  146. package/dist/date/date-picker.d.ts +69 -0
  147. package/dist/date/date-picker.js +89 -0
  148. package/dist/date/date-range-picker.d.ts +65 -0
  149. package/dist/date/date-range-picker.js +92 -0
  150. package/dist/date/index.d.ts +6 -0
  151. package/dist/date/index.js +6 -0
  152. package/dist/date/time-field.d.ts +29 -0
  153. package/dist/date/time-field.js +36 -0
  154. package/dist/dmx/_ramp.d.ts +77 -0
  155. package/dist/dmx/_ramp.js +169 -0
  156. package/dist/dmx/_readout.js +53 -0
  157. package/dist/dmx/_universe.d.ts +105 -0
  158. package/dist/dmx/_universe.js +112 -0
  159. package/dist/dmx/channel-matrix.d.ts +81 -0
  160. package/dist/dmx/channel-matrix.js +285 -0
  161. package/dist/dmx/dmx-bar.d.ts +50 -0
  162. package/dist/dmx/dmx-bar.js +107 -0
  163. package/dist/dmx/dmx-strip.d.ts +41 -0
  164. package/dist/dmx/dmx-strip.js +196 -0
  165. package/dist/dmx/index.d.ts +7 -0
  166. package/dist/dmx/index.js +7 -0
  167. package/dist/dmx/universe-grid.d.ts +61 -0
  168. package/dist/dmx/universe-grid.js +182 -0
  169. package/dist/elements/activity-graph.d.ts +40 -0
  170. package/dist/elements/activity-graph.js +81 -0
  171. package/dist/elements/agent-card.d.ts +49 -0
  172. package/dist/elements/agent-card.js +91 -0
  173. package/dist/elements/agent-handoff.d.ts +24 -0
  174. package/dist/elements/agent-handoff.js +53 -0
  175. package/dist/elements/agent-mode-badge.d.ts +72 -0
  176. package/dist/elements/agent-mode-badge.js +110 -0
  177. package/dist/elements/agent-plan.d.ts +24 -0
  178. package/dist/elements/agent-plan.js +74 -0
  179. package/dist/elements/agent-status.d.ts +29 -0
  180. package/dist/elements/agent-status.js +46 -0
  181. package/dist/elements/agent-surface.d.ts +65 -0
  182. package/dist/elements/agent-surface.js +68 -0
  183. package/dist/elements/animated-number.d.ts +86 -0
  184. package/dist/elements/animated-number.js +118 -0
  185. package/dist/elements/approval-card.d.ts +40 -0
  186. package/dist/elements/approval-card.js +78 -0
  187. package/dist/elements/artifact-card.d.ts +30 -0
  188. package/dist/elements/artifact-card.js +58 -0
  189. package/dist/elements/background-inbox.d.ts +39 -0
  190. package/dist/elements/background-inbox.js +60 -0
  191. package/dist/elements/canvas-split.d.ts +77 -0
  192. package/dist/elements/canvas-split.js +138 -0
  193. package/dist/elements/chart.d.ts +41 -0
  194. package/dist/elements/chart.js +117 -0
  195. package/dist/elements/chat-panel.d.ts +46 -0
  196. package/dist/elements/chat-panel.js +104 -0
  197. package/dist/elements/checkpoint-history.d.ts +31 -0
  198. package/dist/elements/checkpoint-history.js +65 -0
  199. package/dist/elements/clamp.d.ts +90 -0
  200. package/dist/elements/clamp.js +109 -0
  201. package/dist/elements/code-diff.d.ts +42 -0
  202. package/dist/elements/code-diff.js +69 -0
  203. package/dist/elements/code-runner.d.ts +38 -0
  204. package/dist/elements/code-runner.js +84 -0
  205. package/dist/elements/command-palette.d.ts +48 -0
  206. package/dist/elements/command-palette.js +129 -0
  207. package/dist/elements/compaction-row.d.ts +63 -0
  208. package/dist/elements/compaction-row.js +110 -0
  209. package/dist/elements/comparison-card.d.ts +47 -0
  210. package/dist/elements/comparison-card.js +69 -0
  211. package/dist/elements/composer.d.ts +274 -0
  212. package/dist/elements/composer.js +505 -0
  213. package/dist/elements/computer-use.d.ts +40 -0
  214. package/dist/elements/computer-use.js +92 -0
  215. package/dist/elements/confidence-marker.d.ts +39 -0
  216. package/dist/elements/confidence-marker.js +64 -0
  217. package/dist/elements/connection-state.d.ts +29 -0
  218. package/dist/elements/connection-state.js +66 -0
  219. package/dist/elements/context-breakdown.d.ts +30 -0
  220. package/dist/elements/context-breakdown.js +86 -0
  221. package/dist/elements/context-usage.d.ts +99 -0
  222. package/dist/elements/context-usage.js +225 -0
  223. package/dist/elements/conversation-search.d.ts +48 -0
  224. package/dist/elements/conversation-search.js +88 -0
  225. package/dist/elements/cost-meter.d.ts +33 -0
  226. package/dist/elements/cost-meter.js +75 -0
  227. package/dist/elements/data-table.d.ts +31 -0
  228. package/dist/elements/data-table.js +67 -0
  229. package/dist/elements/day-separator.d.ts +34 -0
  230. package/dist/elements/day-separator.js +50 -0
  231. package/dist/elements/diagram.d.ts +34 -0
  232. package/dist/elements/diagram.js +75 -0
  233. package/dist/elements/document-reference.d.ts +37 -0
  234. package/dist/elements/document-reference.js +66 -0
  235. package/dist/elements/draft-restore.d.ts +27 -0
  236. package/dist/elements/draft-restore.js +54 -0
  237. package/dist/elements/edit-message.d.ts +36 -0
  238. package/dist/elements/edit-message.js +77 -0
  239. package/dist/elements/elements.css +459 -0
  240. package/dist/elements/elicitation-form.d.ts +48 -0
  241. package/dist/elements/elicitation-form.js +106 -0
  242. package/dist/elements/empty-state.d.ts +52 -0
  243. package/dist/elements/empty-state.js +98 -0
  244. package/dist/elements/end-of-turn-summary.d.ts +60 -0
  245. package/dist/elements/end-of-turn-summary.js +75 -0
  246. package/dist/elements/error-state.d.ts +29 -0
  247. package/dist/elements/error-state.js +55 -0
  248. package/dist/elements/feedback-dialog.d.ts +36 -0
  249. package/dist/elements/feedback-dialog.js +81 -0
  250. package/dist/elements/file-tree.d.ts +45 -0
  251. package/dist/elements/file-tree.js +84 -0
  252. package/dist/elements/flow-graph.d.ts +45 -0
  253. package/dist/elements/flow-graph.js +78 -0
  254. package/dist/elements/generative/chart.js +200 -0
  255. package/dist/elements/generative/faults.js +111 -0
  256. package/dist/elements/generative/icons.d.ts +41 -0
  257. package/dist/elements/generative/icons.js +190 -0
  258. package/dist/elements/generative/intrinsics.js +483 -0
  259. package/dist/elements/generative/spec.d.ts +110 -0
  260. package/dist/elements/generative/spec.js +366 -0
  261. package/dist/elements/generative.css +1145 -0
  262. package/dist/elements/generative.d.ts +144 -0
  263. package/dist/elements/generative.js +126 -0
  264. package/dist/elements/guardrail-notice.d.ts +29 -0
  265. package/dist/elements/guardrail-notice.js +63 -0
  266. package/dist/elements/image-generation.d.ts +18 -0
  267. package/dist/elements/image-generation.js +64 -0
  268. package/dist/elements/index.d.ts +116 -0
  269. package/dist/elements/index.js +115 -0
  270. package/dist/elements/inline-citation.d.ts +43 -0
  271. package/dist/elements/inline-citation.js +93 -0
  272. package/dist/elements/job-progress.d.ts +49 -0
  273. package/dist/elements/job-progress.js +74 -0
  274. package/dist/elements/launcher-bubble.d.ts +34 -0
  275. package/dist/elements/launcher-bubble.js +72 -0
  276. package/dist/elements/live-region-announcer.d.ts +130 -0
  277. package/dist/elements/live-region-announcer.js +230 -0
  278. package/dist/elements/loading-state.d.ts +27 -0
  279. package/dist/elements/loading-state.js +36 -0
  280. package/dist/elements/map-answer.d.ts +38 -0
  281. package/dist/elements/map-answer.js +93 -0
  282. package/dist/elements/markdown.d.ts +131 -0
  283. package/dist/elements/markdown.js +528 -0
  284. package/dist/elements/math-block.d.ts +51 -0
  285. package/dist/elements/math-block.js +70 -0
  286. package/dist/elements/mcp-server-panel.d.ts +41 -0
  287. package/dist/elements/mcp-server-panel.js +115 -0
  288. package/dist/elements/memory-chips.d.ts +30 -0
  289. package/dist/elements/memory-chips.js +43 -0
  290. package/dist/elements/message-actions.d.ts +38 -0
  291. package/dist/elements/message-actions.js +71 -0
  292. package/dist/elements/message-attachment.d.ts +48 -0
  293. package/dist/elements/message-attachment.js +63 -0
  294. package/dist/elements/message-branches.d.ts +25 -0
  295. package/dist/elements/message-branches.js +65 -0
  296. package/dist/elements/message-pair.d.ts +33 -0
  297. package/dist/elements/message-pair.js +65 -0
  298. package/dist/elements/message-queue.d.ts +31 -0
  299. package/dist/elements/message-queue.js +80 -0
  300. package/dist/elements/message-timing.d.ts +33 -0
  301. package/dist/elements/message-timing.js +37 -0
  302. package/dist/elements/mobile-composer.d.ts +44 -0
  303. package/dist/elements/mobile-composer.js +88 -0
  304. package/dist/elements/model-picker.d.ts +38 -0
  305. package/dist/elements/model-picker.js +69 -0
  306. package/dist/elements/number-ticker.d.ts +18 -0
  307. package/dist/elements/number-ticker.js +47 -0
  308. package/dist/elements/onboarding.d.ts +30 -0
  309. package/dist/elements/onboarding.js +75 -0
  310. package/dist/elements/permission-grant.d.ts +35 -0
  311. package/dist/elements/permission-grant.js +86 -0
  312. package/dist/elements/permission-scopes.d.ts +215 -0
  313. package/dist/elements/permission-scopes.js +249 -0
  314. package/dist/elements/prompt-library.d.ts +43 -0
  315. package/dist/elements/prompt-library.js +117 -0
  316. package/dist/elements/queue-dock.d.ts +101 -0
  317. package/dist/elements/queue-dock.js +132 -0
  318. package/dist/elements/quota-banner.d.ts +26 -0
  319. package/dist/elements/quota-banner.js +66 -0
  320. package/dist/elements/quote-reply.d.ts +44 -0
  321. package/dist/elements/quote-reply.js +79 -0
  322. package/dist/elements/range.d.ts +29 -0
  323. package/dist/elements/range.js +45 -0
  324. package/dist/elements/read-aloud.d.ts +45 -0
  325. package/dist/elements/read-aloud.js +75 -0
  326. package/dist/elements/reasoning-effort.d.ts +36 -0
  327. package/dist/elements/reasoning-effort.js +67 -0
  328. package/dist/elements/reasoning-panel.d.ts +46 -0
  329. package/dist/elements/reasoning-panel.js +75 -0
  330. package/dist/elements/recommendation-card.d.ts +45 -0
  331. package/dist/elements/recommendation-card.js +83 -0
  332. package/dist/elements/regenerate-menu.d.ts +45 -0
  333. package/dist/elements/regenerate-menu.js +51 -0
  334. package/dist/elements/replay.d.ts +217 -0
  335. package/dist/elements/replay.js +498 -0
  336. package/dist/elements/research-report.d.ts +38 -0
  337. package/dist/elements/research-report.js +69 -0
  338. package/dist/elements/retrieval-chunks.d.ts +43 -0
  339. package/dist/elements/retrieval-chunks.js +81 -0
  340. package/dist/elements/revert-dock.d.ts +159 -0
  341. package/dist/elements/revert-dock.js +327 -0
  342. package/dist/elements/reviewable-diff.d.ts +45 -0
  343. package/dist/elements/reviewable-diff.js +110 -0
  344. package/dist/elements/risk-badge.d.ts +52 -0
  345. package/dist/elements/risk-badge.js +91 -0
  346. package/dist/elements/schedule-card.d.ts +42 -0
  347. package/dist/elements/schedule-card.js +90 -0
  348. package/dist/elements/score-breakdown.d.ts +47 -0
  349. package/dist/elements/score-breakdown.js +82 -0
  350. package/dist/elements/scroll-anchor.d.ts +32 -0
  351. package/dist/elements/scroll-anchor.js +123 -0
  352. package/dist/elements/settings-panel.d.ts +52 -0
  353. package/dist/elements/settings-panel.js +107 -0
  354. package/dist/elements/shared-conversation.d.ts +39 -0
  355. package/dist/elements/shared-conversation.js +68 -0
  356. package/dist/elements/sources.d.ts +33 -0
  357. package/dist/elements/sources.js +61 -0
  358. package/dist/elements/speaker-identity.d.ts +32 -0
  359. package/dist/elements/speaker-identity.js +51 -0
  360. package/dist/elements/spec-sheet.d.ts +31 -0
  361. package/dist/elements/spec-sheet.js +45 -0
  362. package/dist/elements/stopped-run.d.ts +22 -0
  363. package/dist/elements/stopped-run.js +50 -0
  364. package/dist/elements/streaming-text.d.ts +31 -0
  365. package/dist/elements/streaming-text.js +45 -0
  366. package/dist/elements/subagent-list.d.ts +37 -0
  367. package/dist/elements/subagent-list.js +79 -0
  368. package/dist/elements/suggestions.d.ts +30 -0
  369. package/dist/elements/suggestions.js +36 -0
  370. package/dist/elements/surfaces.d.ts +85 -0
  371. package/dist/elements/surfaces.js +108 -0
  372. package/dist/elements/tail-status.d.ts +131 -0
  373. package/dist/elements/tail-status.js +94 -0
  374. package/dist/elements/terminal-block.d.ts +29 -0
  375. package/dist/elements/terminal-block.js +54 -0
  376. package/dist/elements/thinking-indicator.d.ts +18 -0
  377. package/dist/elements/thinking-indicator.js +36 -0
  378. package/dist/elements/thread-list.d.ts +35 -0
  379. package/dist/elements/thread-list.js +68 -0
  380. package/dist/elements/thread-search.d.ts +42 -0
  381. package/dist/elements/thread-search.js +99 -0
  382. package/dist/elements/timeline.d.ts +37 -0
  383. package/dist/elements/timeline.js +46 -0
  384. package/dist/elements/todo-list.d.ts +42 -0
  385. package/dist/elements/todo-list.js +59 -0
  386. package/dist/elements/tool-call.d.ts +41 -0
  387. package/dist/elements/tool-call.js +85 -0
  388. package/dist/elements/tool-error.d.ts +39 -0
  389. package/dist/elements/tool-error.js +76 -0
  390. package/dist/elements/tool-group.d.ts +45 -0
  391. package/dist/elements/tool-group.js +71 -0
  392. package/dist/elements/tool-timeline.d.ts +65 -0
  393. package/dist/elements/tool-timeline.js +88 -0
  394. package/dist/elements/trace-waterfall.d.ts +37 -0
  395. package/dist/elements/trace-waterfall.js +69 -0
  396. package/dist/elements/transcript-rules.d.ts +67 -0
  397. package/dist/elements/transcript-rules.js +95 -0
  398. package/dist/elements/turn-footer.d.ts +74 -0
  399. package/dist/elements/turn-footer.js +101 -0
  400. package/dist/elements/typing-indicator.d.ts +16 -0
  401. package/dist/elements/typing-indicator.js +51 -0
  402. package/dist/elements/verdict-row.d.ts +89 -0
  403. package/dist/elements/verdict-row.js +150 -0
  404. package/dist/elements/vocabulary.d.ts +612 -0
  405. package/dist/elements/vocabulary.js +611 -0
  406. package/dist/elements/voice-conversation.d.ts +49 -0
  407. package/dist/elements/voice-conversation.js +123 -0
  408. package/dist/elements/web-preview.d.ts +32 -0
  409. package/dist/elements/web-preview.js +66 -0
  410. package/dist/elements/web-search.d.ts +37 -0
  411. package/dist/elements/web-search.js +66 -0
  412. package/dist/elements/work-collapse.d.ts +103 -0
  413. package/dist/elements/work-collapse.js +131 -0
  414. package/dist/flow/_frame.d.ts +52 -0
  415. package/dist/flow/_frame.js +38 -0
  416. package/dist/flow/controls.d.ts +41 -0
  417. package/dist/flow/controls.js +53 -0
  418. package/dist/flow/flow.css +121 -0
  419. package/dist/flow/handles.d.ts +40 -0
  420. package/dist/flow/handles.js +61 -0
  421. package/dist/flow/index.d.ts +7 -0
  422. package/dist/flow/index.js +7 -0
  423. package/dist/flow/node-card.d.ts +71 -0
  424. package/dist/flow/node-card.js +102 -0
  425. package/dist/flow/signal-edge.d.ts +60 -0
  426. package/dist/flow/signal-edge.js +108 -0
  427. package/dist/flow/use-flow-theme.d.ts +69 -0
  428. package/dist/flow/use-flow-theme.js +109 -0
  429. package/dist/forms/_chassis.d.ts +164 -0
  430. package/dist/forms/_chassis.js +159 -0
  431. package/dist/forms/_glyphs.js +143 -0
  432. package/dist/forms/_listbox.js +55 -0
  433. package/dist/forms/autocomplete.d.ts +61 -0
  434. package/dist/forms/autocomplete.js +88 -0
  435. package/dist/forms/button-group.d.ts +47 -0
  436. package/dist/forms/button-group.js +52 -0
  437. package/dist/forms/checkbox.d.ts +51 -0
  438. package/dist/forms/checkbox.js +98 -0
  439. package/dist/forms/combobox.d.ts +73 -0
  440. package/dist/forms/combobox.js +108 -0
  441. package/dist/forms/env-var-input.d.ts +74 -0
  442. package/dist/forms/env-var-input.js +116 -0
  443. package/dist/forms/field.d.ts +89 -0
  444. package/dist/forms/field.js +102 -0
  445. package/dist/forms/file-upload.d.ts +113 -0
  446. package/dist/forms/file-upload.js +232 -0
  447. package/dist/forms/index.d.ts +26 -0
  448. package/dist/forms/index.js +26 -0
  449. package/dist/forms/input-group.d.ts +36 -0
  450. package/dist/forms/input-group.js +59 -0
  451. package/dist/forms/input.d.ts +45 -0
  452. package/dist/forms/input.js +36 -0
  453. package/dist/forms/multi-select.d.ts +62 -0
  454. package/dist/forms/multi-select.js +107 -0
  455. package/dist/forms/number-field.d.ts +39 -0
  456. package/dist/forms/number-field.js +65 -0
  457. package/dist/forms/otp-field.d.ts +54 -0
  458. package/dist/forms/otp-field.js +59 -0
  459. package/dist/forms/password-input.d.ts +46 -0
  460. package/dist/forms/password-input.js +62 -0
  461. package/dist/forms/radio-group.d.ts +50 -0
  462. package/dist/forms/radio-group.js +64 -0
  463. package/dist/forms/rating.d.ts +69 -0
  464. package/dist/forms/rating.js +103 -0
  465. package/dist/forms/scrub-input.d.ts +81 -0
  466. package/dist/forms/scrub-input.js +249 -0
  467. package/dist/forms/search-input.d.ts +54 -0
  468. package/dist/forms/search-input.js +112 -0
  469. package/dist/forms/select.d.ts +75 -0
  470. package/dist/forms/select.js +91 -0
  471. package/dist/forms/slider.d.ts +44 -0
  472. package/dist/forms/slider.js +65 -0
  473. package/dist/forms/switch.d.ts +36 -0
  474. package/dist/forms/switch.js +71 -0
  475. package/dist/forms/tags-input.d.ts +69 -0
  476. package/dist/forms/tags-input.js +148 -0
  477. package/dist/forms/textarea.d.ts +39 -0
  478. package/dist/forms/textarea.js +37 -0
  479. package/dist/forms/toggle-group.d.ts +41 -0
  480. package/dist/forms/toggle-group.js +56 -0
  481. package/dist/forms/toggle.d.ts +47 -0
  482. package/dist/forms/toggle.js +100 -0
  483. package/dist/icons/containers.d.ts +89 -0
  484. package/dist/icons/containers.js +295 -0
  485. package/dist/icons/index.d.ts +2 -0
  486. package/dist/index.d.ts +138 -0
  487. package/dist/index.js +124 -0
  488. package/dist/instruments/_ansi.js +116 -0
  489. package/dist/instruments/_data-row.d.ts +68 -0
  490. package/dist/instruments/_data-row.js +139 -0
  491. package/dist/instruments/_glyphs.js +90 -0
  492. package/dist/instruments/_ledger-disclosure.js +103 -0
  493. package/dist/instruments/_ledger.d.ts +21 -0
  494. package/dist/instruments/_ledger.js +5 -0
  495. package/dist/instruments/_motion.js +56 -0
  496. package/dist/instruments/_rem.js +21 -0
  497. package/dist/instruments/app-window-frame.d.ts +48 -0
  498. package/dist/instruments/app-window-frame.js +76 -0
  499. package/dist/instruments/code-block.d.ts +35 -0
  500. package/dist/instruments/code-block.js +173 -0
  501. package/dist/instruments/copy-button.d.ts +41 -0
  502. package/dist/instruments/copy-button.js +112 -0
  503. package/dist/instruments/data-row.d.ts +94 -0
  504. package/dist/instruments/data-row.js +151 -0
  505. package/dist/instruments/data-table.d.ts +168 -0
  506. package/dist/instruments/data-table.js +212 -0
  507. package/dist/instruments/data-tree.d.ts +126 -0
  508. package/dist/instruments/data-tree.js +303 -0
  509. package/dist/instruments/group-bar.d.ts +63 -0
  510. package/dist/instruments/group-bar.js +122 -0
  511. package/dist/instruments/index.d.ts +18 -0
  512. package/dist/instruments/index.js +18 -0
  513. package/dist/instruments/ledger.d.ts +204 -0
  514. package/dist/instruments/ledger.js +204 -0
  515. package/dist/instruments/log-viewer.d.ts +74 -0
  516. package/dist/instruments/log-viewer.js +215 -0
  517. package/dist/instruments/meter.d.ts +81 -0
  518. package/dist/instruments/meter.js +96 -0
  519. package/dist/instruments/progress.d.ts +61 -0
  520. package/dist/instruments/progress.js +79 -0
  521. package/dist/instruments/seam-grid.d.ts +66 -0
  522. package/dist/instruments/seam-grid.js +78 -0
  523. package/dist/instruments/sparkline.d.ts +39 -0
  524. package/dist/instruments/sparkline.js +85 -0
  525. package/dist/instruments/stat.d.ts +35 -0
  526. package/dist/instruments/stat.js +59 -0
  527. package/dist/instruments/table.d.ts +170 -0
  528. package/dist/instruments/table.js +237 -0
  529. package/dist/instruments/terminal-frame.d.ts +89 -0
  530. package/dist/instruments/terminal-frame.js +256 -0
  531. package/dist/instruments/two-step-button.d.ts +47 -0
  532. package/dist/instruments/two-step-button.js +166 -0
  533. package/dist/instruments/usage-chart.d.ts +55 -0
  534. package/dist/instruments/usage-chart.js +235 -0
  535. package/dist/layout/_collapse.js +46 -0
  536. package/dist/layout/_glyphs.js +45 -0
  537. package/dist/layout/_ground.d.ts +22 -0
  538. package/dist/layout/_ground.js +9 -0
  539. package/dist/layout/accordion.d.ts +52 -0
  540. package/dist/layout/accordion.js +81 -0
  541. package/dist/layout/bento.d.ts +144 -0
  542. package/dist/layout/bento.js +266 -0
  543. package/dist/layout/breadcrumb.d.ts +35 -0
  544. package/dist/layout/breadcrumb.js +67 -0
  545. package/dist/layout/card.d.ts +51 -0
  546. package/dist/layout/card.js +76 -0
  547. package/dist/layout/carousel.d.ts +96 -0
  548. package/dist/layout/carousel.js +211 -0
  549. package/dist/layout/collapsible.d.ts +74 -0
  550. package/dist/layout/collapsible.js +126 -0
  551. package/dist/layout/container.d.ts +27 -0
  552. package/dist/layout/container.js +43 -0
  553. package/dist/layout/description-list.d.ts +24 -0
  554. package/dist/layout/description-list.js +37 -0
  555. package/dist/layout/frames.d.ts +70 -0
  556. package/dist/layout/frames.js +298 -0
  557. package/dist/layout/grid.d.ts +40 -0
  558. package/dist/layout/grid.js +48 -0
  559. package/dist/layout/index.d.ts +19 -0
  560. package/dist/layout/index.js +18 -0
  561. package/dist/layout/item.d.ts +65 -0
  562. package/dist/layout/item.js +100 -0
  563. package/dist/layout/link.d.ts +48 -0
  564. package/dist/layout/link.js +63 -0
  565. package/dist/layout/pagination.d.ts +54 -0
  566. package/dist/layout/pagination.js +121 -0
  567. package/dist/layout/preview.d.ts +177 -0
  568. package/dist/layout/preview.js +628 -0
  569. package/dist/layout/resizable.d.ts +38 -0
  570. package/dist/layout/resizable.js +68 -0
  571. package/dist/layout/sidebar.d.ts +105 -0
  572. package/dist/layout/sidebar.js +158 -0
  573. package/dist/layout/stack.d.ts +46 -0
  574. package/dist/layout/stack.js +62 -0
  575. package/dist/layout/stepper.d.ts +49 -0
  576. package/dist/layout/stepper.js +81 -0
  577. package/dist/layout/timeline.d.ts +43 -0
  578. package/dist/layout/timeline.js +63 -0
  579. package/dist/lib/aria-class.js +24 -0
  580. package/dist/lib/cn.js +18 -0
  581. package/dist/lib/compose.js +38 -0
  582. package/dist/lib/cva.d.ts +18 -0
  583. package/dist/lib/cva.js +65 -0
  584. package/dist/midi/_notes.d.ts +101 -0
  585. package/dist/midi/_notes.js +183 -0
  586. package/dist/midi/_ruler.d.ts +100 -0
  587. package/dist/midi/_ruler.js +105 -0
  588. package/dist/midi/_time.d.ts +107 -0
  589. package/dist/midi/_time.js +130 -0
  590. package/dist/midi/index.d.ts +9 -0
  591. package/dist/midi/index.js +9 -0
  592. package/dist/midi/musical-time-input.d.ts +48 -0
  593. package/dist/midi/musical-time-input.js +154 -0
  594. package/dist/midi/piano-keyboard.d.ts +50 -0
  595. package/dist/midi/piano-keyboard.js +197 -0
  596. package/dist/midi/spectrum-visualizer.d.ts +65 -0
  597. package/dist/midi/spectrum-visualizer.js +213 -0
  598. package/dist/midi/threshold-rail.d.ts +72 -0
  599. package/dist/midi/threshold-rail.js +214 -0
  600. package/dist/midi/timeline-ruler.d.ts +83 -0
  601. package/dist/midi/timeline-ruler.js +152 -0
  602. package/dist/overlays/_debounce.js +34 -0
  603. package/dist/overlays/_glyphs.js +59 -0
  604. package/dist/overlays/_surface.d.ts +70 -0
  605. package/dist/overlays/_surface.js +70 -0
  606. package/dist/overlays/_wheel.js +42 -0
  607. package/dist/overlays/alert-dialog.d.ts +67 -0
  608. package/dist/overlays/alert-dialog.js +117 -0
  609. package/dist/overlays/command-palette.d.ts +93 -0
  610. package/dist/overlays/command-palette.js +193 -0
  611. package/dist/overlays/context-menu.d.ts +64 -0
  612. package/dist/overlays/context-menu.js +78 -0
  613. package/dist/overlays/dialog.d.ts +72 -0
  614. package/dist/overlays/dialog.js +124 -0
  615. package/dist/overlays/drawer.d.ts +75 -0
  616. package/dist/overlays/drawer.js +151 -0
  617. package/dist/overlays/dropdown-menu.d.ts +79 -0
  618. package/dist/overlays/dropdown-menu.js +156 -0
  619. package/dist/overlays/hover-card.d.ts +58 -0
  620. package/dist/overlays/hover-card.js +91 -0
  621. package/dist/overlays/index.d.ts +15 -0
  622. package/dist/overlays/index.js +15 -0
  623. package/dist/overlays/info-tip.d.ts +41 -0
  624. package/dist/overlays/info-tip.js +45 -0
  625. package/dist/overlays/menubar.d.ts +72 -0
  626. package/dist/overlays/menubar.js +100 -0
  627. package/dist/overlays/popover.d.ts +66 -0
  628. package/dist/overlays/popover.js +114 -0
  629. package/dist/overlays/sheet.d.ts +78 -0
  630. package/dist/overlays/sheet.js +131 -0
  631. package/dist/overlays/toast.d.ts +106 -0
  632. package/dist/overlays/toast.js +146 -0
  633. package/dist/overlays/tooltip.d.ts +57 -0
  634. package/dist/overlays/tooltip.js +95 -0
  635. package/dist/primitives/avatar.d.ts +51 -0
  636. package/dist/primitives/avatar.js +93 -0
  637. package/dist/primitives/button.d.ts +58 -0
  638. package/dist/primitives/button.js +104 -0
  639. package/dist/primitives/chip.d.ts +73 -0
  640. package/dist/primitives/chip.js +238 -0
  641. package/dist/primitives/icon-button.d.ts +41 -0
  642. package/dist/primitives/icon-button.js +59 -0
  643. package/dist/primitives/index.d.ts +10 -0
  644. package/dist/primitives/index.js +10 -0
  645. package/dist/primitives/kbd.d.ts +17 -0
  646. package/dist/primitives/kbd.js +24 -0
  647. package/dist/primitives/separator.d.ts +25 -0
  648. package/dist/primitives/separator.js +30 -0
  649. package/dist/primitives/skeleton.d.ts +20 -0
  650. package/dist/primitives/skeleton.js +27 -0
  651. package/dist/primitives/spinner.d.ts +26 -0
  652. package/dist/primitives/spinner.js +31 -0
  653. package/dist/primitives/status-dot.d.ts +51 -0
  654. package/dist/primitives/status-dot.js +57 -0
  655. package/dist/styles.css +97 -0
  656. package/dist/system/agent-skin.d.ts +27 -0
  657. package/dist/system/agent-skin.js +20 -0
  658. package/dist/system/density.d.ts +73 -0
  659. package/dist/system/density.js +111 -0
  660. package/dist/system/index.d.ts +12 -0
  661. package/dist/system/index.js +10 -0
  662. package/dist/system/overrides.d.ts +80 -0
  663. package/dist/system/overrides.js +277 -0
  664. package/dist/system/portal.d.ts +56 -0
  665. package/dist/system/portal.js +193 -0
  666. package/dist/system/prepaint.d.ts +134 -0
  667. package/dist/system/prepaint.js +120 -0
  668. package/dist/system/theme-provider.d.ts +290 -0
  669. package/dist/system/theme-provider.js +543 -0
  670. package/dist/system/theme-registry.d.ts +53 -0
  671. package/dist/system/theme-registry.js +66 -0
  672. package/dist/system/use-density.d.ts +50 -0
  673. package/dist/system/use-density.js +232 -0
  674. package/dist/system/use-isomorphic-layout-effect.js +33 -0
  675. package/dist/system/use-theme.d.ts +30 -0
  676. package/dist/system/use-theme.js +36 -0
  677. package/dist/system/vocabulary.d.ts +16 -0
  678. package/dist/system/vocabulary.js +125 -0
  679. package/dist/theming/contrast.d.ts +2 -0
  680. package/dist/theming/contrast.js +2 -0
  681. package/dist/theming/create-theme.d.ts +195 -0
  682. package/dist/theming/create-theme.js +524 -0
  683. package/dist/theming/index.d.ts +4 -0
  684. package/dist/theming/index.js +4 -0
  685. package/dist/theming/serialize.d.ts +167 -0
  686. package/dist/theming/serialize.js +241 -0
  687. package/manifest/components/accordion.json +173 -0
  688. package/manifest/components/activity-graph.json +104 -0
  689. package/manifest/components/agent-card.json +150 -0
  690. package/manifest/components/agent-handoff.json +104 -0
  691. package/manifest/components/agent-mode-badge.json +102 -0
  692. package/manifest/components/agent-pile.json +147 -0
  693. package/manifest/components/agent-plan.json +74 -0
  694. package/manifest/components/agent-status.json +87 -0
  695. package/manifest/components/agent-surface.json +93 -0
  696. package/manifest/components/alert-dialog.json +215 -0
  697. package/manifest/components/animated-number.json +75 -0
  698. package/manifest/components/app-bar.json +297 -0
  699. package/manifest/components/app-shell.json +526 -0
  700. package/manifest/components/app-window-frame.json +124 -0
  701. package/manifest/components/approval-card.json +136 -0
  702. package/manifest/components/artifact-card.json +93 -0
  703. package/manifest/components/ask-box.json +288 -0
  704. package/manifest/components/audience-icon.json +46 -0
  705. package/manifest/components/autocomplete.json +262 -0
  706. package/manifest/components/avatar-group.json +83 -0
  707. package/manifest/components/avatar.json +113 -0
  708. package/manifest/components/background-inbox.json +79 -0
  709. package/manifest/components/band.json +104 -0
  710. package/manifest/components/bento.json +306 -0
  711. package/manifest/components/branch-picker.json +146 -0
  712. package/manifest/components/breadcrumb.json +85 -0
  713. package/manifest/components/button-group.json +91 -0
  714. package/manifest/components/button.json +163 -0
  715. package/manifest/components/calendar.json +65 -0
  716. package/manifest/components/canvas-split-body.json +66 -0
  717. package/manifest/components/canvas-split-document.json +55 -0
  718. package/manifest/components/canvas-split-header.json +97 -0
  719. package/manifest/components/canvas-split-line.json +66 -0
  720. package/manifest/components/canvas-split-message.json +70 -0
  721. package/manifest/components/canvas-split-thread.json +55 -0
  722. package/manifest/components/canvas-split.json +61 -0
  723. package/manifest/components/card.json +152 -0
  724. package/manifest/components/carousel.json +242 -0
  725. package/manifest/components/catalogue-icon.json +46 -0
  726. package/manifest/components/channel-beta-icon.json +46 -0
  727. package/manifest/components/channel-matrix.json +239 -0
  728. package/manifest/components/channel-released-icon.json +46 -0
  729. package/manifest/components/chart-container.json +127 -0
  730. package/manifest/components/chart-ramp.json +82 -0
  731. package/manifest/components/chart-swatch.json +72 -0
  732. package/manifest/components/chart-tooltip-content.json +140 -0
  733. package/manifest/components/chart.json +117 -0
  734. package/manifest/components/chat-empty-state.json +55 -0
  735. package/manifest/components/chat-panel-assistant-message.json +52 -0
  736. package/manifest/components/chat-panel-composer.json +74 -0
  737. package/manifest/components/chat-panel-messages.json +52 -0
  738. package/manifest/components/chat-panel-typing.json +52 -0
  739. package/manifest/components/chat-panel-user-message.json +52 -0
  740. package/manifest/components/chat-panel.json +58 -0
  741. package/manifest/components/checkbox-group.json +70 -0
  742. package/manifest/components/checkbox.json +106 -0
  743. package/manifest/components/checkpoint-history.json +91 -0
  744. package/manifest/components/chip.json +174 -0
  745. package/manifest/components/clamp.json +125 -0
  746. package/manifest/components/cli-tool-icon.json +46 -0
  747. package/manifest/components/code-block.json +107 -0
  748. package/manifest/components/code-diff.json +104 -0
  749. package/manifest/components/code-runner.json +124 -0
  750. package/manifest/components/collapsible.json +133 -0
  751. package/manifest/components/color-area.json +62 -0
  752. package/manifest/components/color-field.json +91 -0
  753. package/manifest/components/color-picker.json +235 -0
  754. package/manifest/components/color-slider.json +84 -0
  755. package/manifest/components/color-swatch.json +79 -0
  756. package/manifest/components/colors-section.json +70 -0
  757. package/manifest/components/combobox.json +265 -0
  758. package/manifest/components/command-palette.json +222 -0
  759. package/manifest/components/compaction-row.json +128 -0
  760. package/manifest/components/comparison-card.json +98 -0
  761. package/manifest/components/composer-actions.json +65 -0
  762. package/manifest/components/composer-attach-button.json +65 -0
  763. package/manifest/components/composer-attachment-chip.json +83 -0
  764. package/manifest/components/composer-attachments.json +60 -0
  765. package/manifest/components/composer-bar.json +76 -0
  766. package/manifest/components/composer-command-item.json +85 -0
  767. package/manifest/components/composer-context.json +71 -0
  768. package/manifest/components/composer-input.json +77 -0
  769. package/manifest/components/composer-menu-item.json +75 -0
  770. package/manifest/components/composer-menu.json +89 -0
  771. package/manifest/components/composer-model-item.json +82 -0
  772. package/manifest/components/composer-model-trigger.json +81 -0
  773. package/manifest/components/composer-person-item.json +86 -0
  774. package/manifest/components/composer-send.json +86 -0
  775. package/manifest/components/composer-toolbar.json +65 -0
  776. package/manifest/components/composer-voice-button.json +71 -0
  777. package/manifest/components/composer-voice.json +81 -0
  778. package/manifest/components/composer.json +238 -0
  779. package/manifest/components/computer-use.json +98 -0
  780. package/manifest/components/confidence-marker.json +86 -0
  781. package/manifest/components/connection-state.json +103 -0
  782. package/manifest/components/container.json +75 -0
  783. package/manifest/components/context-breakdown.json +72 -0
  784. package/manifest/components/context-menu.json +134 -0
  785. package/manifest/components/context-usage.json +98 -0
  786. package/manifest/components/conversation-search.json +111 -0
  787. package/manifest/components/copy-button.json +118 -0
  788. package/manifest/components/cost-meter.json +84 -0
  789. package/manifest/components/cue-logotype.json +110 -0
  790. package/manifest/components/cue-mark.json +111 -0
  791. package/manifest/components/cue-portal-frame.json +65 -0
  792. package/manifest/components/data-row.json +222 -0
  793. package/manifest/components/data-table-pagination.json +97 -0
  794. package/manifest/components/data-table-toolbar.json +127 -0
  795. package/manifest/components/data-tree.json +185 -0
  796. package/manifest/components/date-field.json +93 -0
  797. package/manifest/components/date-picker.json +255 -0
  798. package/manifest/components/date-range-picker.json +255 -0
  799. package/manifest/components/day-separator.json +63 -0
  800. package/manifest/components/delegation-card.json +196 -0
  801. package/manifest/components/density.json +93 -0
  802. package/manifest/components/description-list.json +91 -0
  803. package/manifest/components/diagram.json +126 -0
  804. package/manifest/components/dialog.json +222 -0
  805. package/manifest/components/disclosure.json +134 -0
  806. package/manifest/components/dmx-bar.json +107 -0
  807. package/manifest/components/dmx-strip.json +117 -0
  808. package/manifest/components/document-reference.json +108 -0
  809. package/manifest/components/draft-restore.json +96 -0
  810. package/manifest/components/drawer.json +204 -0
  811. package/manifest/components/dropdown-menu.json +275 -0
  812. package/manifest/components/edit-message.json +131 -0
  813. package/manifest/components/elements-command-palette.json +122 -0
  814. package/manifest/components/elements-composer.json +71 -0
  815. package/manifest/components/elements-data-table.json +75 -0
  816. package/manifest/components/elements-timeline.json +77 -0
  817. package/manifest/components/elicitation-form.json +130 -0
  818. package/manifest/components/empty-state-composer.json +71 -0
  819. package/manifest/components/empty-state-greeting.json +49 -0
  820. package/manifest/components/empty-state-suggestion.json +60 -0
  821. package/manifest/components/empty-state-suggestions.json +49 -0
  822. package/manifest/components/empty-state.json +111 -0
  823. package/manifest/components/end-of-turn-summary.json +89 -0
  824. package/manifest/components/env-var-input.json +194 -0
  825. package/manifest/components/error-state.json +93 -0
  826. package/manifest/components/export-dialog.json +101 -0
  827. package/manifest/components/eyebrow.json +59 -0
  828. package/manifest/components/feedback-dialog.json +133 -0
  829. package/manifest/components/field-description.json +66 -0
  830. package/manifest/components/field-error.json +87 -0
  831. package/manifest/components/field-label.json +66 -0
  832. package/manifest/components/field.json +82 -0
  833. package/manifest/components/file-tree.json +96 -0
  834. package/manifest/components/file-upload.json +204 -0
  835. package/manifest/components/flow-background.json +50 -0
  836. package/manifest/components/flow-controls.json +50 -0
  837. package/manifest/components/flow-graph.json +86 -0
  838. package/manifest/components/folder-icon.json +46 -0
  839. package/manifest/components/footer.json +133 -0
  840. package/manifest/components/frac.json +68 -0
  841. package/manifest/components/frames.json +210 -0
  842. package/manifest/components/generation-loader.json +78 -0
  843. package/manifest/components/generative-ui.json +125 -0
  844. package/manifest/components/grid.json +132 -0
  845. package/manifest/components/group-bar.json +122 -0
  846. package/manifest/components/guardrail-notice.json +109 -0
  847. package/manifest/components/hover-card.json +189 -0
  848. package/manifest/components/icon-button.json +106 -0
  849. package/manifest/components/image-generation.json +73 -0
  850. package/manifest/components/info-tip.json +133 -0
  851. package/manifest/components/inline-citation.json +90 -0
  852. package/manifest/components/input-group.json +112 -0
  853. package/manifest/components/input.json +121 -0
  854. package/manifest/components/inspector.json +199 -0
  855. package/manifest/components/item.json +163 -0
  856. package/manifest/components/job-progress.json +116 -0
  857. package/manifest/components/kbd.json +52 -0
  858. package/manifest/components/launcher-bubble.json +130 -0
  859. package/manifest/components/ledger.json +330 -0
  860. package/manifest/components/link.json +94 -0
  861. package/manifest/components/live-region-announcer.json +95 -0
  862. package/manifest/components/log-viewer.json +172 -0
  863. package/manifest/components/map-answer.json +97 -0
  864. package/manifest/components/markdown-text.json +126 -0
  865. package/manifest/components/math-block.json +84 -0
  866. package/manifest/components/mcp-server-icon.json +46 -0
  867. package/manifest/components/mcp-server-panel.json +102 -0
  868. package/manifest/components/memory-chips.json +73 -0
  869. package/manifest/components/menubar.json +184 -0
  870. package/manifest/components/message-actions.json +125 -0
  871. package/manifest/components/message-attachments.json +71 -0
  872. package/manifest/components/message-branches.json +83 -0
  873. package/manifest/components/message-list.json +121 -0
  874. package/manifest/components/message-pair.json +107 -0
  875. package/manifest/components/message-queue.json +85 -0
  876. package/manifest/components/message-timing.json +69 -0
  877. package/manifest/components/message.json +149 -0
  878. package/manifest/components/meter.json +150 -0
  879. package/manifest/components/mobile-composer.json +163 -0
  880. package/manifest/components/model-picker.json +87 -0
  881. package/manifest/components/multi-select.json +255 -0
  882. package/manifest/components/musical-time-input.json +141 -0
  883. package/manifest/components/navigation-menu.json +269 -0
  884. package/manifest/components/node-card.json +144 -0
  885. package/manifest/components/node-handle.json +84 -0
  886. package/manifest/components/number-field.json +131 -0
  887. package/manifest/components/number-ticker.json +67 -0
  888. package/manifest/components/onboarding.json +100 -0
  889. package/manifest/components/otp-field.json +163 -0
  890. package/manifest/components/page-shell.json +129 -0
  891. package/manifest/components/pagination.json +108 -0
  892. package/manifest/components/panel-header.json +89 -0
  893. package/manifest/components/panel.json +71 -0
  894. package/manifest/components/password-input.json +171 -0
  895. package/manifest/components/permission-grant.json +115 -0
  896. package/manifest/components/permission-scopes.json +184 -0
  897. package/manifest/components/piano-keyboard.json +149 -0
  898. package/manifest/components/plussie.json +140 -0
  899. package/manifest/components/popover.json +231 -0
  900. package/manifest/components/preset-section.json +80 -0
  901. package/manifest/components/preview.json +294 -0
  902. package/manifest/components/progress.json +114 -0
  903. package/manifest/components/prompt-library.json +122 -0
  904. package/manifest/components/queue-dock.json +139 -0
  905. package/manifest/components/quota-banner.json +113 -0
  906. package/manifest/components/quote-reply.json +129 -0
  907. package/manifest/components/radio-group.json +83 -0
  908. package/manifest/components/radio.json +94 -0
  909. package/manifest/components/rating.json +188 -0
  910. package/manifest/components/read-aloud.json +139 -0
  911. package/manifest/components/reasoning-effort.json +95 -0
  912. package/manifest/components/reasoning-panel.json +135 -0
  913. package/manifest/components/recommendation-card.json +133 -0
  914. package/manifest/components/regenerate-menu.json +107 -0
  915. package/manifest/components/replay-player.json +124 -0
  916. package/manifest/components/research-report.json +87 -0
  917. package/manifest/components/resizable.json +91 -0
  918. package/manifest/components/retrieval-chunks.json +98 -0
  919. package/manifest/components/revert-dock.json +197 -0
  920. package/manifest/components/reviewable-diff.json +113 -0
  921. package/manifest/components/risk-badge.json +89 -0
  922. package/manifest/components/row.json +122 -0
  923. package/manifest/components/schedule-card.json +121 -0
  924. package/manifest/components/score-breakdown.json +108 -0
  925. package/manifest/components/scroll-anchor.json +84 -0
  926. package/manifest/components/scroll-area.json +114 -0
  927. package/manifest/components/scrollable-tabs-list.json +91 -0
  928. package/manifest/components/scrub-input.json +268 -0
  929. package/manifest/components/seam-cell.json +91 -0
  930. package/manifest/components/seam-grid.json +84 -0
  931. package/manifest/components/seam-list.json +56 -0
  932. package/manifest/components/search-input.json +195 -0
  933. package/manifest/components/section-header.json +140 -0
  934. package/manifest/components/segmented-control.json +178 -0
  935. package/manifest/components/select.json +253 -0
  936. package/manifest/components/separator.json +56 -0
  937. package/manifest/components/settings-panel.json +152 -0
  938. package/manifest/components/shape-section.json +70 -0
  939. package/manifest/components/shared-conversation.json +107 -0
  940. package/manifest/components/sheet.json +210 -0
  941. package/manifest/components/shimmer-label.json +61 -0
  942. package/manifest/components/sidebar.json +245 -0
  943. package/manifest/components/signal-edge.json +56 -0
  944. package/manifest/components/skeleton.json +46 -0
  945. package/manifest/components/skill-icon.json +46 -0
  946. package/manifest/components/slider.json +106 -0
  947. package/manifest/components/sources.json +97 -0
  948. package/manifest/components/sparkline.json +89 -0
  949. package/manifest/components/speaker-identity.json +66 -0
  950. package/manifest/components/spec-sheet.json +93 -0
  951. package/manifest/components/spectrum-visualizer.json +147 -0
  952. package/manifest/components/spinner.json +71 -0
  953. package/manifest/components/stack-icon.json +46 -0
  954. package/manifest/components/stack.json +143 -0
  955. package/manifest/components/stacks-matrix-icon.json +46 -0
  956. package/manifest/components/stat.json +111 -0
  957. package/manifest/components/status-bar.json +233 -0
  958. package/manifest/components/status-dot.json +97 -0
  959. package/manifest/components/stepper.json +107 -0
  960. package/manifest/components/stopped-run.json +97 -0
  961. package/manifest/components/streaming-text.json +82 -0
  962. package/manifest/components/sub.json +45 -0
  963. package/manifest/components/subagent-list.json +106 -0
  964. package/manifest/components/suggestions.json +106 -0
  965. package/manifest/components/sup.json +45 -0
  966. package/manifest/components/swap-label.json +86 -0
  967. package/manifest/components/switch.json +102 -0
  968. package/manifest/components/table-scroll-region.json +78 -0
  969. package/manifest/components/table.json +306 -0
  970. package/manifest/components/tabs.json +156 -0
  971. package/manifest/components/tags-input.json +255 -0
  972. package/manifest/components/tail-status.json +138 -0
  973. package/manifest/components/terminal-block.json +111 -0
  974. package/manifest/components/terminal-frame.json +146 -0
  975. package/manifest/components/textarea.json +104 -0
  976. package/manifest/components/theme-configurator.json +158 -0
  977. package/manifest/components/theme-provider.json +228 -0
  978. package/manifest/components/thinking-indicator.json +69 -0
  979. package/manifest/components/thread-list.json +87 -0
  980. package/manifest/components/thread-search.json +108 -0
  981. package/manifest/components/threshold-rail.json +223 -0
  982. package/manifest/components/time-boundary.json +68 -0
  983. package/manifest/components/time-field.json +93 -0
  984. package/manifest/components/timeline-ruler.json +178 -0
  985. package/manifest/components/timeline.json +131 -0
  986. package/manifest/components/title-bar.json +97 -0
  987. package/manifest/components/toast.json +140 -0
  988. package/manifest/components/todo-list.json +76 -0
  989. package/manifest/components/toggle-group.json +134 -0
  990. package/manifest/components/toggle.json +140 -0
  991. package/manifest/components/token-editor.json +106 -0
  992. package/manifest/components/tool-call-card.json +268 -0
  993. package/manifest/components/tool-call.json +147 -0
  994. package/manifest/components/tool-error.json +141 -0
  995. package/manifest/components/tool-group.json +97 -0
  996. package/manifest/components/tool-timeline.json +147 -0
  997. package/manifest/components/toolbar.json +192 -0
  998. package/manifest/components/tooltip.json +199 -0
  999. package/manifest/components/trace-waterfall.json +84 -0
  1000. package/manifest/components/tree-visibility-toggle.json +99 -0
  1001. package/manifest/components/tree.json +182 -0
  1002. package/manifest/components/turn-footer.json +133 -0
  1003. package/manifest/components/two-step-button.json +150 -0
  1004. package/manifest/components/typing-indicator.json +60 -0
  1005. package/manifest/components/universe-grid.json +155 -0
  1006. package/manifest/components/unread-divider.json +58 -0
  1007. package/manifest/components/usage-chart.json +162 -0
  1008. package/manifest/components/verdict-row.json +106 -0
  1009. package/manifest/components/voice-conversation.json +141 -0
  1010. package/manifest/components/web-preview.json +105 -0
  1011. package/manifest/components/web-search.json +108 -0
  1012. package/manifest/components/work-collapse.json +125 -0
  1013. package/manifest/fixtures.json +776 -0
  1014. package/manifest/manifest.json +5037 -0
  1015. package/manifest/tokens.json +288 -0
  1016. package/package.json +278 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,4028 @@
1
+ # @cueplusplus/ui
2
+
3
+ ## 0.13.0
4
+
5
+ ### Minor Changes
6
+
7
+ - f87c822: `Band`, the page's horizontal section — and `Footer` now spends its recipe
8
+ instead of carrying a second copy of it.
9
+
10
+ **What `Band` is.** A full-bleed element, a rule or a ground running edge to
11
+ edge, and a centred column at the measure — which is what a footer has always
12
+ been, minus the footer. Every dimension it spends belongs to the fourth axis,
13
+ so the same markup composes three ways under `data-style` with no prop
14
+ changing: `--cue-band-y` for the rhythm, `--cue-measure-prose` for the column
15
+ unless `width` names one of `Container`'s four fixed rungs, and
16
+ `--cue-band-rule` / `--cue-band-ground` for the separation. `separation` takes
17
+ `"rule"`, `"ground"`, `"bleed"` (no column — the children run the full width)
18
+ and `"none"` (rhythm alone). Two of those can come out to nothing, by design
19
+ and with nothing to warn you, and they do not vanish symmetrically: a ground is
20
+ `transparent` on `:root` and on two of the three sets, so it paints only under
21
+ `marketing`, while a rule is `0px` under `marketing` alone. The prop's
22
+ documentation carries the table, and a test holds the table to the stylesheet
23
+ so it cannot stop being true.
24
+
25
+ **What changed for existing callers.** `Footer` is built on `Band`'s recipe
26
+ now — `separation="rule"`, spent through two internal helpers rather than
27
+ through `<Band>` itself, because `Band` renders a `<section>` and `Footer` has
28
+ to keep rendering the `<footer>` that makes it the `contentinfo` landmark.
29
+ Under no style set it renders exactly what it rendered before: `--cue-band-y`
30
+ resolves to `var(--cue-space-7)`, the same rung the column always spelled, and
31
+ `--cue-band-rule` resolves to `var(--cue-border-width)`, the same 1px
32
+ `border-t` always painted. Inside a `[data-style]` subtree it now follows the
33
+ axis like any other band: a hairline and doubled rhythm under `editorial`, no
34
+ rule at all and trebled rhythm under `marketing`. **This is the first shipped
35
+ component to follow `data-style`.** An app that wants the old, fixed behaviour
36
+ inside a styled region wraps the footer in `<Density styleSet="console">`.
37
+
38
+ **What did not change.** `FooterProps` is the same four props —
39
+ `width`, `meta`, `columnClassName`, `children` — byte for byte; this is a
40
+ change of implementation, not of contract. The fine-print rule above the
41
+ meta row is still a literal `border-t border-border` and does not follow the
42
+ axis: it separates content inside the footer, not the footer from the page.
43
+
44
+ **What the published manifest says now.** The helper `Footer` spends `Band`'s
45
+ recipe through first landed in `band.tsx`, a public component file — and
46
+ `packages/docgen`'s token scanner only follows a component's imports into an
47
+ underscore-prefixed chassis file, the same convention `chrome/_drag.ts` already
48
+ uses. It never opened `band.tsx`, so `footer.json`'s `tokensUsed` — and the
49
+ token chips on `/docs/components/footer` — silently lost `--cue-space-7` and
50
+ gained neither of the two names that replaced it. The recipe now lives in
51
+ `chrome/_band.ts`, and `footer.json` publishes ten names: the nine it shipped
52
+ before this branch, less the `--cue-space-7` that `--cue-band-y` replaced, plus
53
+ `--cue-band-y` and `--cue-band-rule`. Those two
54
+ are the whole of what a `<footer>` takes from the recipe, so they are the whole
55
+ of what that file holds — the scanner reads a chassis file whole and cannot see
56
+ which branch a call takes, and `Footer` fixes `separation` at the literal
57
+ `"rule"` and types `width` as `ContainerWidth`. `Band`'s other two style-owned
58
+ spends, `--cue-band-ground` and `--cue-measure-prose`, are unreachable from a
59
+ footer and are written in `band.tsx` beside the prop values that select them,
60
+ so `band.json` publishes all five and `footer.json` claims neither. Nothing
61
+ else in either component's manifest entry moved.
62
+
63
+ **What it costs.** Nothing at runtime; the manifest grows one component.
64
+
65
+ **Correction, no behaviour change.** `layout/preview.tsx`'s docblock cited the
66
+ group pages' tooled `Preview` count as 325; `Band` landing raised the
67
+ library's component count and the tooled figure to 326. Comment-only, caught
68
+ by the branch's own review sweep, and folded in here because it is the last
69
+ commit that touches this package's authored tree before the note.
70
+
71
+ - 5b40f07: **A fourth composable axis: `data-style`, for page composition.**
72
+
73
+ Theme, density and font already answer "what colour", "how tight" and "which
74
+ typeface". `data-style` answers the question none of them could: how a _page_ is
75
+ composed — reading measure, prose line height, and the rhythm of a band. Three
76
+ sets ship. `console` is the current look made explicit and provably unchanged,
77
+ `editorial` widens the measure and opens the leading for long-form reading, and
78
+ `marketing` narrows the measure and spends the vertical space a landing page
79
+ wants.
80
+
81
+ Each set declares `--cue-measure-prose`, `--cue-leading-prose`, `--cue-band-y`,
82
+ `--cue-band-rule` and `--cue-band-ground`; `editorial` and `marketing` add
83
+ `--cue-style-display` for a display type scale. Only the display rung composes
84
+ with `--cue-font-scale`, and a name added to the set vocabulary without an
85
+ answer is a build error rather than a silent guess.
86
+
87
+ **Driving it.** `<ThemeProvider styleSet="editorial">` stamps the attribute on
88
+ its own element and on `<html>`, `useTheme().styleSet` and `setStyleSet()` read
89
+ and drive it, and it persists beside theme, density, font and mode under a
90
+ `style` key — written only when there is a set. A pre-paint script applies a
91
+ stored set before the first paint, so turning one on _or off_ lands on the first
92
+ frame rather than the first commit. The set is carried into overlay portals,
93
+ `<Density styleSet>` islands and `<Preview styleSet>` stages, so an overlay
94
+ follows the subtree it was opened from and three sets can be shown side by side
95
+ on one page.
96
+
97
+ `useTheme().styleSet` is **the set in force, not the set named here**. A
98
+ provider that names no set of its own reports the one stamped around it —
99
+ including a `<Density styleSet>` island sitting between it and an outer provider
100
+ — exactly as the DOM resolves it. That is deliberate: it makes the context value
101
+ and the set a portal or a stage carries out of that DOM one value rather than
102
+ two that can disagree. `null` means nothing anywhere has chosen a set, which is
103
+ not the same fact as `console`.
104
+
105
+ `setStyleSet(null)` is the same distinction from the writing side: it clears the
106
+ set this provider **names**, not the set that applies to it. A nested provider
107
+ that clears its own goes back to inheriting whatever is stamped around it, and
108
+ `styleSet` reports that inherited set, because it is what the cascade resolves
109
+ to. At the root the two coincide, which is why only a nested provider shows the
110
+ difference. To positively leave an inherited set, pass `DEFAULT_STYLE_SET`: it
111
+ stamps an attribute whose block restates the `:root` defaults token for token,
112
+ and an attribute is the only thing that out-specifies an ancestor's.
113
+
114
+ Restating a set on a `<Density>` island is what re-resolves its band rhythm at
115
+ that island's rung: `--cue-band-y` is built from `--cue-space-7`, and a custom
116
+ property's `var()` references are substituted where they are declared, so an
117
+ island that only changes density inherits the rhythm already resolved above it.
118
+
119
+ `STYLE_SETS`, `DEFAULT_STYLE_SET`, `STYLE_CONTRACT`, `STYLE_SET_CONTRACT` and
120
+ the `StyleSet` type are exported from `@cueplusplus/tokens`, so an application
121
+ can type a prop and validate a stored preference against the same closed list
122
+ the stylesheet was built from.
123
+
124
+ **What it costs.**
125
+
126
+ - **Every consumer carries 144 more bytes of blocking inline pre-paint script**,
127
+ whether or not the application uses the axis — a typical script grows from 995
128
+ bytes to 1139. It has to read the stored set and stamp or clear the attribute
129
+ before the first paint, and an application that never touches the axis cannot
130
+ opt out of the branch that decides there is nothing to stamp.
131
+ - **The attribute is the generic `data-style`, not `data-cue-style`.** An
132
+ application already using `data-style` on `<html>` for its own purposes will
133
+ have it overwritten while a `<ThemeProvider>` is mounted, and cleared when the
134
+ provider holds no set; the original value is restored on unmount. The generic
135
+ name was chosen for readability, and this collision is the price. If you use
136
+ `data-style` on `<html>` yourself, move it before upgrading.
137
+
138
+ **What is unchanged.** A page that names no set is untouched: no attribute is
139
+ stamped anywhere, no stored preference gains a key, and a stored `"style": null`
140
+ is refused and scrubbed on the next load like every other value nothing
141
+ declares. `console`'s declarations are asserted at build time to be exactly the
142
+ `:root` defaults, and a browser test compares every `--cue-*` on two identical
143
+ subtrees — one bare, one under `console` — and requires them to compute
144
+ identically. Switching to the default set changes nothing, as a checked fact
145
+ rather than a promise.
146
+
147
+ One note on `editorial`'s hairline: its 0.5px `--cue-band-rule` paints a
148
+ measured 1px on Chromium at either device pixel ratio and 0.5px only on retina
149
+ WebKit, so the separation it actually ships is the doubled `--cue-space-7`
150
+ rhythm with a hairline under it.
151
+
152
+ ### Patch Changes
153
+
154
+ - b1c2f25: **Every package now ships the licence it declares.** All sixteen have said `"license": "MIT"` in
155
+ their manifest since they were first published, and none of them carried the text. Installing one
156
+ gave you a licence field and nothing to read; anyone auditing their dependency tree found a package
157
+ that claimed a licence it did not include. Each package root now holds the MIT notice, and npm packs
158
+ a root `LICENSE` whether or not `files` lists it, so it is in the tarball you install.
159
+
160
+ `@cueplusplus/ui` additionally ships `NOTICE.md`, the third-party notice for the assistant-ui code
161
+ vendored into its elements. That one is not automatic — `files` had to name it — and it is byte-for-byte
162
+ the notice that already sat beside the source it covers, because a notice that travels without the
163
+ bytes it describes is not a notice.
164
+
165
+ Nothing else about these packages changed: same exports, same versions of every dependency, same
166
+ registry. If you are upgrading only for this, the diff you receive is licence text.
167
+
168
+ - 0b30ba6: `ImageGeneration` now clears its blur with `blur-none`. It used to ask for `blur-0`, which is a Tailwind v3 class that v4 has no rule for, so the class did nothing at all. You will not see a difference — the image already came out of its blur when generation finished, because a filter transition runs to no filter just as it runs to zero blur — but the class now says what it does, and the elements sync renames it, so a re-sync from assistant-ui will not put it back.
169
+ - 6f791a8: Two families of hard-coded values become tokens, with nothing rendering differently.
170
+
171
+ `PageShell` and `Container` spell their three capped measures as `max-w-(--cue-measure-narrow)` / `-wide` / `-broad` rather than as `max-w-[42rem]` / `[54rem]` / `[84rem]`. Nothing about the props changes — `PageWidth` and `ContainerWidth` still offer `narrow` / `wide` / `broad` / `full`, still default to `narrow`, and `full` still caps nothing.
172
+
173
+ And the 63 `leading-relaxed` utilities across 55 of this library's modules — dialogs, menus, settings panels, chat rows, tooltips, cards, the two shared class strings behind every collapsible and every popup surface — now spend `--cue-leading-ui`, which `@cueplusplus/tokens` pins at 1.625, Tailwind's own value for that utility. Ninety-one components pick the token up in their `tokensUsed`. `leading-none` is untouched: line-height 1 on an icon or a numeral is a shape decision, not a reading one, so a reading axis has nothing to say about it.
174
+
175
+ Both are exact no-ops today. What they buy you is a knob: one declaration of `--cue-leading-ui` or `--cue-measure-wide`, anywhere in your own CSS, and every component that spends it follows — with no fork and no `className` at the call site.
176
+
177
+ - f813329: `Progress` now names its bar in the HTML your server sends. A screen-reader user on a server-rendered page heard "progress bar, 40%" and never the caption beside it: the words were on the page, but the `role="progressbar"` only pointed at them once the browser had hydrated — and on a page that never hydrates, never. Base UI makes that connection from a layout effect; this layer now hands the caption and the bar the same `useId` while it renders, so the name is in the markup from the first byte and the same reader hears "Rendering, progress bar, 40%" whether or not the JavaScript has arrived. Nothing moves on screen, and a bar you named yourself with `aria-label` or `aria-labelledby` keeps the name you gave it — in the served markup and after hydration alike, which is asserted in both renders rather than in the one the change was written in.
178
+ - Updated dependencies [b1c2f25]
179
+ - Updated dependencies [31a7567]
180
+ - Updated dependencies [5b40f07]
181
+ - @cueplusplus/theme-base@1.0.2
182
+ - @cueplusplus/tokens@0.13.0
183
+
184
+ ## 0.12.0
185
+
186
+ ### Minor Changes
187
+
188
+ - 11c7307: A design canvas — `Frames` — and a `Preview` that knows how to sit on one.
189
+
190
+ **Nothing to do.** `Frames` is a new export, all five of `Preview`'s new props are optional, and no
191
+ prop that already existed changed shape. A `Preview` called the way you call one today renders
192
+ **byte-identical markup** — the same string, class for class — and this package's suite holds it to
193
+ three goldens captured from the tree before the change rather than to a promise.
194
+ `@cueplusplus/tokens` and `@cueplusplus/brand-tokens` move with it as one fixed family, and neither
195
+ changes. The snippets below are fragments.
196
+
197
+ **`Frames`** (`@cueplusplus/ui`, and `@cueplusplus/ui/layout`) is a new compound in two parts:
198
+ `Frames.Root` is the canvas, `Frames.Item` is one captioned cell on it. `FramesHeight`,
199
+ `FramesRootProps` and `FramesItemProps` ship beside them.
200
+
201
+ ```tsx
202
+ import { Button } from "@cueplusplus/ui";
203
+ import { Frames, Preview } from "@cueplusplus/ui/layout";
204
+
205
+ <Frames.Root label="Button frames" hint="drag to pan · 2 frames">
206
+ <Preview presentation="frame" label="Tone" count={2}>
207
+ <div className="flex flex-wrap items-end gap-(--cue-space-6)">
208
+ <Frames.Item caption="accent">
209
+ <Button variant="primary">Take cue</Button>
210
+ </Frames.Item>
211
+ <Frames.Item caption="default">
212
+ <Button>Take cue</Button>
213
+ </Frames.Item>
214
+ </div>
215
+ </Preview>
216
+ <Preview presentation="frame" label="Size" count={3}>
217
+
218
+ </Preview>
219
+ </Frames.Root>;
220
+ ```
221
+
222
+ - **Short and wide on purpose.** `height` is `"short"` (18.5rem, the default) or `"tall"` (27rem),
223
+ and a CSS length in `style` beats both. The canvas scrolls sideways and never down.
224
+ - **`label` is required**, and it is not decoration: the canvas is a `role="region"` with
225
+ `tabIndex={0}`, and a scroll region with no name is one a screen reader announces as nothing.
226
+ - **The keyboard scrolls it.** Left and Right step one rung of `--cue-space-8` — read off the canvas
227
+ itself, so a board inside a `<Density>` island steps by that density's rung — PageUp and PageDown
228
+ step a viewport, and Home and End reach the ends. Smoothly, or instantly for a reader who asked
229
+ for less motion.
230
+ - **Dragging pans it**, by `scrollLeft` and not by a transform, so the scrollbar stays real and a
231
+ popover opened inside a frame stays anchored. Only the primary button drags, and a drag that
232
+ starts on a `button`, `a`, `input`, `select`, `textarea`, or an element carrying a `role` of its
233
+ own, is never a pan — so a frame's own controls keep working. `pan={false}` binds no pointer
234
+ listener at all and leaves the keyboard alone.
235
+ - `ground` is `"plain"`, `"dots"` (the default) or `"none"`, and `hint` is an optional node in the
236
+ bottom-right corner, which is where a line like "drag to pan · 8 frames" goes.
237
+ - **It does not zoom, and it does not inspect its children** — it never counts them, indexes them or
238
+ selects one. The count in `hint` is one you passed it. Zoom is a refusal rather than a gap: a
239
+ transformed subtree breaks every popover anchored inside it.
240
+ - **`Frames.Item` goes inside a `Preview`**, not directly inside `Frames.Root`: the canvas holds
241
+ frames, a frame holds a stage, and the stage holds these. `caption` is the line under the
242
+ specimen, and `selected` draws the outline on the one a reader is inspecting.
243
+ - **A stage does not arrange several items — you do.** A stage is a centred grid in both
244
+ presentations, so two items left to it stack. Put them in a row wrapper:
245
+ `<div className="flex flex-wrap items-end gap-(--cue-space-6)">` — `items-end` so specimens of
246
+ different heights share a baseline, `flex-wrap` so a narrow frame folds rather than clips. Every
247
+ example in this library uses that one spelling.
248
+ - The whole `Frames` module is a client component (`"use client"`), as `Preview` already was — so
249
+ `Frames.Item` is one too.
250
+
251
+ **`Preview` gains five optional props.** `name` and `count` are drawn only in the new `"frame"`
252
+ presentation; `ground` and `captions` apply to a panel's stage just as well.
253
+
254
+ ```tsx
255
+ <Preview
256
+ presentation="frame"
257
+ label="Button tone"
258
+ name={<ToneMenu />}
259
+ count={3}
260
+ captions={false}
261
+ >
262
+
263
+ </Preview>
264
+ ```
265
+
266
+ - **`presentation`** is `"panel" | "frame"`. `"panel"` is the default and is what `Preview` has
267
+ always drawn: a bordered card, `tools` floating over the stage's top right, a footer under it.
268
+ `"frame"` draws the label row _above_ the stage instead of over it, moves `tools` into that row
269
+ (where they reserve no room on the stage), makes the stage transparent so whatever it sits on
270
+ shows through, and puts four crosshair marks at its outer corners.
271
+ - **`name`** is the label row's leading slot, drawn only in a frame — a plain node, or your own
272
+ control: pass a `DropdownMenu` whose trigger is the frame's name. Unset, the row opens with
273
+ `label` as text. A slot and not a built-in menu, because this component imports no overlay, and a
274
+ page that draws seventeen Previews should not have to pay for one.
275
+ - **`count`** is the figure drawn after the name, in a frame only. Nothing is counted for you, and
276
+ `0` is a figure it draws.
277
+ - **`ground`** is the stage's ground — `"plain"`, `"dots"` or `"none"`. It defaults to `"dots"` in a
278
+ panel, which is the bench this library has always drawn, and to `"none"` in a frame, where the
279
+ canvas under it already carries a ground and two dot grids at different pitches read as a moiré.
280
+ - **`captions={false}`** turns the `Frames.Item` captions off on that stage, and it is paint and
281
+ nothing more: `opacity: 0` and `pointer-events: none`, never `visibility: hidden` and never
282
+ `display: none`. Each caption keeps its box, so nothing re-flows, and keeps its place in the
283
+ accessibility tree, so a screen reader still reads it — a caption is what names one specimen apart
284
+ from the next. Drop the `caption` prop if the words should not be in the page at all.
285
+
286
+ `PreviewPresentation` and `PreviewGround` are exported beside `PreviewProps`.
287
+
288
+ **Frame chrome follows the page, exactly as a panel's does.** `theme`, `mode`, `density` and `font`
289
+ still land on the stage and nowhere else; the label row, the `name` slot, the count, the tools, the
290
+ crosshairs and the footer stay in the scope around the Preview.
291
+
292
+ `manifest/manifest.json`, `manifest/components/frames.json` and `manifest/components/preview.json`
293
+ were regenerated with them.
294
+
295
+ ### Patch Changes
296
+
297
+ - 7e8857d: Frame captions, preview footers and bento eyebrows are small again, instead of taking their size from whatever sat above them.
298
+
299
+ **Nothing to do.** No prop changed shape, no element moved, no slot was renamed. Six class strings
300
+ changed one word each; a page that looked right before looks the same, and the lines described
301
+ below get smaller.
302
+
303
+ **What you saw.** Five lines of chrome across three components drew at the size of the text around
304
+ them rather than at the smallest rung of the type ladder:
305
+
306
+ - `Preview`'s footer row, and the count beside a frame's name (`presentation="frame"`) — which sat
307
+ _larger_ than the name it followed, though it is the quieter of the two;
308
+ - `Frames.Root`'s corner hint, the "drag to pan · 8 frames" line;
309
+ - `Frames.Item`'s caption under each specimen;
310
+ - `Bento.Tile`'s eyebrow, the mono line above a tile's title.
311
+
312
+ Each rendered at the font size of its nearest styled ancestor, so the same caption was one size on
313
+ a page whose section set a size and another size on a page that did not.
314
+
315
+ **Why.** All five asked for `--cue-text-meta`, and there is no such token: `@cueplusplus/tokens`
316
+ publishes six type rungs — `micro`, `label`, `ui`, `body`, `emphasis`, `title` — and `meta` is not
317
+ one of them. A `var()` naming a property nothing declares makes the whole declaration invalid at
318
+ computed-value time, so the browser threw the `font-size` away and the element inherited. That is
319
+ why nothing warned: the class attribute was well-formed, the markup was identical, and only the
320
+ rendered page was wrong.
321
+
322
+ They now ask for `--cue-text-micro`, the rung those lines were always meant to sit on and the one
323
+ this library already uses for every other mono eyebrow and caption. That is **8px** at the
324
+ `compact` default, and 8 / 8 / 9 / 10 / 11 px across `ultra-compact` through `ultra-large`, before
325
+ any `fontScale` you have set on the provider.
326
+
327
+ **A sixth line, found by the guard below once it was extended, and a different mistake.** `Drawer`'s grab handle — the
328
+ little bar at the top of a bottom drawer, or the side of a side one — asked for
329
+ `rounded-(--radius-full)`, and there is no such Tailwind theme variable: this system's bridge
330
+ publishes `--radius-control`, `--radius-surface` and `--radius-overlay`, and Tailwind's own scale
331
+ runs `--radius-xs` through `--radius-4xl`. So the handle drew a 4px-thick rectangle with square
332
+ ends where it should have been a pill. It is now `rounded-full`, which is the spelling the other
333
+ 184 pill-shaped elements in this library already use — `StatusDot`, `Avatar`, `Spinner`,
334
+ `InfoTip` — and which needs no theme variable at all.
335
+
336
+ **A guard came with it.** `packages/ui` now fails its own suite if a class string anywhere under
337
+ `src/` names a custom property nothing declares — checked against **both** vocabularies a class can
338
+ spend from: the `--cue-*` token layer this package builds on, and the Tailwind `@theme` layer,
339
+ which is this system's bridge (`--radius-control`, `--text-body`, `--color-accent`) together with
340
+ Tailwind's own default scale, since an app that imports `tailwindcss` keeps both. So the next
341
+ misspelling in either is a red test rather than a page nobody measured. A property a component
342
+ sets on its own element, and one spent with a `var()` fallback, are both still allowed — neither
343
+ can go invalid. Properties another library publishes on its elements (Base UI's `--popup-width`,
344
+ `--available-height`) are left alone: nothing in this repository could be the authority on whether
345
+ one of those exists.
346
+
347
+ `manifest/manifest.json`, `manifest/components/preview.json`, `manifest/components/frames.json` and
348
+ `manifest/components/bento.json` were regenerated: each recorded `--cue-text-meta` in its
349
+ `tokensUsed`, which is how an unresolvable property had been published as part of the contract.
350
+
351
+ - 3f1aa4a: A click on a control's own words inside a `Frames` canvas is no longer swallowed by the board.
352
+
353
+ **Nothing to do.** No prop changed shape and no markup moved; a canvas that behaved correctly
354
+ before behaves the same way now.
355
+
356
+ **What was broken.** On a `Frames.Root`, a menu row, a listbox option, a tab — anything this
357
+ library draws as a `<div role="…">` with its label in a `<span>` inside it — did nothing when you
358
+ clicked the words. The same row worked from the keyboard, and it worked if you happened to click
359
+ its padding a few pixels from the edge, which is what made it read as a broken menu rather than a
360
+ broken canvas. The most visible case was the frame name menu on a design board: pick a frame, click
361
+ its name, and the menu stayed open with nothing swapped.
362
+
363
+ **Why.** The canvas decides on every `pointerdown` whether you are pressing something or grabbing
364
+ the board. It looked for `button`, `a`, `input`, `select` and `textarea` _anywhere above the
365
+ pointer_, but for an ARIA `role` it looked **only at the element directly under it** — so the
366
+ `<span>` holding a menu row's text was neither, the canvas took the pointer capture, and the
367
+ `pointerup` and `click` were retargeted to the board. A control's own contents are part of the
368
+ control, and now they are treated that way: the roles a pointer operates — `menuitem`,
369
+ `menuitemcheckbox`, `menuitemradio`, `option`, `tab`, `treeitem`, `button`, `link`, `checkbox`,
370
+ `radio`, `switch`, `slider`, `spinbutton`, `scrollbar`, `separator`, `textbox`, `searchbox` — are
371
+ matched from the pointer upwards, exactly as `button` always was.
372
+
373
+ **Panning is unchanged.** A container role is still a thing to grab, not a thing to press: a drag
374
+ that starts inside a `role="group"` frame, or on the canvas's own `role="region"` ground, still
375
+ pans, and so does a drag across a `role="grid"` table, a `role="menu"`'s own padding or a
376
+ `role="tablist"`'s. Only the elements that _are_ a control, and their contents, are excluded — and
377
+ an element bearing any `role` at all is still excluded when the pointer is on it directly, as it
378
+ was before. `pan={false}` still binds no pointer listener.
379
+
380
+ - @cueplusplus/tokens@0.12.0
381
+
382
+ ## 0.11.1
383
+
384
+ ### Patch Changes
385
+
386
+ - 8ddd141: The manifest's fixture catalogue grows from 18 entries to 106, and 76 props across 74 components
387
+ now name one in `control.fixture`. A code generator, playground or agent reading
388
+ `manifest/components/<name>.json` now finds a named sample for props such as `TodoList.items`,
389
+ `CodeDiff.lines`, `Combobox.items` and `Breadcrumb.items`, where before it found nothing to fill
390
+ them with.
391
+
392
+ **Nothing to do.** No prop, type or export changed, and apart from the one fix at the end of this
393
+ note this release touches only the files under `manifest/`. `@cueplusplus/tokens` and `@cueplusplus/brand-tokens` move to 0.11.1 with it,
394
+ as one fixed family, and neither changes.
395
+
396
+ `manifest/fixtures.json` gains 88 entries:
397
+
398
+ - **Six short text fixtures** (kind `node`): `text.heading`, `text.count`, `text.total`,
399
+ `text.detail`, `text.author` and `text.time` — a heading, a figure, the figure it is out of, a
400
+ qualifier under a figure, an author's name and a clock time. Like `children.label` in 0.11.0,
401
+ they are for a playground or propSet to pick by hand, and no prop's `control.fixture` names them.
402
+ - **82 data fixtures** (71 `array`, 11 `opaque`), each a sample taken from its component's own
403
+ documentation specimen: todo items, diff hunks, trace spans, log lines, chart points, sources,
404
+ threads, models and the like. 75 of them list the printed type they fit in `accepts` —
405
+ `agent.todos` accepts `readonly TodoItem[]`, `layout.show-crumbs` accepts
406
+ `readonly BreadcrumbItem[]` — and every one of those 75 is now the `control.fixture` of at least
407
+ one prop. The other seven (`agent.subagent-progress`, `agents.window-start`,
408
+ `agents.window-end`, `chrome.expanded-front`, `composer.model-names`,
409
+ `knowledge.rig-dashboard`, `knowledge.trait-labels`) claim no type, so they are only ever picked
410
+ by hand.
411
+
412
+ An entry carries an id, a kind, a label and `accepts`, never the value itself. The values live with
413
+ the tools that render or export them: the docs playground, and the studio, whose bridge
414
+ `@cueplusplus/studio-mcp` exports each one as code from 0.1.1.
415
+
416
+ In `manifest/components/`, every change adds a `control.fixture` to a prop that had none, and no
417
+ existing one moved. `Combobox.items` and `MultiSelect.items` share `forms.fixture-types`, and
418
+ `FlowGraph` (`nodes`, `edges`) and `SubagentList` (`agents`, `summaryAgent`) gain two each.
419
+ `manifest.json` changes only in the SHA-256 of each record that changed.
420
+
421
+ Two records also correct the specimen code they embed:
422
+
423
+ - `meter.json` passed `paceTick={50}`, but `paceTick` is how far through the window you are, from
424
+ 0 to 1, so the example drew no tick. It now passes `0.6`, labelled "60% of period elapsed".
425
+ - `math-block.json`, and `frac.json`, `sub.json` and `sup.json`, which embed the same specimen,
426
+ now write `visibleSteps={3}` instead of `visibleSteps={MATH.length}`. The count is the same, but
427
+ a tool that reads only literals can now read it.
428
+
429
+ One fix: `ScrollAnchor` (`@cueplusplus/ui/elements`) now stops its append timer once every message
430
+ has landed, where before the timer went on firing every 1.3 seconds for as long as the element was
431
+ mounted, though nothing on screen changed.
432
+
433
+ - @cueplusplus/tokens@0.11.1
434
+
435
+ ## 0.11.0
436
+
437
+ ### Minor Changes
438
+
439
+ - 8a41dcb: Three new components — `Preview`, `Inspector` and `CodeBlock`, each also exported from the root
440
+ barrel — plus a layout fix to `Bento.Tile` and newly documented `aria-label` and `placeholder`
441
+ props on the text inputs and `Toggle`.
442
+
443
+ **Nothing to do: every addition is a new export, and nothing that already compiled changed.** The
444
+ snippets below are fragments; each component has a full, copy-paste quick start in the package
445
+ README (`node_modules/@cueplusplus/ui/README.md`).
446
+
447
+ **`Preview`** (`@cueplusplus/ui/layout`) frames one component: a stage that can carry its own
448
+ theme, a slot for tools at the top right, and a footer. `label` is required, because the frame is
449
+ a `role="group"` named by it.
450
+
451
+ ```tsx
452
+ import { Chip } from "@cueplusplus/ui";
453
+ import { Preview } from "@cueplusplus/ui/layout";
454
+
455
+ <Preview label="Chip preview" theme="terminal" density="compact" resizable>
456
+ <Chip tone="accent">Standby</Chip>
457
+ </Preview>;
458
+ ```
459
+
460
+ - **The stage is a theme island; the chrome is not.** `theme`, `mode`, `density` and `font` land
461
+ on the stage and nowhere else. `tools`, `footer`, the resize grips and anything they open stay in
462
+ the page's scope. With none of the four set, the stage carries no attribute at all and inherits
463
+ the page.
464
+ - **Naming a theme does not load it.** The stage is scoped with `[data-theme="<name>"]`, so that
465
+ theme's stylesheet (`@cueplusplus/theme-<name>/theme.css`) must already be on the page.
466
+ - A stage `mode` with no `theme` restates the page's theme beside it, and a stage `theme` with no
467
+ `mode` repeats the page's mode, because a theme's light block is one compound selector. A stage
468
+ `density` names the nearest theme, as `<Density>` does.
469
+ - Overlays opened on the stage follow its theme, mode and density. A font pairing stops at the
470
+ stage: portals keep the page's. A stage that sets `theme` or `mode` also keeps the page's
471
+ `ThemeProvider` `overrides` off those overlays, so a popover matches the stage it opened from.
472
+ Inside a stage whose `mode` is `"dark"` or `"light"`, `useTheme().resolvedMode` reports that
473
+ mode; with `"system"` it reports the page's.
474
+ - Changing `theme`, `mode`, `density` or `font` keeps the specimen mounted, so its uncontrolled
475
+ state — a typed value, an open disclosure — survives the switch.
476
+ - The specimen sits centred on a dot grid. One wider than the stage keeps its start edge in view
477
+ and the frame scrolls to the rest. The stage reserves a minimum height before its specimen
478
+ loads, and with no `footer` its bottom corners follow the frame's rounded border.
479
+ - **The tools never cover the specimen.** With `tools`, the stage reserves their corner: it pads
480
+ its top by the tools' whole depth, measured in the page's density where the tools are drawn,
481
+ so a stage given a `density` of its own still clears them. The stage reserves this space only
482
+ when `tools` is set; a Preview without tools draws exactly the same markup.
483
+
484
+ `resizable` makes the stage resizable by pointer and keyboard:
485
+
486
+ - `resizable` (`true`, or `{ axis, minWidth, maxWidth, minHeight, maxHeight }`) adds a grip on
487
+ each axis, within 160–1280 × 96–960 px by default; `axis: "x"` or `"y"` keeps one. Each edge grip
488
+ is a focusable `separator` valued in pixels: arrow keys step 8 px, Shift+arrow 64 px, and
489
+ Home/End jump to the bounds. A corner grip drags both axes with the pointer, a double-click
490
+ resets an axis (the corner resets both), and a `W × H` readout shows while dragging.
491
+ - A resizable Preview fills its container's width. Its width bound in force is the smaller of
492
+ `maxWidth` and the frame's own width, re-measured with a `ResizeObserver`, so it stays inside a
493
+ grid track as the viewport changes; a frame narrower than `minWidth` lowers the floor with it,
494
+ and a width a reader chose comes back when the track widens again. The height is bounded by
495
+ `maxHeight` alone, and a taller stage grows the frame.
496
+ - An axis nobody has resized follows the stage's natural size live — a specimen that grows after
497
+ it loads, a track that widens, a Preview mounted hidden and shown later — and is drawn within the
498
+ same bounds its grip announces, so the first key press or drag starts from what is on screen.
499
+ - Without `resizable` there are no grips, and nothing is measured or observed. `Preview` is a
500
+ client component (`"use client"`).
501
+
502
+ **`Inspector`** (`@cueplusplus/ui/chrome`) is the layout of a settings panel, a properties sidebar
503
+ or a wrench menu: titled sections of label-and-control rows, with a footer. The controls are the
504
+ library's own.
505
+
506
+ ```tsx
507
+ import { Inspector } from "@cueplusplus/ui/chrome";
508
+
509
+ <Inspector.Root>
510
+ <Inspector.Section label="Variants">
511
+ <Inspector.Row label="Size">
512
+ <SegmentedControl items={sizes} />
513
+ </Inspector.Row>
514
+ <Inspector.Row label="Label" htmlFor="label">
515
+ <Input id="label" />
516
+ </Inspector.Row>
517
+ </Inspector.Section>
518
+ <Inspector.Footer note="preset · primary" action={<Button>Reset</Button>} />
519
+ </Inspector.Root>;
520
+ ```
521
+
522
+ - `Inspector.Section` takes a `label`, drawn as a `SectionHeader` and used as the group's name.
523
+ - `Inspector.Row` is one column until the Inspector itself is 18rem wide, then label beside
524
+ control. It is a container query, so a row follows the width of the popover or sidebar it sits
525
+ in, not the viewport's.
526
+ - **How a row names its control.** With `htmlFor`, the label is a `<label>` for that id. Without
527
+ it, the row lays its `label` on the control as `aria-label`, but only when the control is a
528
+ single element not already named by `aria-label` or `aria-labelledby`; text, lists and fragments
529
+ are left as they are. The row has no role of its own and never names itself, so a
530
+ `SegmentedControl`, which is already a group, is not announced twice.
531
+ - `Inspector.Footer` puts a quiet `note` at the start and one `action` at the end. A lone `action`
532
+ still sits at the end edge.
533
+ - Static markup — no hooks, no `"use client"` — so a server component can render it.
534
+
535
+ **`CodeBlock`** (`@cueplusplus/ui/instruments`) is a block of code with a caption and a copy
536
+ button, and no syntax-highlighter palette.
537
+
538
+ ```tsx
539
+ import { CodeBlock } from "@cueplusplus/ui/instruments";
540
+
541
+ <CodeBlock
542
+ label="JSX"
543
+ language="jsx"
544
+ code={'<Button variant="primary">Take cue</Button>'}
545
+ />;
546
+ ```
547
+
548
+ - `code` is printed verbatim, and the copy button copies exactly that. `label` is the caption and
549
+ defaults to the language. `language` defaults to `"tsx"` and lands on the `<pre>` as
550
+ `data-language`.
551
+ - `language="jsx"` adds emphasis in three inks the theme already has: tag names in `--cue-fg`,
552
+ attribute names in `--cue-fg-muted`, punctuation in `--cue-fg-subtle`. It reads string literals
553
+ whole, so a brace or a `<` inside a quoted string does not break it, and a `{…}` expression child
554
+ stays plain. Every other language prints plain.
555
+ - No directive: the copy button is its only client code, so a server page can render the rest.
556
+
557
+ **Changes to existing components:**
558
+
559
+ - **`Bento.Tile`**: a long `eyebrow` now truncates on one line instead of running under the tile's
560
+ `meta` and `action` in a narrow track. This is the only runtime change to an existing component.
561
+ - **`Input`** (and with it `PasswordInput` and `SearchInput`) and **`Textarea`** now declare
562
+ `aria-label` and `placeholder` in their props, and **`Toggle`** declares `aria-label`. This is
563
+ documentation only: all of them were already accepted as inherited HTML attributes and forwarded
564
+ to the element, so nothing you pass behaves differently. They now have rows in the props table
565
+ and in the manifest.
566
+
567
+ **Manifest.** `manifest/manifest.json` indexes the three new components, and
568
+ `manifest/components/{preview,inspector,code-block}.json` are new. The records for `input`,
569
+ `password-input`, `search-input`, `textarea` and `toggle` were regenerated with their new rows.
570
+ `manifest/fixtures.json` gains `children.label` ("A short label"), a one-word text fixture for a
571
+ playground or propSet to pick by hand; no prop's `control.fixture` names it.
572
+
573
+ ### Patch Changes
574
+
575
+ - @cueplusplus/tokens@0.11.0
576
+
577
+ ## 0.10.0
578
+
579
+ ### Minor Changes
580
+
581
+ - ddce99e: `@cueplusplus/ui/layout` — and the root barrel with it — now exports `Bento`, a grid of unequal
582
+ tiles whose span is a prop on the tile rather than a class you count out on the container.
583
+
584
+ **Nothing to do: it is a new export, and nothing that already compiled changed.** Adopt it where
585
+ you were writing a bare grid and hand-counting `col-span-*`:
586
+
587
+ ```tsx
588
+ import { Bento } from "@cueplusplus/ui/layout";
589
+
590
+ <Bento.Root cols={3} rows={2} gap={5}>
591
+ <Bento.Tile span={2} rowSpan={2} minHeight="hero" title="Kitchen sink">
592
+ Every component, on one page.
593
+ </Bento.Tile>
594
+ <Bento.Tile href="/docs/components" title="Components">
595
+ Props, variants and a live example.
596
+ </Bento.Tile>
597
+ </Bento.Root>;
598
+ ```
599
+
600
+ `Bento.Root` takes `cols` (2, 3 or 4; defaults to 3), `rows` (1–4), `dense`, and a `gap` off the
601
+ density space ladder (defaults to `4`). `Bento.Tile` takes a `span` (1–4) and a `rowSpan` (1–2)
602
+ from closed sets, plus `minHeight`, `eyebrow`, `title` and `meta`. The container clamps every span
603
+ to its own tracks on the way past — the columns always, and the rows wherever it declared any. The
604
+ column clamp is per breakpoint, because every bento is two tracks wide between 640 px and 1024 px
605
+ whatever it was asked for, and CSS Grid answers an over-wide span by growing an implicit track
606
+ rather than by refusing it.
607
+
608
+ Two things a props table cannot show you, both of them compile-time where a compiler can reach:
609
+
610
+ - **A tile is one thing or the other, in the type.** Either a **link** — `href`, optionally with
611
+ `render`, a router's own element which is cloned, keeps its own `href` because `next/link`
612
+ requires it to declare one, and has its `className` merged with the tile's rather than replaced
613
+ — whose whole surface is a single `<a>` and whose content is therefore static. Or a **panel**,
614
+ whose children may be live and whose link or control is a visible sibling in `action`. `href`
615
+ and `action` are mutually exclusive in the type, and a dev-time `console.error` names any tile
616
+ that puts an interactive child inside an anchor.
617
+ - **Static markup throughout** — no hooks and no `"use client"` — so a server component can draw a
618
+ bento without opening a client boundary. That is why the router escape hatch is an element to
619
+ clone rather than a render function: a directive is per module, and the hook that reads a render
620
+ function is a hook.
621
+
622
+ `manifest/manifest.json` and the new `manifest/components/bento.json` were regenerated with it.
623
+
624
+ - 16d2cae: `@cueplusplus/ui/system` now exports `isSafeTokenValue(value: string): boolean` — whether a string
625
+ may be written into the density stylesheet `ThemeProvider` builds from its `overrides` prop.
626
+
627
+ **Nothing to do.** Nothing about that stylesheet changed: this is the rule `overridesCss()` has
628
+ always applied, exported. Adopt it if you assemble a `TokenOverrides` out of anything a person or
629
+ an API supplies, so a value is refused at your own input rather than discovered from a
630
+ `console.warn` one render later, with half of what you built never having reached the sheet:
631
+
632
+ ```ts
633
+ import {
634
+ isSafeTokenValue,
635
+ type TokenOverrides,
636
+ } from "@cueplusplus/ui/system";
637
+
638
+ isSafeTokenValue("Iosevka, monospace"); // → true
639
+ isSafeTokenValue("red;}"); // → false
640
+ ```
641
+
642
+ It lands against `./system`'s standing policy — that the prop's _shape_ is public while
643
+ `inlineOverrides()`, `overridesCss()` and `OverridesContext` stay internal, because a consumer
644
+ reaching for those would be writing the custom properties by hand — and it lands on the right side
645
+ of it: a _writer_ of custom properties is what the policy excludes, and a _reader_ that refuses a
646
+ value before it is ever written is the opposite of one. `@cueplusplus/ui/configurator`'s
647
+ `isSafeCssValue` is unchanged and unmoved; it is the same rule behind a subpath that also carries
648
+ `ThemeConfigurator`, `ExportDialog` and an optional `react-aria-components` peer, and a caller
649
+ that only wants the question answered should not have to take that graph to ask it.
650
+
651
+ - 88cf751: The component manifest's `schemaVersion` is now `2`, and every `PropDoc` in it — root props and
652
+ every compound part's props alike — carries a required `control`: `{ kind, members?, min?, max?,
653
+ nullable?, fixture? }`, where `kind` is one of `boolean`, `enum`, `number`, `string`, `node`,
654
+ `callback`, `array`, `object` or `opaque`. It answers the question `type` never could: _what edits
655
+ this prop_.
656
+
657
+ **What a reader of `manifest.json` must change: the version pin, and nothing else.** A consumer
658
+ that asserts `schemaVersion === 1` will reject every document this release publishes and has to
659
+ move to `2`; a consumer that ignores the field keeps working once the pin moves. Both published
660
+ manifests (`manifest/manifest.json` and every document under `manifest/components/`) were
661
+ regenerated, as was `manifest/tokens.json`, whose `schemaVersion` moves with them — so all three
662
+ have to be read at the same version.
663
+
664
+ A `variant` prop's `members` are its `cva` recipe's, in the recipe's order; a numeric literal union
665
+ carries its own `min`/`max`, and a prop whose JSDoc states a range with `@min`/`@max` carries that;
666
+ a union with an explicit `| null` is `nullable`.
667
+
668
+ ## Migrating
669
+ - **`type` is unchanged.** It still prints the TypeScript type verbatim, literal unions included,
670
+ and it is still the field to render when a reader wants to know what a prop _is_. `control` is a
671
+ new field beside it, not a replacement for it: nothing was renamed and nothing was removed.
672
+ - **A consumer pinned to `schemaVersion === 1` must move to `2`.** The number changed because
673
+ `control` is _required_: a consumer that reads it needs to know whether the document it fetched
674
+ has one, and an optional field could never tell it — absent would mean both "no control" and
675
+ "an older manifest". A consumer that ignores the field keeps working after changing the pin.
676
+
677
+ ### Patch Changes
678
+
679
+ - c469f49: `Button` now **declares** `children` in `ButtonProps`, so the published manifest documents it and
680
+ the props table has a row for it.
681
+
682
+ **Nothing to do.** The component did not change — it has always taken children, through
683
+ `React.ComponentPropsWithoutRef<"button">`. What changed is what the package says about itself:
684
+ docgen documents a prop only where the library declares it, so the system's most used control
685
+ published no `children` at all. The page did not list it, and a propSet could not name it, which
686
+ is how `/play/button` came to preview an unlabelled `<button>` behind a standing accessibility
687
+ exemption. Declaring it is what lets that exemption expire.
688
+
689
+ If you read `manifest/components/button.json` to generate code or to drive a playground,
690
+ `children` is now in `props` and can be set like any other prop.
691
+
692
+ - 481e8c5: `manifest/components/ledger.json` said `Ledger` could not fold. It can, and the note now says so.
693
+
694
+ **Nothing to do unless you read the manifest.** No API changed, no component moved, and nothing
695
+ you have written needs editing. The documentation that ships inside the package described the
696
+ plain bench as having "no disclosure anywhere — the ledger does not collapse", which was true
697
+ before 0.8.0 and false in it: `collapsible` puts a real
698
+ `button[aria-expanded aria-controls]` inside the `h2`/`h3` and folds the run of rows below it.
699
+
700
+ The corrected note says what shipped — folding is opt-in per heading, and a ledger nobody asks to
701
+ fold still emits no control and no expansion state at all. The manifest is the only published
702
+ surface this touches.
703
+
704
+ - 64b4bab: Every package manifest's `repository` field now names `cueplusplus/cue-ui`, following the
705
+ repository's rename from an earlier working title.
706
+
707
+ **Nothing to do.** No code, no exports, no types, and no package name: the scope is the
708
+ organisation, not the repository, so every specifier you already import is the one you keep
709
+ importing. What moves is metadata — GitHub Packages matches a package to a repository through that
710
+ field, so from this release the "source" link on each package's registry page, and the repository
711
+ the next publish is attributed to, name `cue-ui`. The `directory` beside it is unchanged, so each
712
+ link still lands on that package rather than on the monorepo root.
713
+
714
+ If you have a bookmark or a script pointing at the old URL, GitHub's rename redirect answers it;
715
+ updating it to `https://github.com/cueplusplus/cue-ui` is tidiness rather than repair.
716
+
717
+ - 3471a15: `tokensUsed` in the published component manifest is now scanned from code rather than from bytes,
718
+ so it stops attributing tokens to components that never resolve them.
719
+
720
+ **What to do: re-read the manifest before you trust a `tokensUsed` list you cached.** No API
721
+ changed and nothing you render moves, but the manifest says something different about 48 of 321
722
+ components, and the old answer was padded — in thirteen cases wholly wrong. If you read
723
+ `tokensUsed` to decide which tokens to define when restyling a component, the list you had was
724
+ too wide. It is narrower now, and true.
725
+
726
+ The scanner matched `--cue-*` anywhere in a file's text, comments included. A docblock sentence
727
+ naming a token published it as one the component uses, and a sentence arguing _against_ one
728
+ published the opposite of what it said: `forms/_chassis.ts` explains that the ring reads
729
+ `--cue-focus`, **not** `--cue-accent`, and eleven controls on that chassis published
730
+ `--cue-accent` because of it.
731
+
732
+ What moved, exactly:
733
+
734
+ - **Thirty-four components lose one to four tokens they never painted.**
735
+ - **Thirteen now correctly publish none** — `agent-surface`, `animated-number`,
736
+ `chart-container` and the ten container icons — because their lists were comment-derived in
737
+ their entirety: they paint through classes rather than resolving custom properties by name, and
738
+ the ten icons render no `className` at all. An empty array renders as no Tokens section rather
739
+ than an empty one, which is what the four components that already shipped one do.
740
+ - **One gains four.** `MarkdownText` painted `--cue-accent`, `--cue-fg`, `--cue-fg-muted` and
741
+ `--cue-fg-subtle` and published none of them: the literal pass pairs quotes across the whole
742
+ file, and the apostrophes in that file's prose desynced the pairing so the class strings below
743
+ were read as the insides of strings. Blanking the comments re-pairs them.
744
+ - **Two entries that were never tokens are gone**, `--cue-icon-` and `--cue-space-`: the regex had
745
+ been stopping at the `*` in `--cue-icon-*` and the `$` in `gap-(--cue-space-${n})`.
746
+
747
+ - Updated dependencies [64b4bab]
748
+ - Updated dependencies [362cdda]
749
+ - Updated dependencies [c2238cc]
750
+ - @cueplusplus/tokens@0.10.0
751
+ - @cueplusplus/theme-base@1.0.1
752
+
753
+ ## 0.9.0
754
+
755
+ ### Minor Changes
756
+
757
+ - 5eb0da2: The ten colour presets leave `@cueplusplus/tokens` and become packages of their
758
+ own. `@cueplusplus/ui/styles.css` now ships the blank base instead of a palette,
759
+ so a theme is something you install, import and register — the same way a
760
+ third-party theme always should have been, and now is.
761
+
762
+ `@cueplusplus/tokens` keeps the two axes it actually owns: the five density
763
+ rungs and the eight font pairings, plus the contracts, `base.json` and the
764
+ primitives a theme source aliases. It emits `axes.css` and no colour at all.
765
+ Each preset is `@cueplusplus/theme-<name>` — `cue`, `dusk`, `hivehub`, `luma`,
766
+ `quotamate`, `requestport`, `signal`, `snuffle`, `terminal`, `venu` — carrying
767
+ the stylesheet it always carried, byte for byte: nothing about any shipped
768
+ palette moved, and a frozen snapshot of what `tokens@0.8.0` emitted is committed
769
+ so that stays checkable rather than merely asserted.
770
+
771
+ ## Migrating
772
+
773
+ **The unattributed default stops being `cue`.** An app that imports
774
+ `@cueplusplus/ui/styles.css` and registers nothing now paints the blank base.
775
+ Nothing errors and nothing falls back: a `data-theme` value is styled by
776
+ whichever stylesheet declares it, and with no theme package imported, none does.
777
+
778
+ Ten custom properties move in dark and seven in light. Measured on a root
779
+ stamped `data-theme="cue"`, which is what every `ThemeProvider` stamped before
780
+ this release and still stamps, so this is the delta for an app that changes
781
+ nothing:
782
+
783
+ | token | dark: `cue` → blank base | light: `cue` → blank base |
784
+ | ---------------------------------------------------- | ----------------------------------------------------------------------- | ------------------------------ |
785
+ | `--cue-accent` | `#ffffff` → `#8a93a6` | `#0a0a0a` → `#3f4759` |
786
+ | `--cue-accent-hover` | `rgba(255,255,255,0.85)` → `#9aa3b6` | `rgba(0,0,0,0.85)` → `#333a4a` |
787
+ | `--cue-accent-soft` | follows `--cue-accent`: it is a 14% `color-mix` of it | same |
788
+ | `--cue-focus` | `#ffffff` → `#8a93a6` | `#0a0a0a` → `#3f4759` |
789
+ | `--cue-font-theme-mono` | JetBrains Mono → nothing; the base declares no theme mono | same as dark |
790
+ | `--cue-font-mono` | resolves through the line above, so JetBrains Mono → the platform stack | same as dark |
791
+ | `--cue-fg` | `#f5f5f7` → `#f5f5f5` | unchanged |
792
+ | `--cue-hair`, `--cue-hair-strong`, `--cue-row-hover` | follow `--cue-fg`: each is a `color-mix` of it (6%, 12%, 3.5%) | unchanged |
793
+ | `--cue-accent-fg` | unchanged | `#fcfcfc` → `#ffffff` |
794
+
795
+ The last four rows are the ones an eye catches and a list of "the accent and the
796
+ focus ring" would not have mentioned: four of the ten dark changes and the one
797
+ light change are tokens no theme file names directly, because they are mixes of
798
+ two that do.
799
+
800
+ To get `cue` back, exactly as it was:
801
+
802
+ ```bash
803
+ pnpm add @cueplusplus/theme-cue
804
+ ```
805
+
806
+ ```css
807
+ @import "@cueplusplus/ui/styles.css";
808
+ @import "@cueplusplus/theme-cue/theme.css";
809
+ ```
810
+
811
+ ```tsx
812
+ import cue from "@cueplusplus/theme-cue";
813
+
814
+ <ThemeProvider themes={[cue]} theme="cue">
815
+ {children}
816
+ </ThemeProvider>;
817
+ ```
818
+
819
+ The import order is load-bearing: a `[data-theme]` block and the base's bare
820
+ `:root` tie at (0,1,0), so the one declared later wins. Importing the theme
821
+ before `styles.css` paints the blank base over it. Importing the package from
822
+ its root, rather than the `manifest.json` subpath, also closes `ThemeName` to
823
+ the themes you installed, so a misspelt `theme=` is a type error.
824
+
825
+ Then the rest:
826
+
827
+ - **`@cueplusplus/tokens/theme.css` is a deprecated alias** of `axes.css` for
828
+ one minor. It still resolves and still delivers the geometry and type axes, so
829
+ a stylesheet naming it keeps building — it simply carries no palette. Change
830
+ the line to `@cueplusplus/tokens/axes.css`, or drop it: `ui/styles.css` already
831
+ brings the axes in through `@cueplusplus/theme-base/base.css`.
832
+ - **Gone from `@cueplusplus/tokens`:** the `themes/*.css` and
833
+ `registry/theme-*.json` subpaths, and the `THEMES`, `ThemeName`,
834
+ `THEME_SUPPORTS_LIGHT` and `DEFAULT_THEME` exports. `ThemeName` now comes from
835
+ `@cueplusplus/theme-base` (re-exported by `@cueplusplus/ui`) and widens with
836
+ each theme package installed, rather than naming the ten that happened to be
837
+ compiled in; whether a theme ships a light block is `supportsLight` on its own
838
+ `manifest.json`; and there is no default palette to name, because the library
839
+ contains none.
840
+ - **`@cueplusplus/tokens`' root entry is now `dist/tokens.js`**, with
841
+ `dist/tokens.d.ts` beside it, where it used to be the TypeScript source
842
+ `dist/tokens.ts`. The import specifier, the exported names and their values
843
+ are unchanged — `import { DENSITIES } from "@cueplusplus/tokens"` is the same
844
+ line it was — but the file behind it is one Node can execute. It could not
845
+ before: Node refuses to strip types for anything under `node_modules`, so the
846
+ package worked in a workspace and failed from a registry install, taking
847
+ `@cueplusplus/theme-base` and the `cue-theme` CLI with it. If you added
848
+ `@cueplusplus/tokens` to your bundler's transpile list to work around that
849
+ (Next.js' `transpilePackages`, or the equivalent), take it out.
850
+ - **`@cueplusplus/tokens` gains `base.json`'s `fonts.handshake`**: the three
851
+ literals of the monospace resolution (`font-pairing-mono`, `font-theme-mono`,
852
+ and the `var()` that resolves them), published as data so a theme built
853
+ anywhere spells them the same way this build does.
854
+ - **`@cueplusplus/ui`'s `THEME_NAME_PATTERN` and `RESOLVED_MONO` are now
855
+ re-exports** of `@cueplusplus/theme-base`'s. Same names, same values, one
856
+ definition instead of three.
857
+ - **`/r/tokens.json`'s `themes` are objects**, not strings:
858
+ `{ name, package, supportsLight, densities }`. A consumer reading the registry
859
+ for a theme list wants to know which package delivers it.
860
+ - **Every published tarball now carries a `README.md`.** On a registry the
861
+ package page _is_ the README, and `tokens`, `ui` and `brand-tokens` shipped
862
+ without one; `packages/release/test/publishable-packages.test.mjs` holds all
863
+ fifteen to it now, beside the same check for `CHANGELOG.md`.
864
+ - **`@cueplusplus/ui` depends on `@cueplusplus/theme-base@^1`**, a range rather
865
+ than a pin, deliberately: two copies of `theme-base` in one tree would break
866
+ the type registry, because a theme's `declare module` augments one copy while
867
+ `ui`'s re-exported `ThemeName` resolves against the other, and neither errors.
868
+
869
+ - 1b98830: `ThemeProvider` takes `themes`, an ordered array of theme manifests, and the runtime reads
870
+ everything about a theme from its manifest instead of from a copy inside this package. Types open:
871
+ `ThemeName`, `DensityLevel` (`Density` on `@cueplusplus/theme-base`) and `FontName` are now `@cueplusplus/theme-base`'s, which a theme package
872
+ widens by declaration merging — `"foo"` is a valid theme in an app that installed
873
+ `@cueplusplus/theme-foo`, and `ThemeName` is `string` in one that installed none. Three hooks —
874
+ `useThemes()`, `useDensities()`, `useFonts()` — return what the active theme offers. Every place
875
+ that validated a persisted name against a frozen list validates against the registry. A typed
876
+ `overrides` prop layers colours, fonts and densities over the active theme, with a development
877
+ contrast report.
878
+
879
+ ## Migrating
880
+ - **`themes` is optional for this release.** An app that passes none gets the blank base and the
881
+ base axes, with one development warning. Pass `themes={[cue]}` from `@cueplusplus/theme-cue`
882
+ when it publishes. With nothing registered there is no theme vocabulary to be out of, so
883
+ `ThemeProvider` and `prepaintScript` no longer reject a persisted or default theme name they do
884
+ not recognise: a well-formed name is stamped as-is. Pass `themes` to close the vocabulary again.
885
+ - **`createTheme({ base })` takes a manifest, not a name.** `createTheme({ name, base: "terminal" })`
886
+ becomes `createTheme({ name, base: terminal })` with `import terminal from "@cueplusplus/theme-terminal"`.
887
+ With no `base` it starts from the blank base. `DEFAULT_THEME_BASE` is removed.
888
+ - **`createTheme`'s radius rules now emit both selector forms** — `[data-theme="x"] [data-density="y"]`
889
+ beside `[data-theme="x"][data-density="y"]` — so a `<Density>` island and `useControlHeight`'s probe
890
+ get the crossed value too. A byte-comparison against the old output will differ on those lines.
891
+ - **`ThemeScope` from `@cueplusplus/ui/configurator` gains `manifest: ThemeManifest | null`.** Every
892
+ function that takes a scope needs it — `baseValue`, `resolveValue`, `resolvedTokens`, `setOverride`,
893
+ `clearOverride`, `isOverridden`, `scopeKey`, `exportName`, `buildExports({ scope })` and
894
+ `<ExportDialog scope>`. Pass the active manifest (`useTheme().manifest`) or `null` for the blank
895
+ base. The panel's `create-theme` export now emits an import of the base theme's manifest instead
896
+ of `base: "<name>"`.
897
+ - **`overrides` is typed `TokenOverrides`**, exported from `@cueplusplus/ui` and `@cueplusplus/ui/system`.
898
+ It is unrelated to `ThemeOverrides` on `@cueplusplus/ui/configurator`, which is still the
899
+ configurator's edit snapshot.
900
+ - `THEME_COLOR_TOKENS` is now exactly `COLOR_CONTRACT` from `@cueplusplus/tokens`: the same 26 names
901
+ in the same order.
902
+ - **`ThemeProvider` opens on the active theme's `densities.default` and `fontPairings.default`** when
903
+ you pass no `density` or `font` — the rung and pairing a theme declares it prefers. Pass one to
904
+ override it for every theme. No first-party theme declares either, so nothing shipped moves.
905
+ `prepaintScript` opens on the same tier: `defaults.density` and `defaults.font` are both optional
906
+ in the options form, and an absent one falls to that theme's declared preference rather than to the
907
+ library's base rung, so the blocking script stamps exactly what the provider is about to commit
908
+ with no work on your side. Passing `defaults.density` or `defaults.font` is the same statement as
909
+ passing the prop — do both, or neither. One consequence for the positional form: its fourth
910
+ argument is now validated like the other three, so `prepaintScript(key, theme, density, font)` with
911
+ a pairing outside the base eight throws the same `defaults.font … is not offered by theme …` the
912
+ options form gives, where before the script silently fell back to `system`.
913
+ - **Three types 16 public signatures name are now exported** from `@cueplusplus/ui` and
914
+ `@cueplusplus/ui/system`: `ThemeManifest`, `DensityEntry` and `FontEntry`. They appear in
915
+ `ThemeProviderProps.themes`, `PrepaintOptions.themes`, `ThemeContextValue.manifest`,
916
+ `ThemeRegistry`, `useThemes()`, `useDensities()`, `useFonts()` and `ThemeScope.manifest`, so
917
+ `@cueplusplus/ui` re-exports them: an app that names one of those types in a signature of its own
918
+ need not reach past the package it installed.
919
+ - **`ThemeRegistry` is exported** from `@cueplusplus/ui/system` — the shape the three registry hooks
920
+ read: the registered manifests, the first one's name, and the rungs and pairings the _active_ theme
921
+ offers.
922
+ - **`RESOLVED_MONO` is exported** from `@cueplusplus/ui/theming`: the `var()` chain every theme block
923
+ sets `--cue-font-mono` to, pairing first. A tool that writes theme CSS beside `serializeThemeCss`
924
+ needs the same string.
925
+ - **`themeSelector`'s options gain `pair?: boolean`**, which emits the descendant form
926
+ `[data-theme="x"] [data-density="y"]` beside the compound one, as more than one selector when
927
+ `density` is set. Neither a `<Density>` island nor `useControlHeight`'s `document.body` probe
928
+ carries `data-theme` beside its `data-density`, so this is what reaches them. This function, and
929
+ `@cueplusplus/theme-tools`' `cue-theme build`, now emit three forms per rung instead of two — an
930
+ exact match, a structural fallback, and the unchanged compound — so that a rung a theme adds or
931
+ retunes no longer applies inside a nested provider of another theme: not through a `<Density>`
932
+ island, not through the nested provider's own root opening on a rung the outer theme happens to
933
+ retune, and not even when that outer theme is _re-entered_ after a detour through a different one
934
+ several levels in, which an ancestor-only structural check cannot always tell apart from the
935
+ outer detour alone. **`<Density>` and the internal measuring probe now also render
936
+ `data-cue-theme`**, stamped from the nearest `<ThemeProvider>` (or omitted where there is none) —
937
+ an inert attribute nothing in this library reads back except the exact-match selector above, which
938
+ needs it to resolve "nearest theme" the way `useContext` already does, rather than the "does some
939
+ ancestor carry this theme" a pure CSS selector is limited to. All three forms are wrapped in
940
+ `:where()`, which contributes no specificity, so together they tie with the `overrides` prop's own
941
+ document-scoped density rule at (0,2,0) — as they always did — rather than outranking it.
942
+
943
+ - e587341: `contrastReport()` now measures the focus ring.
944
+
945
+ `CONTRAST_REQUIREMENTS` gains a second tier: nine **advisory** pairs holding
946
+ `--cue-focus` and `--cue-danger` — the two roles a form control paints as a
947
+ ring, and therefore WCAG 1.4.11 non-text contrast surfaces — to 3:1 against
948
+ every ground one can be painted over. `ContrastRequirement` carries
949
+ `advisory?: boolean`, `ContrastReport` carries `advisories` beside `failures`,
950
+ and `passes` still counts only the eleven required pairs. No theme that was
951
+ clean yesterday fails today; every theme now gets told about its ring.
952
+
953
+ The gap was known and the gate for it lived in a test file, which protected the
954
+ ten shipped presets and nobody who calls `createTheme()`. The dark block takes
955
+ the accent into `--cue-focus` verbatim, so
956
+ `createTheme({ name: "acme", base: snuffle, accent: "#3b3f8f" })` — the manifest
957
+ imported from `@cueplusplus/theme-snuffle`, and a brand colour straight into the
958
+ anchor, which is the documented use — came back
959
+ `passes: true` with twenty-two checks, none of which named `focus`, over a ring
960
+ measuring 2.01:1 down to 1.55:1 on the five grounds it can sit on. The same call
961
+ now returns those five pairs in `report.advisories`, each with its reason. It is
962
+ the failure 0.6.0 fixed in `luma`, `venu` and `hivehub` at 2.45:1, and a report
963
+ is the only place a consumer would ever meet it: an invisible focus ring is
964
+ invisible in code review and invisible in a screenshot taken with a mouse.
965
+
966
+ All ten shipped presets clear the new tier in both modes — two hundred ring
967
+ measurements, the tightest `terminal.dark --cue-danger` on `--cue-surface-3` at
968
+ 3.13:1 — and the preset gate now runs through `contrastReport()` rather than
969
+ through its own copy of the same loop.
970
+
971
+ `danger/bg` is not restated in the ring tier: the status pairs have always held
972
+ the red against the page at this same floor, as a required pair, and one
973
+ measurement gets one id.
974
+
975
+ **If you iterate `CONTRAST_REQUIREMENTS` or render `report.checks`,** the array
976
+ goes from 11 entries to 20 and a two-block report from 22 checks to 40. Filter
977
+ on `advisory` to keep the old set.
978
+
979
+ ### Patch Changes
980
+
981
+ - cf80a87: `FlowGraph` draws an edge faint until both of the nodes it joins have been
982
+ reached, which is what its `edges` prop has always said it does.
983
+
984
+ The behaviour was documented, published and unimplemented: the prop's
985
+ description — "An edge dims until both of its ends are visible" — ships in
986
+ `manifest/components/flow-graph.json` and in the type declarations, the
987
+ `transition-opacity duration-500` was already on the path, and the component
988
+ computed whether an edge was live and then never used the answer. Every edge
989
+ painted at full strength from the first frame, so a graph revealing itself
990
+ through `visibleCount` showed all of its connections before it had any of its
991
+ nodes.
992
+
993
+ Found by turning the lint gate on: the unused binding was the first thing it
994
+ reported.
995
+
996
+ - 2737aec: `@cueplusplus/tokens` exports the contracts and the axes as data, so a theme
997
+ built outside this repository can resolve over the same base: `COLOR_CONTRACT`,
998
+ `GEOMETRY_CONTRACT` and `FONT_TOKENS` as typed tuples, `base.json` (every
999
+ density's geometry, the pairings' stacks, the derived templates, the defaults),
1000
+ `primitives.tokens.json` (the tier-1 file a theme source aliases), and
1001
+ `axes.css` — `theme.css` with the theme axis removed. Nothing that exists today
1002
+ moves; `theme.css` is unchanged.
1003
+
1004
+ `@cueplusplus/ui/theming` keeps every export it had. The contrast arithmetic now
1005
+ lives in `@cueplusplus/theme-base` and is re-exported here; `ui` gains that
1006
+ package as a dependency. No behaviour changes.
1007
+
1008
+ - Updated dependencies [cf80a87]
1009
+ - Updated dependencies [5eb0da2]
1010
+ - Updated dependencies [814c859]
1011
+ - Updated dependencies [2737aec]
1012
+ - @cueplusplus/tokens@0.9.0
1013
+ - @cueplusplus/theme-base@1.0.0
1014
+
1015
+ ## 0.8.0
1016
+
1017
+ ### Minor Changes
1018
+
1019
+ - 4c7b963: `Ledger.Tier` and `Ledger.Group` take `collapsible`: the heading stays the `h2`/`h3` element it always was and gains a real `button[aria-expanded aria-controls]` inside it, which folds the run of rows below away. The heading outline is untouched, so a page that offers the same dataset folded and unfolded does not move a screen-reader user's heading list. Expansion is controlled or uncontrolled — `expanded`, `defaultExpanded`, `onExpandedChange` — and the library stores nothing: which places a reader folded is the caller's to keep. A folded run of rows is hidden rather than unmounted, so a group holding its own fold inside a tier still holds it after the tier above has been shut and opened again. A ledger nobody asks to fold renders exactly as before, with no client boundary.
1020
+
1021
+ ### Patch Changes
1022
+
1023
+ - @cueplusplus/tokens@0.8.0
1024
+
1025
+ ## 0.7.0
1026
+
1027
+ ### Minor Changes
1028
+
1029
+ - 1217d75: `Table.GroupRow` takes `headingLevel` (`2`–`6`), which makes the divider's label
1030
+ slot a real heading element instead of a `<span>` — so a grouped table joins the
1031
+ page's heading outline without nesting an `<h2>` inside a `<span>`, which is what
1032
+ passing a heading as `label` had to do. It defaults to no heading, so every
1033
+ existing divider is unchanged.
1034
+
1035
+ The row-activation reading is now public: `activateRowFromClick` and
1036
+ `activateRowFromKeyDown` are exported from `@cueplusplus/ui`, so a `Table.Row` —
1037
+ which owns its own activation — or any other element a console makes into a row
1038
+ runs the same decision `DataRow.Root` runs, instead of a second copy of it.
1039
+
1040
+ ### Patch Changes
1041
+
1042
+ - edb938d: `Table.Row interactive` draws a focus ring. The prop supplied the
1043
+ pointer half — the row-hover wash and a pointer cursor — and nothing for
1044
+ focus, while the row-activation reading above tells a caller to put `tabIndex`
1045
+ on the `<tr>` so `activateRowFromKeyDown` has something to fire on. The two
1046
+ together made a row a keyboard user could reach and activate with nothing on
1047
+ screen changing. **This is a WCAG 2.4.7 (Focus Visible, AA) fix you inherit by
1048
+ upgrading.**
1049
+
1050
+ The ring is the one `Row` already draws: 2px solid in `--cue-accent`, pulled
1051
+ inside the row by `-outline-offset-2` rather than floating outside it, because
1052
+ these tables live in clipped panes and scroll regions where an outset ring on
1053
+ the first or last row is cut off by the pane's own rim. It is `focus-visible`,
1054
+ so a pointer press still paints nothing, and it rides on `interactive` alone:
1055
+ a static table of figures is untouched. Nothing to change — and if you drew a
1056
+ ring of your own on a focusable row, the library's is now under it.
1057
+
1058
+ Added after the fact: this shipped in 0.7.0 with no changeset, and a late entry
1059
+ beats a hole in the record.
1060
+
1061
+ - @cueplusplus/tokens@0.7.0
1062
+
1063
+ ## 0.6.0
1064
+
1065
+ ### Minor Changes
1066
+
1067
+ - bf116b5: `AppShell.Bar` gains the desktop drag contract `TitleBar` already had, plus a truly bar-centred `center` slot and a trailing `actions` cluster, so a Tauri or Electron console spends one chrome row on window furniture instead of two.
1068
+
1069
+ `StatusBar.Item` gains `interactive`, `prominence` and `render`, so a reading can become a real button or link with hover, press and inset keyboard-focus states, and the new `StatusBar.Group` fuses adjacent items into one compact control. The published manifest now lists `StatusBar.Item` and `StatusBar.Group` with their props, so a props table or lookup tool reads them the way it already reads `AppShell`'s parts.
1070
+
1071
+ ## Migrating
1072
+
1073
+ Nothing to change. Both additions are opt-in: without `platformDrag`, `center` or `actions` the bar emits no drag attributes at all, and a `StatusBar.Item` with none of the new props is the same inert `<div>` with the same classes it always was.
1074
+
1075
+ Two shapes moved underneath, in case your own CSS reaches into them. `AppShell.Bar` now wraps its brand, its leading children and its actions in `[data-slot="app-shell-brand-content"]`, `[data-slot="app-shell-bar-leading"]` and `[data-slot="app-shell-bar-actions"]` respectively — each a `<div>` laid out as a flex row with the bar's own gap, so the rhythm is unchanged and `brandClassName` still lands on the cell. Under `platformDrag` those clusters take their content's width rather than filling their cell, which is what leaves the bar's dead space to the window manager; an `ms-auto` that needs to reach the cell's rim belongs on a box inside the cluster there. And `StatusBar`'s root is now `overflow-x-clip`: the bar has always promised to be one row tall, and now it keeps that promise when it is overfilled.
1076
+
1077
+ - fb20c71: Dense data surfaces: `DataRow` is the shared row anatomy (marker, label, description, tags, figure and an action track hidden until hover, focus or selection, with a touch fallback and no reflow when it appears); `Ledger` groups rows under sticky tier headings and gutter slugs with no disclosure semantics; `DataTree` draws the same rows as a hierarchy under `role="tree"` with the APG keyboard map and CSS depth guides; and `Table` gains `Table.GroupRow` (a `<th scope="rowgroup">` divider so a grouped list stays one table with its columns named once), `sticky` on `Table.Head`, and `interactive`/`selected` on `Table.Row`. Existing tables are untouched: every addition is opt-in.
1078
+
1079
+ ### Patch Changes
1080
+
1081
+ - 778374d: The keyboard focus ring is now visible in the luma, venu and hivehub light
1082
+ themes. Each set `--cue-focus` to its brand accent, and on paper that accent
1083
+ read as low as 2.45:1 against the recessed fill a focused field paints it
1084
+ over — under the 3:1 WCAG 1.4.11 asks of a focus indicator. Each light block
1085
+ now carries its own focus colour: the same accent hue, stepped down in
1086
+ lightness until it clears every surface a field can sit on. `--cue-accent` is
1087
+ unchanged, so no button, chip or link moves.
1088
+ - 013960d: Form controls now show one quiet focus signal instead of two. A focused field strengthens its rim by one rung (`--cue-border` → `--cue-border-strong`) and draws a single 2px ring in the theme's own `--cue-focus`, pulled flush against the rim rather than floating outside it on a gap. Grouped controls — `InputGroup`, `SearchInput`, `Combobox`, `MultiSelect`, `Autocomplete`, `TagsInput`, `NumberField`, `ScrubInput`, `Composer`, `MusicalTimeInput` and the segmented date and time fields — strengthen their rim for any focus inside but wait for a keyboard-visible descendant before drawing the ring, so a mouse click no longer lights the whole cluster up. An invalid field stays red while it is focused: because a flush ring is drawn over the rim it hugs, the ring itself turns `--cue-danger` rather than leaving a border nobody can see underneath it — so focus keeps its own indicator and invalid keeps its colour, on the plain controls and on the grouped ones alike.
1089
+
1090
+ ## Migrating
1091
+
1092
+ No API changed, and no consumer edit is required. Anyone who added local overrides to mute the old accent-rim-plus-outer-ring treatment can delete them: the defaults are now the quiet version, and a local override will fight the theme's focus colour rather than help it.
1093
+
1094
+ - Updated dependencies [778374d]
1095
+ - @cueplusplus/tokens@0.6.0
1096
+
1097
+ ## 0.5.0
1098
+
1099
+ ### Minor Changes
1100
+
1101
+ - f71a189: ## The hooks are documented API
1102
+
1103
+ `useAgentThread` had no page. Neither did `useTheme`, `useToast`, `useCueTable`
1104
+ or the eight others — not because they are private, but because the generator
1105
+ that writes this library's documentation only ever looked for components. A
1106
+ symbol you are told to import, whose shape decides how you write the call, was
1107
+ reaching you as a name in a changelog and a type in your editor and nothing
1108
+ else. An agent reading the manifest could not answer "what does `useCueTable`
1109
+ take" at all.
1110
+
1111
+ `manifest.json` now carries a `hooks` array beside `components`, with every
1112
+ `use*` an entry subpath exports:
1113
+
1114
+ ```
1115
+ useAgentThread @cueplusplus/ui/agent-runtime
1116
+ useCarousel @cueplusplus/ui/layout/carousel
1117
+ useControlHeight @cueplusplus/ui
1118
+ useCuePortalProps @cueplusplus/ui
1119
+ useCueTable @cueplusplus/ui/instruments/data-table
1120
+ useDensity @cueplusplus/ui
1121
+ useFlowTheme @cueplusplus/ui/flow
1122
+ useSidebar @cueplusplus/ui
1123
+ useTheme @cueplusplus/ui
1124
+ useToast @cueplusplus/ui
1125
+ useZoom @cueplusplus/ui/configurator
1126
+ ```
1127
+
1128
+ Each entry carries what the source already knew and had nowhere to put: the
1129
+ `signature` TypeScript resolves for it (`(size?: ControlSize): number`, not a
1130
+ prop table with nothing in it), the description and summary, `@param`,
1131
+ `@returns` and `@throws` where the JSDoc has them, every `@example` — all of
1132
+ them, so `useFlowTheme`'s second one survives — plus `group`, `importPath`,
1133
+ `peerDependencies`, `clientOnly` and the page it lives on.
1134
+
1135
+ The array is **inline**. A component's row points at a companion file because
1136
+ its props, parts, variants and tokens do not fit; a hook's whole documentation
1137
+ does, so `manifest.json` answers about a hook with no second fetch. That is why
1138
+ a hook row has `url` and `mdUrl` but no `jsonUrl` — there is no companion to
1139
+ name.
1140
+
1141
+ Every hook is `clientOnly: true`, and every one of them says so on its page. The
1142
+ seven internal look-alikes — `useComposedRefs`, `useDebouncedValue`,
1143
+ `useEdgeScroll` and the rest — are a component's own machinery, no entry exports
1144
+ them, and none of them appears. A new one showing up in this array is a
1145
+ decision, not a diff.
1146
+
1147
+ ## Two hooks that could not have carried a page
1148
+
1149
+ **`useDensity` now shows the read it exists for.** Its description always said
1150
+ this is the JS read for code that cannot ask CSS — virtualised row heights,
1151
+ canvas drawing, layout math — and then showed you none of it. It carries the
1152
+ virtualiser idiom now: the level, the measured row height beside it, and the
1153
+ overscan that has to move with the level because shorter rows mean more of them
1154
+ on screen.
1155
+
1156
+ **`useSidebar` was one sentence long.** It said it reads the collapse state and
1157
+ stopped, which left the two things a caller actually needs undocumented: that it
1158
+ hands back `toggle` as well as `collapsed` — the same valve `Sidebar.Rail`
1159
+ pulls, so a collapse control does not have to live on the rail — and what
1160
+ happens outside a `Sidebar.Root`. It does not throw there. It reports
1161
+ `collapsed: false` and a `toggle` that does nothing, because a row rendered on
1162
+ its own in a test or a docs page is a legitimate thing and the honest answer for
1163
+ it is that nothing is collapsed. Its example swaps a label for an icon and keeps
1164
+ the `aria-label` across both.
1165
+
1166
+ Behaviour is unchanged in both. This is JSDoc.
1167
+
1168
+ ## Finding one
1169
+
1170
+ Hooks share the components namespace on the docs site — `/docs/components/use-agent-thread`,
1171
+ and the markdown twin at `…/use-agent-thread.md` — so a link to a hook looks
1172
+ like a link to anything else, the sidebar lists them under their group, and
1173
+ `llms.txt` and `llms-full.txt` carry them. `useAgentThread`'s page mounts the
1174
+ live scripted thread that already ran under **Agent runtime**, because the bench
1175
+ now names the hook it was built to show.
1176
+
1177
+ The skills references list them per group, and the lookup script answers:
1178
+
1179
+ ```
1180
+ node scripts/lookup.mjs --props useAgentThread # signature, params, returns, example
1181
+ node scripts/lookup.mjs --hooks # all eleven, with their import paths
1182
+ ```
1183
+
1184
+ ## What did not change
1185
+
1186
+ `schemaVersion` is still `1` — this is an addition, and additions never bump it.
1187
+ `components` still means components: every count, badge, table and `--list` that
1188
+ said "155 components" says it still, and none of them quietly grew by eleven. No
1189
+ component's documentation moved, no runtime code changed, and nothing new is
1190
+ required of a consumer who never reads the manifest.
1191
+
1192
+ - 8991fa3: ## Every state watchable
1193
+
1194
+ An agent UI is mostly states nobody can get to. A tool call that is _running_
1195
+ holds that shape for four hundred milliseconds; a run _waiting on a person_
1196
+ holds it until somebody answers; a _cancelled_ turn is a picture you have to
1197
+ break something to see. This library has ninety-five components for those
1198
+ states and, until now, two ways of showing them: a still, or a script that plays
1199
+ once and cannot be stopped, rewound or linked to.
1200
+
1201
+ Now every state a run passes through is watchable — the run has a transport, the
1202
+ transport has a scrubber, and the frame you stopped on has an address. `t=8` in
1203
+ a gallery link opens the run paused on the approval, in your theme, at your
1204
+ density, on the device you picked.
1205
+
1206
+ **`ReplayPlayer`** ships on its own subpath, `@cueplusplus/ui/elements/replay`,
1207
+ with `useReplay` beside it for a console that wants the transport and not the
1208
+ chrome.
1209
+
1210
+ ```tsx
1211
+ import {
1212
+ ReplayPlayer,
1213
+ type ReplayScript,
1214
+ } from "@cueplusplus/ui/elements/replay";
1215
+
1216
+ const SCRIPT: ReplayScript = {
1217
+ title: "Patching universe 3",
1218
+ frames: [
1219
+ {
1220
+ scene: (
1221
+ <ToolCall
1222
+ label="Read"
1223
+ activeLabel="Reading the plot"
1224
+ query="plot.md"
1225
+ request='{"path":"plot.md"}'
1226
+ result=""
1227
+ running
1228
+ open={false}
1229
+ onOpenChange={() => {}}
1230
+ />
1231
+ ),
1232
+ hold: 600,
1233
+ label: "tool-call",
1234
+ },
1235
+ {
1236
+ scene: (
1237
+ <ApprovalCard
1238
+ state="request"
1239
+ command="cue write --cue 1"
1240
+ title="Write the first cue"
1241
+ subtitle="Universe 3, channel 1"
1242
+ />
1243
+ ),
1244
+ hold: 1600,
1245
+ label: "awaiting-approval",
1246
+ },
1247
+ { scene: <MarkdownText>{answer}</MarkdownText>, hold: 0, label: "done" },
1248
+ ],
1249
+ };
1250
+
1251
+ <ReplayPlayer script={SCRIPT} />;
1252
+ <ReplayPlayer script={SCRIPT} startAt={17} autoPlay={false} rate={0.5} />;
1253
+ ```
1254
+
1255
+ ### A controller over a filmstrip, not an animation
1256
+
1257
+ Every frame carries the **whole scene** rather than the part that changed, so
1258
+ the component's entire rendering rule is `frames[index].scene`. That is what
1259
+ makes the position controls honest: frame 12 reached by scrubbing is the same
1260
+ DOM as frame 12 reached by watching, because there is no accumulated state for
1261
+ the two paths to disagree about. A player whose frames accumulated — the way a
1262
+ simulated terminal accumulates printed lines inside its own closure — can be
1263
+ played and it can be restarted, but it can never be scrubbed.
1264
+
1265
+ ### One clock, so pause is exact
1266
+
1267
+ The two older players in this library pace themselves with a chain of
1268
+ `setTimeout`s, which makes pause impossible: the time already spent inside the
1269
+ in-flight timeout is unrecoverable, so a resume can only start the hold again.
1270
+ Here the playhead is a number of _script_ milliseconds and wall time reaches it
1271
+ through one monotonic origin — `base + (now() - origin) * rate` — so pausing is
1272
+ "write down the playhead", resuming is "take a new origin", and the unspent
1273
+ 380 ms of a 400 ms hold is still 380 ms on the other side of the pause. The same
1274
+ identity is what makes the rate multiplier exact rather than approximate, and
1275
+ what makes a seek and a play land on the same frame.
1276
+
1277
+ The transport is play, pause, step either way, scrub, restart and rate, built
1278
+ from shipped cue components — a `Slider` for the scrubber, `IconButton`s, a
1279
+ `Chip` for the state — and painted in cue's own metrics even when the scene
1280
+ inside it is an `AgentSurface` island at upstream fidelity. The chrome belongs
1281
+ to the console showing the run, not to the run.
1282
+
1283
+ ### Reduced motion is a mode, not an ending
1284
+
1285
+ Under `prefers-reduced-motion: reduce` **nothing is ever scheduled** — not
1286
+ scheduled and hidden, not scheduled at speed, not scheduled at all — and the
1287
+ player opens on the resting frame. Every control except play and rate keeps
1288
+ working, so a reader who asked for stillness can still step through the run one
1289
+ frame at a time and read every state it passes through. That is strictly more
1290
+ than "show the end and stop", which is what this library offered before.
1291
+
1292
+ ### Legible from outside
1293
+
1294
+ The root publishes `data-replay-state` (the current frame's authored label),
1295
+ `data-replay-playing` and `data-replay-frame`, the way `TerminalFrame` publishes
1296
+ `data-simulating` — enough for a stylesheet, a screenshot harness or a test to
1297
+ say "show me the awaiting-approval frame" without reaching into React.
1298
+
1299
+ State labels are **authored, not derived**: nothing in this tree emits a
1300
+ run-state event and a scripted thread has no clock, so each frame carries its
1301
+ own word from a set of seven — `queued`, `thinking`, `tool-call`,
1302
+ `awaiting-approval`, `streaming`, `cancelled`, `done`. Two kinds of waiting, two
1303
+ kinds of work, the one state that is waiting on a _human_, and the two endings a
1304
+ transcript really has.
1305
+
1306
+ ### No peer, and no icon set
1307
+
1308
+ `./elements/replay` declares an empty peer row in the dist contract, and that is
1309
+ a claim being checked rather than an omission: the five transport glyphs are
1310
+ drawn in the file at 16px. A player is chrome, and chrome that drags an icon set
1311
+ in behind it is a dependency nobody chose.
1312
+
1313
+ ## One run, written down once
1314
+
1315
+ A player needs something to play, and eight example pages hand-authoring their
1316
+ own transcripts would have produced eight different fictions — eight inventions
1317
+ of what a `grep` returns, eight decisions about what a cancelled call says, and
1318
+ a reader who cannot compare two of them because they are not looking at the same
1319
+ run.
1320
+
1321
+ So the run is data, in the corpus (`@repo/specimens`) rather than in the
1322
+ package: fourteen tool payloads — read, glob, grep, bash, edit, write, patch,
1323
+ plan, todowrite, task, webfetch, websearch, question, mcp — each readable at any
1324
+ of six statuses through one total function, `withStatus(fixture, status)`, whose
1325
+ totality the suite pins at all eighty-four pairs. Beside them, fourteen message
1326
+ samples: turns with mentions and attachments, three reasoning blocks, five
1327
+ answers including the one with a table and the one with sources, and `streamed`
1328
+ for the part arriving a word at a time.
1329
+
1330
+ `CANONICAL_TRANSCRIPT` is what they compose into — the dark-mode-toggle run the
1331
+ Cursor evidence recorded, in twenty-three beats over two turns: read, grep,
1332
+ glob, plan, edit, shell, web search, a markdown answer, then a follow-up whose
1333
+ edit streams in. A transcript is not a script; it is the _content_ of one, with
1334
+ no opinion about how a beat is drawn. `transcriptScript(transcript, scene)` is
1335
+ the join: hand it the way your surface draws a beat and it hands back a
1336
+ `ReplayScript` with the frame count, the state labels and the captions already
1337
+ right.
1338
+
1339
+ ## Eight studies, at fidelity, named
1340
+
1341
+ The gallery gains eight pages, all playing that same twenty-three-beat run, so
1342
+ the only thing that differs between any two of them is the chrome.
1343
+
1344
+ Five are **fidelity studies of named consumer products** — ChatGPT, Claude,
1345
+ Gemini, Grok and Perplexity — drawn to the trade dress the research recorded:
1346
+ ChatGPT's always-visible action bar against Claude's hover-and-focus one,
1347
+ Gemini's gradient-carried surface with no avatars, Grok's near-monochrome fold
1348
+ of consecutive calls into one group, Perplexity's answer-engine shape where the
1349
+ question becomes a heading over a strip of sources. Three are **studies of
1350
+ IDE-style consoles** — Claude Code's timeline rail and number-keyed permission
1351
+ card, Cursor's five-level conversation-density switch, Hermes's composer status
1352
+ stack with approval answered inline.
1353
+
1354
+ These are internal reference and they say so on their own faces: every one of
1355
+ the eight carries a header note naming the product and its maker, stating that
1356
+ the page is a fidelity study drawn from the elements family, that it is **not a
1357
+ cue product surface**, and that it is not affiliated with the maker — beside a
1358
+ chip that says whether the palette was quoted from the record or matched to it.
1359
+ Nothing here is a template to ship a product on. They exist so the family can be
1360
+ judged against the surfaces it will be asked to imitate.
1361
+
1362
+ Two gallery axes make them linkable: `surface` (`cue` or `aui`, the value-set
1363
+ the page's `AgentSurface` island paints in) and `t`, the frame the replay opens
1364
+ on. Both compose with theme, density, mode and device rather than overriding
1365
+ any, and `t` is optional rather than defaulted — a stamped `t=0` pauses the run
1366
+ on its first frame, while no `t` at all lets it play, and defaulting would have
1367
+ turned every copied link into the paused one.
1368
+
1369
+ The corpus and the site are `private: true` and none of this reaches the
1370
+ tarball. It is here because it is the evidence for the minor above: a transport
1371
+ nobody has driven through twenty-three states on eight different chromes is a
1372
+ transport nobody has tested.
1373
+
1374
+ ## What did not change
1375
+
1376
+ The root barrel and `@cueplusplus/ui/elements` gained nothing — no export, no
1377
+ peer, no bytes. The transport is chrome _around_ the elements family rather than
1378
+ a member of it, so the group's import graph stays exactly as the vendor sync
1379
+ emitted it. No component's props, structure or rendering moved; `elements.css`,
1380
+ the island, the tokens and the sync pin are untouched.
1381
+
1382
+ - 7fb3004: ## Model output, rendered
1383
+
1384
+ Every component in this library has been able to say one thing about a string a
1385
+ model wrote: it goes into the DOM as characters. `Message` places it,
1386
+ `ToolCallCard` places it, all ninety-four elements place it, and the `Chat`
1387
+ group's doctrine header said so in a line — **text is text**. That line was
1388
+ doing real work. A component that quietly parsed model output would put every
1389
+ consumer of this library one prompt away from an injection, because the
1390
+ practical shape of prompt injection is not "the model turned evil", it is "the
1391
+ model faithfully relayed bytes an attacker left on a page it fetched".
1392
+
1393
+ Verbatim is still the default, everywhere, unchanged. What this release adds is
1394
+ the one exception, and everything about how it ships is an argument that it
1395
+ should stay one: **`MarkdownText`**, on its own subpath
1396
+ `@cueplusplus/ui/elements/markdown`, with its own two peers, not re-exported
1397
+ from `@cueplusplus/ui/elements` and never going to be.
1398
+
1399
+ ```tsx
1400
+ import { Message } from "@cueplusplus/ui";
1401
+ import { MarkdownText } from "@cueplusplus/ui/elements/markdown";
1402
+
1403
+ <Message agent="researcher">
1404
+ <MarkdownText streaming={turn.status === "running"}>
1405
+ {turn.text}
1406
+ </MarkdownText>
1407
+ </Message>;
1408
+ ```
1409
+
1410
+ `Message` did not change to make that work. It places what it is given, and what
1411
+ it is given is a string until a caller writes the line above — which is the
1412
+ whole design: rendering markup is a line of code somebody wrote, not a behaviour
1413
+ they inherited.
1414
+
1415
+ ### What renders
1416
+
1417
+ Twenty tags, written out in the source rather than inherited from
1418
+ `hast-util-sanitize`'s `defaultSchema`, because a spread schema means the set of
1419
+ tags this library vouches for is whatever a transitive dependency decided this
1420
+ week.
1421
+
1422
+ | | |
1423
+ | ------ | ---------------------------------------------------------------------------------------------------------------------- |
1424
+ | blocks | paragraphs, ATX and setext headings, bullet and ordered lists, block quotes, thematic breaks, fenced and indented code |
1425
+ | inline | emphasis, strong, strikethrough, inline code, links, images, hard breaks |
1426
+
1427
+ Headings start at `h2` and clamp at `h6` — model output is a passage inside a
1428
+ page that already owns its `h1` — and `headingLevel` moves the base. Fenced
1429
+ blocks are `<pre><code>` containing the model's characters and nothing else
1430
+ until `renderCodeBlock` is passed: a highlighter is a second parser over the
1431
+ same untrusted string, usually one that emits HTML, so opting into it is
1432
+ deliberate. Every part carries a `data-slot`, so an app restyles a heading or a
1433
+ quote from the outside without a prop for it.
1434
+
1435
+ ### What is dropped
1436
+
1437
+ **Raw HTML has no path to the DOM, and the first reason is an absence rather
1438
+ than a setting.** `react-markdown` spreads its own `allowDangerousHtml: true`
1439
+ over whatever `remarkRehypeOptions` it is given, so that option cannot be turned
1440
+ off from here and `<img onerror=…>` does become a `raw` node. A `raw` node is a
1441
+ span of source text rather than an element, and the one thing that turns it back
1442
+ into markup is `rehype-raw`. Nothing in this package depends on that plugin, and
1443
+ a test asserts it never will — a `pnpm add rehype-raw` is a failing build, not a
1444
+ quiet demotion of defence-in-depth to defence. `rehype-sanitize` then drops the
1445
+ `raw` node type outright, and `skipHtml` removes whatever survived. Three gates,
1446
+ each of them disabled in turn in the suite to prove they are redundant rather
1447
+ than load-bearing one at a time.
1448
+
1449
+ None of the three is a deny-list of dangerous tags. A `<script>`, an `<iframe>`,
1450
+ an `<svg>`, a `<form>` and an `onclick=` are the same non-event: there is
1451
+ nothing in the tree for them to be.
1452
+
1453
+ **Attributes are enumerated, not filtered.** An element outside the subset
1454
+ renders nothing; an element inside it renders only the attributes named beside
1455
+ it — `href`/`title` on a link, `src`/`alt`/`title` on an image, `start` on an
1456
+ ordered list, a single `language-…` class on a `code`. Everything else gets the
1457
+ empty list. No element in the subset accepts `id`, `style`, `srcset`,
1458
+ `formaction` or any `on*` handler, so none of them has an attribute to arrive
1459
+ on. Comments and doctypes are refused, and DOM clobbering has no attribute to
1460
+ travel on.
1461
+
1462
+ **URLs are allow-listed twice, and to three protocols.** `http`, `https` and
1463
+ `mailto`, on both `href` and `src`. A relative URL stays relative; a
1464
+ scheme-relative `//host` does not. A `javascript:` link, a `data:text/html`
1465
+ image, `vbscript:`, `blob:`, `file:` and the `irc:`/`xmpp:` handlers React's
1466
+ default pipeline permits all lose the attribute and leave an inert element
1467
+ behind — an anchor with no destination, not an anchor to the page you are on.
1468
+
1469
+ **Every link carries `rel="noopener noreferrer nofollow"`, and that is not a
1470
+ prop.** Reverse tabnabbing, a private console URL travelling in a `Referer`
1471
+ header, and SEO laundering through somebody's transcript are not things a caller
1472
+ should be able to turn off by accident. `target` is configurable (`_blank` by
1473
+ default, because a transcript that navigates away loses the run); `rel` is not.
1474
+ Images are `loading="lazy"`, `decoding="async"`, `referrerPolicy="no-referrer"`.
1475
+
1476
+ No GFM plugin is enabled, so there are no tables, task lists, footnotes or
1477
+ autolink literals — an explicit `<https://…>` autolink still works. Task lists
1478
+ are absent on purpose as much as by omission: a checkbox inside a transcript is
1479
+ a control a model drew.
1480
+
1481
+ The threat model above is on the component, in its JSDoc, which means it is on
1482
+ the docs page, in the manifest and in a consumer's hover — not in a wiki that
1483
+ drifts from the code. It is held by 122 adversarial assertions run three ways:
1484
+ settled, streaming, and at every prefix of a hostile stream.
1485
+
1486
+ ### Streaming
1487
+
1488
+ Pass `streaming` while the turn is arriving and the token the model is halfway
1489
+ through typing is hidden rather than rendered as punctuation. Streaming markdown
1490
+ is not malformed markdown — CommonMark has an answer for every truncation, and
1491
+ that answer is the problem: `**bo` is a paragraph containing two literal
1492
+ asterisks, and three frames later it is bold text and the asterisks are gone.
1493
+ The visible result is punctuation flickering through every emphasis, link and
1494
+ code span as it arrives, with a reflow behind it.
1495
+
1496
+ Six rules, covering unclosed fences, odd inline-code runs, half-written links
1497
+ and autolinks, and unmatched emphasis openers — the words appear the moment the
1498
+ model writes them and only their formatting waits for the closing token. None of
1499
+ it is a second parser and none of it can widen what renders: every rule only
1500
+ ever _deletes_ characters before the source reaches the pipeline, so a sealed
1501
+ stream passes the same three gates as a settled document.
1502
+
1503
+ ### The line in `Chat` that changed
1504
+
1505
+ One doctrine bullet, from "**Text is text.** Nothing here parses markdown or
1506
+ HTML" to "**Text is text, unless somebody says otherwise.**" — the same default,
1507
+ now named as a default rather than as the only option, pointing at the renderer
1508
+ for when a consumer wants it. No component in that group changed.
1509
+
1510
+ ## The vocabulary a model composes
1511
+
1512
+ The second half of this release is the other end of the same problem. A model
1513
+ that can only answer in prose answers a flight query in prose. `GenerativeUI`
1514
+ takes a tree of JSON a model emitted — 27 intrinsic names, nested — and draws
1515
+ it.
1516
+
1517
+ ```tsx
1518
+ import { GenerativeUI } from "@cueplusplus/ui/elements/generative";
1519
+ import "@cueplusplus/ui/elements/generative.css";
1520
+
1521
+ // A `GenerativeAction` carries the intrinsic that fired it as `type`, so it is
1522
+ // named rather than spread over a message discriminator of the same name.
1523
+ <GenerativeUI
1524
+ spec={JSON.parse(toolResult)}
1525
+ onAction={({ action, type, value }) =>
1526
+ send({ type: "ui-action", action, intrinsic: type, value })
1527
+ }
1528
+ onFault={(fault) => telemetry.warn("generative", fault)}
1529
+ />;
1530
+ ```
1531
+
1532
+ | | the 27 |
1533
+ | -------------- | --------------------------------------------------------------------- |
1534
+ | layout (7) | `Card` `Row` `Col` `Box` `Spacer` `Divider` `Carousel` |
1535
+ | typography (4) | `Header` `Text` `Caption` `Markdown` |
1536
+ | data (4) | `Fact` `Table` `Chart` `Badge` |
1537
+ | media (2) | `Image` `Icon` |
1538
+ | controls (7) | `Button` `Select` `Input` `DatePicker` `Checkbox` `RadioGroup` `Form` |
1539
+ | lists (2) | `ListView` `ListViewItem` |
1540
+ | feedback (1) | `Alert` |
1541
+
1542
+ Every other component here is handed props by a programmer, and a wrong one
1543
+ costs a type error at the call site. This one is handed a tree by a language
1544
+ model over a network, so `spec` is typed `unknown` — a signature demanding a
1545
+ node would push every consumer into the same cast, which is a claim about the
1546
+ bytes nobody is in a position to make — and the interesting question is not what
1547
+ a correct spec renders as. It is what an incorrect one does, and the answer is
1548
+ never an exception:
1549
+
1550
+ - a `$type` from a newer catalogue draws an in-place notice and its siblings
1551
+ still render;
1552
+ - a spec that is not a component tree draws a legible error state, never a blank
1553
+ box that looks like a message still streaming;
1554
+ - a tree deeper than 32 levels stops and says so rather than exhausting the
1555
+ stack and taking the console with it;
1556
+ - a component that throws anyway is caught by a boundary the renderer mounts
1557
+ above its own walk.
1558
+
1559
+ All four also arrive as a stream on `onFault` — `not-a-node`, `unknown-type`,
1560
+ `missing-prop`, `too-deep`, each with a dotted path — because the person who
1561
+ needs to know this console is a version behind the model driving it is usually
1562
+ not the person looking at it.
1563
+
1564
+ Two gates keep a widget from becoming a beacon. `Box` and `Card` take a
1565
+ `background` and a size the model writes freely, and those land in an inline
1566
+ style: colours are allow-listed by _shape_ (a hex triple, a numeric colour
1567
+ function, a bare keyword) so `url(…)` and `image-set(…)` cannot reach a
1568
+ `background`, and lengths must be a plain number-and-unit, which is enough for
1569
+ `50%`, `12rem` and `320px` and not enough for `calc(…)` or `var(…)`.
1570
+
1571
+ ### It needs no Tailwind
1572
+
1573
+ The stylesheet is plain `[data-aui]` CSS and the tree it draws carries no
1574
+ `class` attribute anywhere. That is upstream's arrangement and it is worth
1575
+ keeping precisely: a widget a model composed has to draw inside whatever console
1576
+ it was sent to, and a console is very often somebody else's app with no utility
1577
+ framework, no shadcn and no design system of its own. Every hook is an attribute
1578
+ rather than a class, because a class is a claim on a name in a host's one global
1579
+ namespace and `[data-aui="card"]` collides with nothing.
1580
+
1581
+ The consequence runs all the way down. The 24 icons are hand-drawn SVG rather
1582
+ than `lucide-react`; the chart is hand-drawn SVG rather than `recharts`; the
1583
+ carousel snaps with CSS rather than a script. **`./elements/generative` declares
1584
+ no optional peer at all** — an empty row in the dist-contract's peer map, which
1585
+ is that claim checked against the built package — so there is no package in the
1586
+ tree whose absence turns a weather card into a stack trace.
1587
+
1588
+ The sheet has two honest modes, and one seam between them. Exactly one rule in
1589
+ 1,100 lines reads `--cue-*` tokens, and every reference in it carries a literal
1590
+ fallback: with `@cueplusplus/ui/styles.css` present the vocabulary is cue's
1591
+ palette, type ladder and density; without it, it is a plain legible widget that
1592
+ a `<link>` away works in a host that has never heard of cue. Every other rule
1593
+ spends `--aui-*` and nothing else, which is what makes a re-skin forty
1594
+ declarations in one place rather than a hunt through two hundred rules.
1595
+
1596
+ ### Both skins reach it, two ways
1597
+
1598
+ The vocabulary answers the same two axes `AgentSurface` publishes, and it
1599
+ answers them whether they arrive as props or by inheritance:
1600
+
1601
+ ```tsx
1602
+ <GenerativeUI spec={spec} skin="aui" fidelity="upstream" />;
1603
+
1604
+ <AgentSurface skin="aui">
1605
+ <GenerativeUI spec={spec} /> {/* no props: the stamp is on an ancestor */}
1606
+ </AgentSurface>;
1607
+ ```
1608
+
1609
+ Every island rule that lands on the root is written with both selectors — the
1610
+ stamp on an ancestor, and the stamp on the root itself — so a tree that is its
1611
+ own island and a tree inside a transcript paint identically.
1612
+
1613
+ - **`skin`** — `cue` by default. `aui` is upstream's own
1614
+ `data-aui-theme="elements"` layer, transcribed: a 20px card, a 12px borderless
1615
+ field on a filled ground, a pill button, a shadow instead of a hairline, and
1616
+ labels that stop shouting. Geometry only — the colour re-pointing arrives
1617
+ through `elements.css`, so an `aui`-skinned widget still tracks the app's
1618
+ light and dark rather than pinning a palette of its own.
1619
+ - **`fidelity`** — `cue-metrics` by default, which drives the five density knobs
1620
+ upstream names (`--aui-control-height`, `--aui-control-font-size`,
1621
+ `--aui-button-padding-x`, `--aui-field-padding-x`, `--aui-card-padding`) and
1622
+ the five type steps off cue's ladder. `upstream` pins assistant-ui's own
1623
+ numbers instead — a 36px control, 14px control type, `px-4`, `px-3`, `p-5` —
1624
+ so density stops at the island edge. The two blocks are line for line, and a
1625
+ test keeps them that way: a property one pins and the other does not restore
1626
+ is a value that survives a nesting, and the subtree would draw at neither
1627
+ fidelity.
1628
+
1629
+ ### Where the two halves meet
1630
+
1631
+ Once, through one prop. The `Markdown` intrinsic renders its source verbatim —
1632
+ as characters — unless `renderMarkdown` is given, because the generative subpath
1633
+ ships no parser and will not acquire one:
1634
+
1635
+ ```tsx
1636
+ <GenerativeUI
1637
+ spec={spec}
1638
+ renderMarkdown={(md) => <MarkdownText>{md}</MarkdownText>}
1639
+ />
1640
+ ```
1641
+
1642
+ Neither subpath depends on the other. A console drawing weather cards installs
1643
+ no sanitizer; a console rendering markdown installs no vocabulary.
1644
+
1645
+ ## Where to look
1646
+
1647
+ Benched at the states that matter rather than at the happy path. `MarkdownText`
1648
+ on `elements-thread`: a settled document exercising the whole subset, whose last
1649
+ line is a real injection shape and renders as nothing; a turn cut mid-token; a
1650
+ fence still filling. `GenerativeUI` on `elements-knowledge`: a composed
1651
+ dashboard, a form with all six control types, a chart, an unknown component, and
1652
+ a spec that is not a tree at all. Pages are at `/docs/components/markdown-text`
1653
+ and `/docs/components/generative-ui`.
1654
+
1655
+ ## What did not change
1656
+
1657
+ The root barrel, and `@cueplusplus/ui/elements`. Both entries gained nothing —
1658
+ no export, no peer, no bytes. `react-markdown` and `rehype-sanitize` are
1659
+ optional peers of `./elements/markdown` alone, externalised rather than inlined
1660
+ for the usual reason and one more: bundling a sanitizer would freeze a security
1661
+ boundary at whatever version was in the store the day this package was built,
1662
+ where a consumer's `pnpm update` could never reach it.
1663
+
1664
+ No component's props, structure or rendering moved. The `Chat` group is the same
1665
+ six components it was; `Message`, `AskBox`, `ToolCallCard` and all ninety-four
1666
+ elements still render what they are given as characters. `elements.css`, the
1667
+ island, the tokens and the sync pin are untouched.
1668
+
1669
+ One accessibility fix, found by running axe over the specs a model can emit
1670
+ rather than over the benches: a `ListViewItem` a model put outside a `ListView`
1671
+ drew as `role="listitem"`, which requires a `list` parent it did not have. A
1672
+ stray row now draws as a row and says nothing about where it is, which is true.
1673
+
1674
+ `schemaVersion` is still `1`.
1675
+
1676
+ - 42e0672: ## One voice at the foot of the transcript
1677
+
1678
+ Two elements in this family are live regions. `TailStatus` is a `role="status"`,
1679
+ because that is what makes the row at the foot of a transcript reach a reader
1680
+ who cannot see the spinner. `LiveRegionAnnouncer` is a polite region with a
1681
+ queue, and the nine transitions in `ANNOUNCEMENTS` are the same states that row
1682
+ narrates.
1683
+
1684
+ A console that mounted both — the ordinary shape of an agent console, and the
1685
+ one the benches show — had every state read out twice, out of step, because the
1686
+ two regions update at different moments. Neither file mentioned the other.
1687
+
1688
+ They compose now, and the rule is that **the announcer speaks and the row
1689
+ draws**:
1690
+
1691
+ ```tsx
1692
+ <TailStatus action={TAIL_ACTIONS.searchingCode} detail="composer.tsx" />;
1693
+ <LiveRegionAnnouncer messages={log} />;
1694
+ ```
1695
+
1696
+ Mounting an announcer takes the speech off the row automatically. The words stay
1697
+ on the screen, the `role="status"` goes, and `data-announces="false"` says which
1698
+ of the two it is doing. Nothing to wire: the row notices on its own, wherever in
1699
+ the tree either of them sits.
1700
+
1701
+ The row is the half that defers because the announcer is the better speaker. It
1702
+ covers transitions the row can never show — an error, a permission request, a
1703
+ finished answer. It is mounted empty and stays mounted, which is the only way a
1704
+ live region is announced reliably, while a tail row comes and goes with the run.
1705
+ And its whole mechanism — 400ms of spacing, five deep, paused while the page is
1706
+ hidden or a modal has the reader — exists because narration that changes several
1707
+ times a second is unusable when it is spoken, which is exactly what the row is.
1708
+
1709
+ Two ways to decide it by hand:
1710
+
1711
+ - `<TailStatus announce />` — the row speaks even with an announcer mounted, for
1712
+ a console whose announcer is fed something other than these actions.
1713
+ - `<TailStatus announce={false} />` — the row never speaks, for a console that
1714
+ narrates the foot of the transcript some other way.
1715
+
1716
+ And `useAnnouncerPresent()` is exported, so a console's own status row can ask
1717
+ the same question before becoming a second live region.
1718
+
1719
+ - 51e5046: ## The amber carries its own ink too
1720
+
1721
+ This is the amber half of the argument `the-red-carries-its-own-ink` already
1722
+ made, and it is the same argument: the tone a control is _filled_ with decides
1723
+ what can be read on it, and no other token in the palette knows that.
1724
+
1725
+ `TwoStepButton` is the one control in the library whose whole reason for
1726
+ existing is that the press is consequential — arm, then confirm. Its `warn`
1727
+ confirm painted `bg-warn text-black`. Black on an amber is a good guess, and on
1728
+ the ten dark blocks it is right by a distance: 7.11:1 to 12.41:1. It is wrong on
1729
+ the light ones, because a light theme darkens its amber to clear the paper and a
1730
+ darkened amber wants paper back. **3.58:1** on requestport, **3.74:1** on dusk,
1731
+ **4.22:1** on cue's own light block — three of the twenty preset x mode blocks
1732
+ under AA, on the half of the control that does the thing.
1733
+
1734
+ It could not be closed before: there was no `--cue-warn-fg` to close it with,
1735
+ and the queued note for the red said so in as many words. There is one now.
1736
+
1737
+ ### `--cue-warn-fg`
1738
+
1739
+ A new authored anchor, per preset, per mode, in the colour contract beside the
1740
+ tone it is chosen against, with `text-warn-fg` / `bg-warn-fg` bridged into
1741
+ Tailwind like every other colour token:
1742
+
1743
+ ```css
1744
+ [data-theme="cue"] {
1745
+ --cue-warn: oklch(0.8 0.17 75);
1746
+ --cue-warn-fg: #000000;
1747
+ }
1748
+ [data-theme="cue"][data-mode="light"] {
1749
+ --cue-warn: oklch(0.55 0.115 70);
1750
+ --cue-warn-fg: #fcfcfc;
1751
+ }
1752
+ ```
1753
+
1754
+ Not one value is hand-picked. Each is the same `pickInk` `createTheme()` already
1755
+ uses for `accent-fg` and `danger-fg`, run over the extremes the theme itself
1756
+ owns — its page, its deepest well, its type — falling to pure black or white
1757
+ only where none of the three reaches AA. Run over the reds instead, it
1758
+ reproduces all twenty shipped `danger-fg` values exactly, spelling included,
1759
+ which is the check that this is the same derivation rather than a second one
1760
+ wearing its name.
1761
+
1762
+ The amber does not answer the way the red did, and that is the argument for
1763
+ deriving against each tone instead of writing one rule about ink on a status
1764
+ colour. All ten dark blocks take their deepest ground. The light ten split three
1765
+ ways: cue, dusk and requestport take their paper; hivehub, luma, signal and
1766
+ terminal take the theme's own type; quotamate, snuffle and venu take plain black
1767
+ because nothing they own reaches AA on their amber. The worst amber pair in the
1768
+ system is **4.77:1** (luma, light). The worst pair anywhere is still the red's
1769
+ 4.59:1.
1770
+
1771
+ `CONTRAST_REQUIREMENTS` gains an eleventh pair, `warn-fg/warn` at 4.5:1, so a
1772
+ generated palette is judged on it too, and `createTheme()` derives it for one.
1773
+ The configurator shows it beside the amber in the Status group. The assistant-ui
1774
+ island declares its own, because that block re-tints `--cue-warn` and an ink
1775
+ chosen against the app's amber is an ink chosen against a colour nobody is
1776
+ looking at in there.
1777
+
1778
+ ### The sweep covers the amber now
1779
+
1780
+ `test/destructive-ink.test.ts` reads the source and measures what the components
1781
+ _spend_ rather than what the palette _offers_. It swept the red and said in its
1782
+ own prose that it did not sweep the amber, because sweeping a defect nobody
1783
+ could fix turns a missing token into a red suite instead of into a decision. The
1784
+ token exists, so the amber is in: every class string that makes `bg-warn` a
1785
+ ground and puts an ink on it, every rule in the package's own stylesheets that
1786
+ does the same, measured on all twenty blocks at 4.5:1, with the fills pinned by
1787
+ name per tone so a new one has to choose its ink in the commit that paints it. A
1788
+ tint keeps its exemption — `bg-warn/10 text-warn` and the search highlight's
1789
+ `bg-warn/35 text-fg` are read against the page, and the report already owns
1790
+ those pairs — and the file says out loud what that exemption does not cover.
1791
+
1792
+ One number beside it got more honest. Both that sweep and
1793
+ `everywhere-the-red-is-spent` said the right ink "falls to 3.95:1 on quotamate's
1794
+ dark" under `bg-danger/90` without naming the ground the alpha let through. It
1795
+ is **3.94:1** over `--cue-sunken` and **3.97:1** over `--cue-bg`. That the two
1796
+ grounds disagree at all is the reason an alpha is refused rather than measured.
1797
+
1798
+ ### And the amber's pair is swept on its own, the way the red's is
1799
+
1800
+ `destructive-ink.test.ts` covers `warn-fg` on `warn` across all twenty blocks
1801
+ for exactly as long as `TwoStepButton` keeps painting that string, because it
1802
+ measures the call site. The red does not depend on that: it has a second,
1803
+ unconditional sweep of the token pair beside `RevertDock` in
1804
+ `elements/canon.test.tsx`. The amber had no equivalent, and the gap is not
1805
+ theoretical — with the confirm moved off the amber in the way a later refactor
1806
+ legitimately might, and that file's pinned list updated to match, a `warn-fg`
1807
+ re-tinted down to **1.62:1** passed every contrast guard in the package.
1808
+
1809
+ So `instruments/two-step-button.test.tsx` gains the sweep the red has, asking
1810
+ nothing about what any component currently paints, plus the spot check that says
1811
+ this confirm takes the amber at full strength in the amber's own ink rather than
1812
+ the `text-black` it used to.
1813
+
1814
+ ### If you ship your own theme block
1815
+
1816
+ A hand-written `[data-theme="acme"]` block should declare `--cue-warn-fg`
1817
+ alongside `--cue-warn`, the same way it now declares `--cue-danger-fg` alongside
1818
+ `--cue-danger`. Without it, `text-warn-fg` resolves to nothing on your theme.
1819
+ Themes generated by `createTheme()` and the configurator's exports already carry
1820
+ it.
1821
+
1822
+ - 704f9e5: ## The canon
1823
+
1824
+ The elements family this library spent four releases adopting is ninety-five
1825
+ files deep and it is one product's answer to what a transcript needs. A very
1826
+ good one — and one. So before closing the adoption, four teardowns went reading
1827
+ the others: Cursor and Trae's local bundles, Claude Code's editor panel, the
1828
+ Codex extension, and the open-source field — opencode, Cline, Roo, Continue,
1829
+ Void, Zed, OpenHands, Hermes. One question each. **Which rows do three or more
1830
+ shipping agent UIs have, that no component catalogue ships at all?**
1831
+
1832
+ Seventeen answers, and they are in this release. They live in
1833
+ `@cueplusplus/ui/elements` beside the vendored family, in the same barrel,
1834
+ inside the same island, on the same `data-slot` conventions — but they are
1835
+ cue's, hand-written in house style rather than fetched and rewritten, and the
1836
+ sync codemod never sees them, so there is nothing here for a re-sync to revert.
1837
+ Every file opens with a comment naming the products that justify it and the
1838
+ report section that recorded it.
1839
+
1840
+ ```tsx
1841
+ import {
1842
+ CompactionRow,
1843
+ TailStatus,
1844
+ TurnFooter,
1845
+ WorkCollapse,
1846
+ verbFor,
1847
+ } from "@cueplusplus/ui/elements";
1848
+
1849
+ <WorkCollapse
1850
+ durationMs={252_000}
1851
+ toolCalls={14}
1852
+ open={open}
1853
+ onOpenChange={setOpen}
1854
+ >
1855
+ {rows}
1856
+ </WorkCollapse>;
1857
+
1858
+ <CompactionRow
1859
+ state="completed"
1860
+ tokensBefore={128_000}
1861
+ tokensAfter={24_000}
1862
+ messagesBefore={214}
1863
+ messagesAfter={31}
1864
+ />;
1865
+
1866
+ <TurnFooter
1867
+ onCopy={copy}
1868
+ onFork={fork}
1869
+ timestamp={{
1870
+ label: "2m ago",
1871
+ title: "Today at 2:14 PM",
1872
+ subtitle: "Worked for 2m 15s",
1873
+ }}
1874
+ />;
1875
+
1876
+ <TailStatus
1877
+ action={verbFor("shell", "active")}
1878
+ detail="pnpm test"
1879
+ onCancel={stop}
1880
+ />;
1881
+ ```
1882
+
1883
+ ### The seventeen, and who else ships them
1884
+
1885
+ | | | |
1886
+ | --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------ |
1887
+ | `TailStatus` | the live row at the foot: an action, a detail that truncates on its own, an inline Cancel that exists only when there is something to cancel | Cursor's `AgentTranscriptTailStatus`, opencode's action vocabulary |
1888
+ | `WorkCollapse` | `Worked for 4m 12s and made 14 tool calls`, expanding back to the rows it folded | Cline's `WorkActivity`, Hermes's run collapsing |
1889
+ | `TimeBoundary` / `UnreadDivider` | the two transcript rules nobody documents — one row each, placed by the caller | Cursor's row union (the only exhaustive one in any shipping product) |
1890
+ | `TurnFooter` / `EndOfTurnSummary` | copy / reply / fork with a three-level timestamp, and `3 files changed +48 −12` | Cursor; the diff-stat half also Zed, Roo, Cline |
1891
+ | `CompactionRow` | `128k → 24k tokens · 214 → 31 messages`, in all five states | Cline, Zed, Roo, Continue |
1892
+ | `QueueDock` | what you typed while it was working — foldable, with edit / send-now / remove on every entry | opencode's `followupDock`, Cursor, Cline, Roo, Zed |
1893
+ | `RevertDock` | restore, on the files-versus-conversation axis, behind a second press | Roo's `CheckpointMenu`, Cline, Cursor, opencode |
1894
+ | `ContextUsage` | a segmented bar, a `Category / Tokens / Usage` table, and the stat rows that turn a gauge into a bill | Cursor's tray, opencode's context tab, Claude Code's modal |
1895
+ | `PermissionScopes` | approval whose "always allow" writes its own label and names the file it writes to | Claude Code's scope cycler, Zed's granularity dropdown, Roo, Cline, Void |
1896
+ | `AgentModeBadge` | how much authority the run is holding, as a chip and as `data-agent-mode` | Claude Code's mode-tinted chrome, Cursor's mode tokens |
1897
+ | `RiskBadge` | an explicit `unknown` / `low` / `medium` / `high` on the call, with the qualifier beside it | OpenHands' `SecurityRisk`; the qualifier from Cline, Roo, Zed |
1898
+ | `VerdictRow` | a scored verification result on a finished turn — rating, score, and what is most likely wrong | OpenHands' critic display, generalised past the critic |
1899
+ | `AnimatedNumber` | a token, cost or duration readout that counts to its new value in tabular numerals | Claude Code's live thinking-token counter |
1900
+ | `Clamp` | the truncation trio — a height, a fixed-pixel dissolve, a way back | Claude Code's five separate truncations |
1901
+ | `LiveRegionAnnouncer` | one polite atomic region and the queue that makes it audible | Claude Code's announcement queue, Codex's announcement strings |
1902
+
1903
+ Nothing here shipped on taste. The rule for the phase was that a component
1904
+ which could not point at a report section did not ship, and the two that are
1905
+ gaps rather than crowds say so in their own files: `RiskBadge` exists because
1906
+ six of the seven open-source UIs encode risk _implicitly_ — you reconstruct how
1907
+ dangerous a call is from which approval bucket its tool happened to land in —
1908
+ and one, OpenHands, makes it a property of the action. `VerdictRow` exists
1909
+ because every agent runtime now scores its own work somewhere and, in six of
1910
+ seven, that judgement reaches the reader as prose or not at all.
1911
+
1912
+ ### Copy is data, not markup
1913
+
1914
+ The tense triad is the single most repeated pattern in the whole study.
1915
+ `Run` / `Running` / `Ran`. Cursor keeps it in `tool-action-labels.js`, Void as
1916
+ `{proposed, running, done}`, Continue as `{wouldLikeTo, isCurrently,
1917
+ hasAlready}`, Hermes as five category verbs, opencode inside an i18n dictionary
1918
+ its own `AGENTS.md` forbids anyone to bypass. Six products, the same three
1919
+ strings, private to the app every single time.
1920
+
1921
+ Here they are tables. `VERB_TRIADS` with `verbFor()` over it — which falls back
1922
+ to `Use` / `Using` / `Used` for a kind it does not know, because an MCP server
1923
+ names its own tools and a row forced to choose between `undefined` and the raw
1924
+ wire name will render the wire name. Beside it: `TAIL_ACTIONS` (opencode's ten
1925
+ status phrases), `TURN_ACTION_LABELS`, `COMPACTION_LABELS`, `QUEUE_LABELS`, the
1926
+ revert triple, the context set, the authority ladder, the permission pair, the
1927
+ two judgements, `CLAMP_LABELS` and the nine `ANNOUNCEMENTS`.
1928
+
1929
+ ```tsx
1930
+ import { VERB_TRIADS, verbFor } from "@cueplusplus/ui/elements";
1931
+
1932
+ // Your product's agent renders rather than uses.
1933
+ const VERBS = {
1934
+ ...VERB_TRIADS,
1935
+ render: { proposed: "Render", active: "Rendering", past: "Rendered" },
1936
+ };
1937
+ ```
1938
+
1939
+ Every component that draws one of these takes the copy as a prop and defaults
1940
+ to the table. Localising a console, or writing for a product whose agent
1941
+ "edits" where this one "patches", is spreading a table — not forking a
1942
+ component to change a word.
1943
+
1944
+ ### The mode ladder is a token contract, not a colour prop
1945
+
1946
+ The most distinctive systemic idea in the study is not a component. Claude
1947
+ Code's panel puts one attribute on the composer, the send button and the
1948
+ spinner, and each re-derives its accent from it — six modes painted in four
1949
+ colours, because what a mode _means_ is how much authority the run is holding,
1950
+ and that ladder has four rungs everywhere. Cursor reaches the same place from
1951
+ the token side: a quadruple per mode that the chip, the composer border and the
1952
+ transcript accent all read.
1953
+
1954
+ So `@cueplusplus/ui/elements.css` gains sixteen properties — four per rung,
1955
+ declared under `[data-agent-mode]` — and four aliases pointing at whichever
1956
+ rung the attribute names:
1957
+
1958
+ ```css
1959
+ .composer:focus-within {
1960
+ border-color: var(--cue-elements-mode-icon);
1961
+ }
1962
+ .send-button {
1963
+ background: var(--cue-elements-mode-icon);
1964
+ color: var(--cue-elements-mode-background);
1965
+ }
1966
+ .turn-rail {
1967
+ background: var(--cue-elements-mode-border);
1968
+ }
1969
+ ```
1970
+
1971
+ Stamp `data-agent-mode="unsafe"` on the composer and all three follow, with no
1972
+ prop threading and no branch in any component. `AgentModeBadge` is the readout
1973
+ over the same four properties, not the API.
1974
+
1975
+ They are declared under the attribute rather than at `:root` on purpose: a
1976
+ custom property's `var()` references resolve where they are declared, so a rung
1977
+ declared at `:root` would paint a badge inside an `<AgentSurface skin="aui">`
1978
+ in the console's palette while everything around it painted in the island's.
1979
+ The default rung is the family's neutral, not `manual`, because an unmapped
1980
+ mode painted brand-orange would be claiming the run asks before it acts, and
1981
+ that is the one claim nobody should make on a guess. Those same four neutrals
1982
+ are the badge's inline fallbacks, so a consumer who never imports
1983
+ `elements.css` gets a legible chip that makes no claim rather than an invisible
1984
+ one — and the unpainted and the unmapped chip look alike deliberately.
1985
+
1986
+ ### The controls that are not allowed to lie
1987
+
1988
+ Three of the seventeen sit where a control that misstates what it is about to
1989
+ do is a defect rather than a polish item.
1990
+
1991
+ **`PermissionScopes`** is the one control in an agent UI whose label has to be
1992
+ computed, because what it grants is different every time it appears:
1993
+ `Yes, allow Bash(cue patch:*) for this project (just you)`, with the file that
1994
+ grant is saved to in a sentence underneath. Two departures from the reference,
1995
+ both in the same direction. Nothing is elided — the reference shortens a rule
1996
+ past twenty characters and strips the `:*` off a prefix rule, and both cuts
1997
+ land on exactly the part that says how far the grant reaches, so this label
1998
+ wraps instead — inside the word where it has to, since a rule and a directory
1999
+ are one word to a line breaker and an underscored path gives it nowhere else
2000
+ to go, and so does every other string the caller hands it: the subject beside
2001
+ the question and the sentence naming the file the grant is written into break
2002
+ the same way, because the account of where a permission lands is no more
2003
+ allowed off the edge of the card than the account of what it grants. And when
2004
+ there are more rules than the sentence can name, every one of them is listed
2005
+ underneath, because `and 3 more` is a count and a count is not an account. No scope means no always-allow answer at all: a standing
2006
+ grant that cannot say where it is saved is not one this element will offer.
2007
+ The scope table ships the four reaches in words and a file path for none of
2008
+ them — where a grant lands is the product's answer, and a design system that
2009
+ guessed at it would commit the very defect this element exists to prevent.
2010
+
2011
+ **`RevertDock`** is the family's one destructive control and is built like one.
2012
+ The axis first, because a run changes the files and the conversation about them
2013
+ and putting one back is not putting the other back. Then the gate: choosing a
2014
+ scope only arms the dock, `onRestore` can fire from the second separate press
2015
+ alone, and `This action cannot be undone.` is on the screen the whole time the
2016
+ dock is open rather than in a props table somebody reads afterwards. Then the
2017
+ geometry, which is the half a gate cannot do on its own: arming does not
2018
+ replace the scope list — it marks the chosen scope, disables the list where it
2019
+ stands and appends the question _underneath_ it, so the destructive control is
2020
+ never drawn over the control that armed it and a double click cannot walk
2021
+ through both steps. Its confirm also refuses presses for 400 ms after it
2022
+ arrives, and fires once per arm. The way out is `TwoStepButton`'s ruling rather
2023
+ than a second one: focus lands on `Keep everything` and never on the
2024
+ destructive half, that button comes first in the DOM as well as on the screen,
2025
+ `Escape` disarms and stops there rather than closing whatever the dock is
2026
+ inside, and folding the dock away disarms it — an armed state nobody can see is
2027
+ one somebody re-opens into.
2028
+
2029
+ **`RiskBadge`** treats `unknown` as a first-class value drawn in the neutral,
2030
+ never in the low-risk green: a runtime with no scorer must not be made to look
2031
+ like one that scored everything safe. And the qualifier renders beside the
2032
+ label rather than hiding in a `title`, because `High risk` alone is a
2033
+ temperature while `High risk · outside your workspace` is a fact somebody can
2034
+ act on.
2035
+
2036
+ ### Three that are contracts more than components
2037
+
2038
+ **`AnimatedNumber`** counts to its new value instead of jumping to it, in
2039
+ tabular numerals — both halves lifted from one line of a shipping bundle, and
2040
+ the second is not cosmetic: a proportional `1` is narrower than a `0`, so a
2041
+ stepping counter nudges everything to its right on every update, which is what
2042
+ makes a busy transcript feel unstable. The value stays a prop and every tween
2043
+ lands exactly on it. Mid-tween the digits are `aria-hidden` with the settled
2044
+ value carried beside them, so a screen reader hears what the run reported and
2045
+ never a frame of the animation. Reduced motion is answered by _arriving_: no
2046
+ frame is ever scheduled, because a readout that stopped where it was would be
2047
+ wrong rather than merely still.
2048
+
2049
+ **`Clamp`** is a height, a dissolve of a fixed number of pixels, and a way
2050
+ back. The fixed depth is the part worth copying — ten pixels of dissolve is the
2051
+ same cut over a two-line row and a forty-line one, which is why the reference's
2052
+ five separate truncations read as one idea. The fade is a **mask**, not a
2053
+ gradient to the surface colour, which is the bug the reference shipped: its
2054
+ diff variant hard-codes `#1e1e1e` and smears on a light theme, while a mask
2055
+ fades the content itself and has no colour to get wrong in any theme, either
2056
+ mode, or inside an island. The trio is published as
2057
+ `--cue-elements-clamp-height` / `-fade`, so a height overridden in a stylesheet
2058
+ moves the dissolve with it. The reveal is always in the DOM and always
2059
+ focusable — a `Show more` chip that appears on hover only is unreachable for
2060
+ anyone not holding a pointer.
2061
+
2062
+ **`LiveRegionAnnouncer`** is the accessibility surface, and it is a queue
2063
+ rather than a live region wired to state, because the naive version announces
2064
+ almost nothing: polite announcements replace each other, so a region bound to a
2065
+ transcript that changes four times a second speaks once and swallows the rest.
2066
+ Four rules make it audible — 400 ms of spacing, five deep with the _oldest_
2067
+ dropped (behind a busy run the newest is the true one), held while the page is
2068
+ hidden or a modal has the reader, and an identical sentence re-announced by
2069
+ appending a space, which changes the string without changing a word of it. The
2070
+ hold is in force from the first render rather than the first effect, so an
2071
+ announcer that mounts into a background tab holding a backlog — a restored
2072
+ session, which is the whole reason `announceOnMount` exists — does not spend a
2073
+ line of it finding out where it is. The
2074
+ region mounts empty and stays mounted, since a live region added at the same
2075
+ moment as its text is one most assistive technologies never announce. One
2076
+ decision moves to the caller: `paused` is a prop rather than an internal
2077
+ ref-count of open overlays, because this library has its own portal contract
2078
+ and an announcer holding a second opinion about what is on screen would be
2079
+ wrong exactly when it mattered.
2080
+
2081
+ ### What did not change
2082
+
2083
+ No new subpath, no new peer, no new group. The seventeen are exports of
2084
+ `@cueplusplus/ui/elements`, drawn with the icon set that subpath already
2085
+ declares, and the root barrel gained nothing. The ninety-five vendored files
2086
+ are untouched and the sync pin, the codemod and `elements-sync.lock.json` are
2087
+ exactly where P0 left them — a re-sync at a newer upstream SHA rewrites the
2088
+ vendored tree and cannot touch a line of this. The tokens are untouched:
2089
+ everything here paints from the contract the flagship release already shipped,
2090
+ and `elements.css` gained one section rather than a new dependency for anyone
2091
+ in cue mode.
2092
+
2093
+ ***
2094
+
2095
+ **This closes the adoption.** `@cueplusplus/ui/elements` is now the ninety-five
2096
+ vendored components, the island that can paint them in either value-set, the
2097
+ sanitized markdown surface and generative vocabulary, the replay transport with
2098
+ its fixture catalogue and eight gallery studies — and the seventeen rows the
2099
+ field study found that nobody was shipping.
2100
+
2101
+ - 06eb4ac: ## The flagship answers in colour
2102
+
2103
+ `cue` is a monochrome brand, and until this release it carried the monochrome
2104
+ all the way through to the one vocabulary that exists to be told apart at a
2105
+ glance. On the default theme `--cue-ok`, `--cue-busy`, `--cue-warn` and
2106
+ `--cue-info` were all `#ffffff` on dark and all `#0a0a0a` on light: four
2107
+ different meanings, one colour, distinguishable only by the glyph or the word
2108
+ beside them. It read as restraint on a settings page. It reads as a defect on a
2109
+ transcript where three tool calls are in three different states and the only
2110
+ question is which of them is still moving.
2111
+
2112
+ They are chromatic now.
2113
+
2114
+ | token | before (dark / light) | after (dark / light) |
2115
+ | -------------- | --------------------- | ----------------------------------------------- |
2116
+ | `--cue-ok` | `#ffffff` / `#0a0a0a` | `oklch(0.72 0.17 149)` / `oklch(0.53 0.14 149)` |
2117
+ | `--cue-busy` | `#ffffff` / `#0a0a0a` | `oklch(0.78 0.13 220)` / `oklch(0.54 0.1 225)` |
2118
+ | `--cue-warn` | `#ffffff` / `#0a0a0a` | `oklch(0.8 0.17 75)` / `oklch(0.55 0.115 70)` |
2119
+ | `--cue-info` | `#ffffff` / `#0a0a0a` | `oklch(0.75 0.12 252)` / `oklch(0.55 0.16 258)` |
2120
+ | `--cue-danger` | `#ff5f57` / `#d03030` | unchanged |
2121
+
2122
+ `ok` and `warn` are assistant-ui's own semantic pair; `busy` is a console cyan
2123
+ for in-flight and `info` a calmer blue for news. `danger` does not move — it was
2124
+ the one tone already chromatic, and it is the one nobody should have to
2125
+ re-learn.
2126
+
2127
+ The light row is darker than these hues usually run, and that is the point: a
2128
+ tone is not only a dot here. `Chip`, `StatusDot`, `Toast` and the rest of the
2129
+ tone family draw `text-<tone>` at 11px uppercase — text, judged at 4.5:1, not at
2130
+ the 3:1 a signal is held to. Every light value above clears 4.5:1 on `--cue-bg`
2131
+ (4.79 to 4.85, beside `danger`'s 4.94) and every one of them is inside sRGB, so
2132
+ the measured ratio is the painted one. Dark was never in question: it runs 8.5
2133
+ to 10.3:1.
2134
+
2135
+ The chrome is untouched. Ink, ground, surfaces, borders, `accent`, `focus` and
2136
+ `selection` are the same monochrome they have always been, on both modes. This
2137
+ change is the status vocabulary and the live hue below it, and nothing else.
2138
+
2139
+ The other nine presets do not move at all: `terminal`, `signal`, `venu`,
2140
+ `hivehub`, `dusk`, `luma`, `snuffle`, `quotamate` and `requestport` were already
2141
+ chromatic here, and an app on any of them sees nothing new in this section.
2142
+
2143
+ ### Migrating
2144
+
2145
+ **A console on the default theme that relied on monochrome statuses will now
2146
+ see colour.** No API moved and nothing needs editing to keep working, but
2147
+ pixels change, and they change in more places than a status pill: `Chip`,
2148
+ `StatusDot`, `Meter`, `Toast`, `Stat`, `StatusBar`, `LogViewer`, `Sparkline`,
2149
+ `GroupBar`, `TwoStepButton`, `Tree`, `TerminalFrame`, `AppWindowFrame`,
2150
+ `UsageChart`, `FileUpload`, `EnvVarInput`, `AskBox`, `DelegationCard` and
2151
+ `ToolCallCard` all spend these four tokens, along with the MIDI and DMX
2152
+ instruments. Anywhere a `tone` prop was chosen and then quietly rendered white,
2153
+ it now renders the tone. Screenshot tests on `data-theme="cue"` will need new
2154
+ baselines; a design that used `tone="info"` as a way of saying "plain" will now
2155
+ say "blue".
2156
+
2157
+ If a product deliberately wants the monochrome back, it is four declarations —
2158
+ the soft fill and the rim derive from the base with `color-mix`, so overriding
2159
+ the base moves the whole family with it:
2160
+
2161
+ ```css
2162
+ @import "tailwindcss";
2163
+ @import "@cueplusplus/ui/styles.css";
2164
+
2165
+ [data-theme="cue"] {
2166
+ --cue-ok: #ffffff;
2167
+ --cue-busy: #ffffff;
2168
+ --cue-warn: #ffffff;
2169
+ --cue-info: #ffffff;
2170
+ }
2171
+ [data-theme="cue"][data-mode="light"] {
2172
+ --cue-ok: #0a0a0a;
2173
+ --cue-busy: #0a0a0a;
2174
+ --cue-warn: #0a0a0a;
2175
+ --cue-info: #0a0a0a;
2176
+ }
2177
+ ```
2178
+
2179
+ Two selectors, not one, and the block goes _after_ the import — the preset's own
2180
+ rules are `[data-theme="cue"]` and `[data-theme="cue"][data-mode="light"]`, so
2181
+ an override has to match their specificity and win on source order. An app that
2182
+ runs `mode="system"` mirrors these onto
2183
+ `[data-theme="cue"][data-mode="system"]` inside the two `prefers-color-scheme`
2184
+ queries, exactly as the generated sheet does.
2185
+
2186
+ ## `--cue-stream`, the live hue
2187
+
2188
+ The colour contract gains one token and its two derivations —
2189
+ `--cue-stream`, `--cue-stream-soft` and `--cue-stream-border` — on all ten
2190
+ presets, in both modes, at the same 12% and 35% mixes the statuses use. The
2191
+ colour contract is 42 custom properties where it was 39.
2192
+
2193
+ It is the colour of something still arriving: a token mid-stream, a tool still
2194
+ running, a cursor that has not stopped. It is deliberately **not** a sixth
2195
+ status. A status is something a `Chip` or a `StatusDot` can be _told_ to be, and
2196
+ "streaming" is not a tone a caller picks — it is a property of a surface while
2197
+ the model is talking. There is no `tone="stream"` and there is not going to be
2198
+ one. What it shares with the statuses is the shape of its family, which is why
2199
+ it is derived beside them and bridged beside them:
2200
+
2201
+ ```
2202
+ --color-stream → text-stream bg-stream/15 border-stream/30
2203
+ ```
2204
+
2205
+ The bridge carries the tone at full strength and lets Tailwind's alpha modifier
2206
+ do the washes, the way `Chip` already writes `bg-busy/10`.
2207
+
2208
+ The flagship pair is assistant-ui's `--aui-live` — `blue-400` on ink verbatim,
2209
+ and on paper `blue-500`'s hue taken down to `oklch(0.55 0.21 259.815)`, because
2210
+ the elements draw the live hue as text and `blue-500` itself lands at 3.6:1
2211
+ there. Every other preset answers in its own blue, except `terminal`, which
2212
+ already owned a live hue and keeps it.
2213
+
2214
+ Three things that hold a second copy of the contract moved with it, so nothing
2215
+ needs to be told about the new token twice: `createTheme()` resolves and derives
2216
+ `stream` like any other tone and darkens an invented light one to the non-text
2217
+ AA floor; the contrast report checks `stream/bg` against that same floor — every
2218
+ preset clears it with room to spare, the tightest being `terminal`'s light block
2219
+ at 4.89:1; and the configurator edits and exports it beside the statuses rather
2220
+ than in the miscellany. A theme somebody generated before this release and pasted into their
2221
+ own CSS keeps working: `stream` falls back through the same resolution order
2222
+ every other invented token does.
2223
+
2224
+ ## `@cueplusplus/ui/elements`
2225
+
2226
+ A new group, and the largest one this library has added at once: the agent's own
2227
+ surface. Fifteen elements, plus the island and the two label primitives the
2228
+ family is built out of, at `/docs/components/elements`.
2229
+
2230
+ | element | what it is |
2231
+ | ----------------- | ----------------------------------------------------------- |
2232
+ | `ToolCall` | one invocation, request and result behind a disclosure |
2233
+ | `ToolGroup` | calls that went out together, collapsed to one row |
2234
+ | `ToolError` | one call failed, with the error legible |
2235
+ | `TerminalBlock` | output streaming line by line, ending in an exit status |
2236
+ | `CodeDiff` | a unified diff, tinted, sized for a chat column |
2237
+ | `ReviewableDiff` | the same diff where each hunk is a decision |
2238
+ | `FileTree` | everything a run touched, with the churn per file |
2239
+ | `ApprovalCard` | the agent asking before it does something with side effects |
2240
+ | `PermissionGrant` | granting a capability rather than approving an action |
2241
+ | `AgentPlan` | a checklist the agent works through |
2242
+ | `TodoList` | the agent's working list, rewritten mid-run |
2243
+ | `AgentStatus` | one pill: what it is doing, and for how long |
2244
+ | `SubagentList` | parallel workers, their models and their progress |
2245
+ | `TraceWaterfall` | every span in a run on one nested time axis |
2246
+ | `ChatPanel` | the whole family working together, with its five parts |
2247
+
2248
+ Beside them: `ShimmerLabel` and `SwapLabel`, the eighteen surface recipes the
2249
+ family shares as plain class strings (`paper`, `floating`, `field`, `mono`,
2250
+ `live`, `codeSurface`, `collapsePanel`, `pressable`, the swap pairs and the
2251
+ rest), and `range`'s six functions — `at`, `clamp`, `indexIn`, `pct`,
2252
+ `progressOf`, `take` — which are the arithmetic a replayed transcript is made
2253
+ of.
2254
+
2255
+ Two conventions run through the group and both are worth knowing before you read
2256
+ the source:
2257
+
2258
+ **State is a prop, and time is the caller's.** Nothing here holds a run.
2259
+ `ToolCall` is _told_ `running` and `open`; `TerminalBlock` is told how many of
2260
+ its lines are visible. Replaying a conversation is arithmetic on props, which is
2261
+ what makes every one of these benchable at every state.
2262
+
2263
+ **These components are written upstream's way, not this library's.** Plain
2264
+ exported functions with no `forwardRef`, a `data-slot` attribute on every root,
2265
+ `className` merged last through `cn`. That is deliberate rather than sloppy: it
2266
+ keeps a re-synced diff readable, and `data-slot` is what lets an app restyle a
2267
+ part from the outside without a prop for it. `AgentSurface` — cue's own — is the
2268
+ one component in the group written in the house style.
2269
+
2270
+ `lucide-react` is an **optional peer** of this entry, which is why the group is
2271
+ its own subpath instead of part of the root barrel: the family draws its own
2272
+ icons rather than taking them from a prop. Import nothing from `/elements` and
2273
+ it costs nothing — no install, no bundle, no resolution error.
2274
+
2275
+ `@cueplusplus/ui/elements.css` ships beside it and is **optional even for this
2276
+ subpath**, because the family is painted by the token layer already:
2277
+
2278
+ ```css
2279
+ @import "tailwindcss";
2280
+ @import "@cueplusplus/ui/styles.css";
2281
+ @import "@cueplusplus/ui/elements.css"; /* motion, and the supersede island */
2282
+ ```
2283
+
2284
+ It carries the two vocabularies upstream never shipped as anything but
2285
+ dependencies — the shimmer sweep and the nine entrance utilities, both stilled
2286
+ under `prefers-reduced-motion` — and the island values below. Skip it and the
2287
+ elements still render in cue's palette; you lose the motion and `skin="aui"`
2288
+ becomes a no-op.
2289
+
2290
+ **Every class it declares is namespaced `cue-elements-`**, and that is a
2291
+ compatibility promise rather than a house style. The entrance utilities are
2292
+ `tw-animate-css`'s by name — `animate-in`, `fade-in`, `slide-in-from-*` — and
2293
+ `tw-animate-css` is what shadcn installs by default, so a stylesheet declaring
2294
+ them again would give such an app two `.animate-in` rules with import order
2295
+ picking the winner, silently. The vendoring codemod renames them at emit, the
2296
+ lockfile records the table as `classRenames`, and a test fails on any class in
2297
+ this stylesheet that a third-party utility package could also own.
2298
+
2299
+ ## The island: one DOM, two value-sets
2300
+
2301
+ ```tsx
2302
+ import { AgentSurface, ToolCall } from "@cueplusplus/ui/elements";
2303
+
2304
+ <AgentSurface skin="aui" fidelity="upstream">
2305
+ <ToolCall
2306
+ label="Searched"
2307
+ activeLabel="Searching"
2308
+ query="composer"
2309
+ request={request}
2310
+ result={result}
2311
+ running
2312
+ open={open}
2313
+ onOpenChange={setOpen}
2314
+ />
2315
+ </AgentSurface>;
2316
+ ```
2317
+
2318
+ `AgentSurface` renders a plain `div` and stamps `data-cue-skin` and
2319
+ `data-cue-fidelity` on it. `elements.css` re-declares, under those attributes,
2320
+ the `--cue-*` tokens the family reads, and custom properties inherit — so the
2321
+ whole subtree repaints with no prop threading and no branch inside any
2322
+ component.
2323
+
2324
+ The two axes are independent, which is the switchable fidelity the spec asked
2325
+ for:
2326
+
2327
+ - **`skin`** — `cue` (the default) or `aui`, which installs assistant-ui's four
2328
+ shadcn variables and four literal hues as cue tokens, in light and dark.
2329
+ - **`fidelity`** — `cue-metrics` (the default) keeps cue's type scale and
2330
+ declares exactly one metric of its own:
2331
+ `--spacing: calc(0.25rem * var(--cue-density, 1))`. That is the lever that
2332
+ carries the density ladder into five hundred-odd vendored geometry utilities,
2333
+ none of which names a `--cue-space-*` token. `upstream` pins assistant-ui's
2334
+ radii, their 13.5/12/11px type and a flat `--spacing: 0.25rem` instead, so
2335
+ **density stops at the island edge** and the subtree keeps upstream's
2336
+ proportions whatever the console around it is set to.
2337
+
2338
+ It is not a theme. `data-theme`, `data-mode` and `data-density` are untouched,
2339
+ and a light app gets a light island.
2340
+
2341
+ Overlays follow. The portal stamp gains both axes, so a popover or dialog opened
2342
+ from inside an island — which mounts on `<body>`, far from the attributes —
2343
+ paints like the transcript it came from rather than like the page. Outside an
2344
+ island the two attributes are _absent_ rather than defaulted, so nothing that
2345
+ does not use this pays for it.
2346
+
2347
+ ## Where this code came from
2348
+
2349
+ Elements are not a package. `@assistant-ui/ui` is private at `0.0.0` and never
2350
+ published; the only channel is a shadcn registry whose index carries no version,
2351
+ no date and no hash, rebuilt from upstream `main` on every deploy. Copy-in is
2352
+ the intended use — so this release also ships the only thing that can answer
2353
+ "which Elements do we have".
2354
+
2355
+ `scripts/elements-sync/` is pinned to upstream commit
2356
+ `31a049fcfa846a76da7c8e2c0bcd62960825dbd4` (resolved 2026-08-22). It fetches
2357
+ all 96 `elements-*` registry items, verifies every allowlisted file against
2358
+ `raw.githubusercontent.com` at that pin, records every byte it read in
2359
+ `elements-sync.lock.json`, and runs a committed codemod that rewrites the fetched
2360
+ TSX before it lands: the ink ramp onto cue's four stops, the four literal
2361
+ Tailwind hues onto `stream`/`ok`/`danger`/`warn` with their alphas intact,
2362
+ literal pixel sizes onto the type scale, radii onto the three rungs, and every
2363
+ `dark:` variant resolved away because cue's mode axis is `[data-mode]` rather
2364
+ than `prefers-color-scheme`. Geometry is deliberately left alone: it all compiles
2365
+ through `--spacing`, which is the island's lever. Upstream's own 679 documented
2366
+ prop rows are merged into JSDoc on the way out, so the props table on each page
2367
+ is upstream's description of upstream's prop.
2368
+
2369
+ What that buys a consumer: the elements arrive as source this library owns and
2370
+ can fix, not as a dependency that can move underneath you; the "version" you have
2371
+ is a SHA you can read in `NOTICE.md` and diff against; and the licence obligation
2372
+ travels with the package. Upstream's MIT notice — Copyright (c) 2025 AgentbaseAI
2373
+ Inc. — is preserved verbatim in `packages/ui/src/elements/NOTICE.md` and named in
2374
+ the group's own documentation.
2375
+
2376
+ Two consequences worth stating plainly. **The directory is generated.** Every
2377
+ file in `src/elements/` except `surfaces.tsx` and `agent-surface.tsx` is output;
2378
+ a bug there is fixed by changing a codemod rule and re-running the sync, never by
2379
+ editing the file, because the next re-sync deletes a hand edit without telling
2380
+ anyone. And **six upstream export names are renamed at emit**, because docgen
2381
+ slugs are globally unique and the `Chat` family stays authoritative:
2382
+ `Composer → ElementsComposer`, `CommandPalette → ElementsCommandPalette`,
2383
+ `DataTable → ElementsDataTable`, `EmptyState → ChatEmptyState`,
2384
+ `Timeline → ElementsTimeline`, and `Source → CitationSource` (that one collides
2385
+ inside upstream's own family). None of the six is in this release — they land
2386
+ with the rest of the catalogue — but the table is generated by the sync, ships in
2387
+ the group barrel, and is the answer to "why is the import name not the one on
2388
+ assistant-ui's site".
2389
+
2390
+ ## What did not change
2391
+
2392
+ Outside the `cue` preset's four status values and its light `stream`, no colour
2393
+ moved. No component's
2394
+ props, structure or rendering changed, with two additions that are worth naming
2395
+ because they are additions rather than nothing: the portal stamp — both
2396
+ `useCuePortalProps` and `CuePortalFrame` — now carries `data-cue-skin` and
2397
+ `data-cue-fidelity` alongside the four axes it already carried, and both are
2398
+ _absent_ outside an island rather than defaulted; and the configurator's
2399
+ `TokenEditor` lists `stream` in its Status group, so a generated theme edits it
2400
+ where you would look for it.
2401
+
2402
+ The root barrel is untouched, so an app that never imports `/elements` gets no
2403
+ new peer, no new stylesheet and no new bytes, and `tw-shimmer` and
2404
+ `tw-animate-css` were reimplemented as CSS in this package rather than taken as
2405
+ dependencies. The `Chat` group is unchanged and stays authoritative on every
2406
+ name the two families both wanted.
2407
+
2408
+ Text is still text. Nothing in this group parses model output as markdown or
2409
+ HTML; the elements render what they are given, for the same reason `Message`
2410
+ does. Sanitized markdown is a documented, opt-in surface arriving later, with its
2411
+ own peer and its own entry.
2412
+
2413
+ `schemaVersion` is still `1`.
2414
+
2415
+ - 4e510e7: ## The red carries its own ink
2416
+
2417
+ `RevertDock`'s confirm is the one solid destructive fill in the elements family
2418
+ and the one control in it that does something irreversible. It painted
2419
+ `bg-danger/90 text-accent-fg`, and both halves of that were wrong.
2420
+
2421
+ `--cue-accent-fg` is the ink authored against `--cue-accent`. It has no
2422
+ relationship to `--cue-danger`, and on the ten reds this system ships it lands
2423
+ anywhere between **3.58:1** (venu, dark) and **6.67:1** (requestport, light).
2424
+ The `/90` then let the paper through and took roughly another 0.65 off the
2425
+ ratio. In cue's own light theme the label on that button measured **4.29:1** at
2426
+ the 12px this row is set in — under AA, on the press that cannot be undone.
2427
+ Inside an `<AgentSurface skin="aui">` in light mode it was **3.82:1**.
2428
+
2429
+ There is no existing token that fixes it. Every candidate the palette already
2430
+ owns fails somewhere: `accent-fg` fails terminal and venu in dark, `bg` fails
2431
+ terminal and hivehub in dark, the theme's own `fg` fails every light block. The
2432
+ ink a red can carry is a property of _that red_, not of the mode — so the red
2433
+ now has one.
2434
+
2435
+ ### `--cue-danger-fg`
2436
+
2437
+ A new authored anchor, per preset, per mode, in the colour contract beside the
2438
+ tone it is chosen against:
2439
+
2440
+ ```css
2441
+ [data-theme="cue"] {
2442
+ --cue-danger: #ff5f57;
2443
+ --cue-danger-fg: #000000;
2444
+ }
2445
+ [data-theme="cue"][data-mode="light"] {
2446
+ --cue-danger: #d03030;
2447
+ --cue-danger-fg: #fcfcfc;
2448
+ }
2449
+ ```
2450
+
2451
+ with `text-danger-fg` / `bg-danger-fg` bridged into Tailwind like every other
2452
+ colour token. `ok`, `busy`, `info` and `stream` have no such ink, and that is
2453
+ deliberate: they are read as signals — a dot, a rim, a 12% wash — and the text
2454
+ near them sits on a surface. The red is a tone this system fills a control with,
2455
+ which is what makes text on it text. (The amber turns out to be the other one —
2456
+ _The amber carries its own ink too_, later in this same release.)
2457
+
2458
+ Each preset's value is the extreme it already owns, measured: every light block
2459
+ takes its paper, seven dark blocks take their deepest ground, and the two whose
2460
+ red sits in the middle of the range (terminal, hivehub) take white, because
2461
+ nothing else reaches AA on it. The worst pair in the system is now **4.59:1**;
2462
+ it was **3.58:1**.
2463
+
2464
+ `createTheme()` derives the same token for a generated theme the way it derives
2465
+ `accent-fg` — best of the theme's ground, its deepest well and its ink, with
2466
+ pure black or white only when none of the three can be read — and
2467
+ `CONTRAST_REQUIREMENTS` gained a tenth pair, `danger-fg/danger` at 4.5:1, so a
2468
+ generated palette is judged on it too. The configurator shows it next to the red
2469
+ in the Status group.
2470
+
2471
+ A sweep in `@cueplusplus/ui`'s own suite now measures the shipped pairing across
2472
+ all ten presets in both modes and fails under 4.5:1, and a second one measures
2473
+ it inside the assistant-ui island, where the two halves legitimately disagree
2474
+ about which ink their red wants.
2475
+
2476
+ ### If you ship your own theme block
2477
+
2478
+ A hand-written `[data-theme="acme"]` block should declare `--cue-danger-fg`
2479
+ alongside `--cue-danger`. Without it, `text-danger-fg` resolves to nothing on
2480
+ your theme. Themes generated by `createTheme()` and the configurator's exports
2481
+ already carry it.
2482
+
2483
+ - b9ab713: ## The rest of the family
2484
+
2485
+ P0 shipped fifteen elements and the machinery that makes more of them. This
2486
+ release finishes the catalogue. **All 96 of assistant-ui's `elements-*` registry
2487
+ items now emit** — 94 elements, the eighteen shared surface recipes, and
2488
+ `range`'s six functions — through the same pinned sync, the same codemod and the
2489
+ same island. `@cueplusplus/ui/elements` goes from 23 exported components to
2490
+ **132**, plus two hooks, and is now the largest group in the library.
2491
+
2492
+ None of it was typed. The 79 new elements arrived the way the first fifteen did:
2493
+ `scripts/elements-sync/` fetches the registry at upstream
2494
+ `31a049fcfa846a76da7c8e2c0bcd62960825dbd4`, verifies every file against
2495
+ `raw.githubusercontent.com` at that pin, hashes what it read into
2496
+ `elements-sync.lock.json`, and runs a committed codemod that rewrites the fetched
2497
+ TSX before it lands. Adding an element is a line in an allowlist plus whatever
2498
+ rule its source needed; the emitted files are never edited by hand, because the
2499
+ next re-sync would delete the edit without telling anyone.
2500
+
2501
+ What that means for a consumer is the sentence the whole phase exists for: **the
2502
+ entire assistant-ui Elements catalogue is now cue-owned source.** Not a
2503
+ dependency that can move underneath you — there is no package to depend on;
2504
+ `@assistant-ui/ui` is private at `0.0.0` and the registry carries no version
2505
+ field. Every one of the 94 is painted by `--cue-*` tokens and answers to
2506
+ `data-theme`, `data-mode` and `data-density` like everything else here, and every
2507
+ one of them re-skins to assistant-ui's own values inside
2508
+ `<AgentSurface skin="aui">` without a prop being threaded anywhere.
2509
+
2510
+ | what landed | elements | what it covers |
2511
+ | ----------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
2512
+ | thread & reasoning | 20 | reasoning revealed a beat at a time, text arriving a token at a time, and everything that hangs off one turn — actions, attachments, branches, timing, the queue, editing, quoting, regenerating, reading aloud |
2513
+ | knowledge & structured output | 21 | where an answer came from and how far to trust it — sources, inline citations, retrieved passages, the document it quotes, a confidence marker — and what it arrives as when prose is not enough: tables, charts, diagrams, spec sheets, maps, maths, canvases |
2514
+ | agents & observability | 18 | who is doing the work and what it costs — agent cards, handoffs, background runs, checkpoints, job progress, spend, context, quota — plus a session's failure modes and the four drawings it is legible in afterwards |
2515
+ | composer, thread list & shell | 20 | the application around the conversation — the composer and its twenty-one facets, the palette, the model picker, the prompt library, the thread list and its searches, settings, onboarding, the blank screen, the launcher bubble, the call |
2516
+
2517
+ Six upstream names are renamed at emit, because docgen slugs are globally unique
2518
+ and the `Chat` family stays authoritative: `Composer → ElementsComposer`,
2519
+ `CommandPalette → ElementsCommandPalette`, `DataTable → ElementsDataTable`,
2520
+ `EmptyState → ChatEmptyState`, `Timeline → ElementsTimeline`, and
2521
+ `Source → CitationSource`, which collides inside upstream's own family. The table
2522
+ is generated by the sync, ships in the group barrel's JSDoc, and is the answer to
2523
+ "why is the import name not the one on assistant-ui's site".
2524
+
2525
+ `composer` is the family's one compound: twenty-one exports out of a single file,
2526
+ all of them published, because upstream's own examples compose the facets rather
2527
+ than configuring a root — and a composer exported without `useSlashMatches` and
2528
+ `useMentionMatches` is a composer whose two menus nobody can open.
2529
+
2530
+ ## Four new kitchen-sink sections
2531
+
2532
+ The corpus is split the way the batches were, not lumped into one page:
2533
+ `elements-thread`, `elements-knowledge`, `elements-agents` and
2534
+ `elements-composer` join `elements` on `/kitchen-sink`, each with its own
2535
+ heading and its own axe case. One section holding a hundred and thirty benches
2536
+ is one unreadable file and one slow accessibility run; five sections are five of
2537
+ each. Every element is benched at its real states — a streaming element shows
2538
+ streaming _and_ settled, an error element shows the error — which is what makes
2539
+ the props table on each component page evidence rather than a claim.
2540
+
2541
+ ## `heat-graph`, a second optional peer
2542
+
2543
+ `ActivityGraph` draws a calendar of runs, and it draws it with `heat-graph` —
2544
+ the headless grid assistant-ui publishes beside the elements (MIT, same author).
2545
+ It ships as an **optional peer** at `^0.0.15`, the shape `recharts` and
2546
+ `@xyflow/react` already have: declared in `peerDependencies`, marked optional,
2547
+ externalised by the bundler, named in this entry's expected-peer list, and
2548
+ installed here only as a devDependency. One component of 132 reaches it, so an
2549
+ app that never renders a heat calendar installs nothing for it and sees no
2550
+ resolution error.
2551
+
2552
+ **This is a deliberate departure from the spec.** §B of
2553
+ `docs/specs/2026-08-22-agent-elements.md` says `heat-graph` is "vendored as a
2554
+ helper, not a peer". That line was written before the pipeline existed. Vendoring
2555
+ it now would mean hand-writing eight React components into a directory whose
2556
+ entire contract is that every file in it is generated — a hand-written tree
2557
+ inside a generated one, invisible to the lockfile, unreachable by the codemod,
2558
+ and the first thing a re-sync would trip over. The plan's decision 3, which asks
2559
+ for a peer with its four coordinated edits, is what was followed instead. The
2560
+ reason is recorded in `rules.mjs` beside the batch it belongs to, so the next
2561
+ person to read the spec finds the disagreement where the code is.
2562
+
2563
+ ## Two elements carry cue runtime, and that is the point
2564
+
2565
+ Two of the 96 needed more than a class rewrite, and both were held back through
2566
+ their own batch for a decision rather than improvised inside one. The decision:
2567
+ **the codemod may write cue's own code into a vendored file.**
2568
+
2569
+ - **`InlineCitation`** is the family's one overlay — a Base UI preview card
2570
+ behind each numbered marker — and its `Portal` now spreads
2571
+ `useCuePortalProps()`. A portaled subtree mounts on `<body>`, where the token
2572
+ layer resolves the document's values instead of the island's, so without the
2573
+ stamp a hover card opened inside an `<AgentSurface skin="aui">` would paint in
2574
+ your console's palette while the sentence behind it painted in assistant-ui's.
2575
+ That is precisely the bug P0 gave `PortalStamp` its `skin` / `fidelity`
2576
+ carriage to prevent. `@base-ui/react` was already a required peer, so this
2577
+ costs nothing new to install.
2578
+ - **`ScrollAnchor`** is the one element in all 96 that drives its own clock — a
2579
+ `setInterval` appending a turn every 1.3s — and the one whose viewport scrolls
2580
+ itself. Its effect now returns early under `usePrefersReducedMotion()`, after
2581
+ painting the still frame: the _finished_ transcript, pinned to the bottom,
2582
+ rather than a scene frozen three messages in. Its two imperative smooth
2583
+ scrolls lost their `behavior: "smooth"` option, which no browser will still for
2584
+ `prefers-reduced-motion`; the element already carries `scroll-smooth` on the
2585
+ same viewport, so the scroll stays smooth from CSS and one
2586
+ `motion-reduce:scroll-auto` twin now turns all three of them off.
2587
+
2588
+ Read as a leak, four lines of cue runtime inside vendored source look like the
2589
+ vendoring failing. They are the vendoring working. These files are cue-owned
2590
+ source — that is the whole premise, since Elements ship as copy-in behind a
2591
+ registry with no version — so `packages/ui/src/elements/` is this repository's
2592
+ code, generated rather than typed. The codemod already rewrites every class in it
2593
+ onto cue's tokens; making a vendored element obey cue's _contracts_ is the same
2594
+ act performed one layer up. An element that portals without the island stamp, or
2595
+ re-arms a callback without asking whether the reader wants motion, is broken in
2596
+ this system's terms whatever it does upstream.
2597
+
2598
+ And it is a rule, never a hand edit. `RUNTIME_INJECTIONS` in `rules.mjs` names
2599
+ the file, the hook, the module, the binding, the component and the exact lines
2600
+ that change, each with its reason; the codemod throws on every anchor it cannot
2601
+ find, `sync.test.mjs` fails on an injection that stopped reaching the emitted
2602
+ tree, and both files carry the reasoning in a comment where a reader meets the
2603
+ import.
2604
+
2605
+ ## Six text inputs get a focus ring back
2606
+
2607
+ `ElementsComposer`'s input, `ElementsCommandPalette`'s, `PromptLibrary`'s,
2608
+ `ThreadSearch`'s, `ConversationSearch`'s and `MobileComposer`'s are bare
2609
+ `<input>`s whose entire upstream class string is a placeholder colour, a size,
2610
+ `bg-transparent` and `outline-none`. They removed the browser's focus outline and
2611
+ put nothing in its place — a keyboard user tabbing into a command palette had no
2612
+ idea they had arrived. It is not a ring the codemod dropped; upstream never wrote
2613
+ one.
2614
+
2615
+ The rewrite restores cue's ring only where something in the class string says the
2616
+ element is focusable, and its signal was interaction-state variants and
2617
+ `group`/`peer` labels — none of which these six carry. `::placeholder` and
2618
+ `caret-color` apply to `input` and `textarea` and to nothing else in CSS, so
2619
+ either one is proof the element takes a caret, and an element that takes a caret
2620
+ is focusable. Both now count, and the six come back with the same
2621
+ `focus-visible:outline-2 / solid / offset-1 / accent` recipe every other control
2622
+ in this package uses.
2623
+
2624
+ **This is a WCAG 2.4.7 (Focus Visible, AA) fix you inherit by upgrading.** No
2625
+ prop moved and nothing needs editing; if you render any of those six, a visible
2626
+ focus indicator appears where there was none. The fix is in `rules.mjs`, so a
2627
+ re-sync reproduces it byte for byte rather than losing it.
2628
+
2629
+ ## `llms.txt` is an index again
2630
+
2631
+ The machine surfaces on `ui.cueplusplus.com` re-tier, because one of them was a
2632
+ release away from failing its own cap: `/llms.txt` was 386 lines against a
2633
+ ceiling of 400, and 308 of those lines were a component's name, its URL and a
2634
+ one-liner. What grows that file is the catalogue, and the catalogue is what the
2635
+ library is for, so raising the cap would have bought exactly one release.
2636
+
2637
+ `/llms.txt` now lists **groups** — nineteen lines, each with the group's page,
2638
+ its component and hook counts, the specifier it imports from and what it is for —
2639
+ and points at the sheet for everything inside them. 61 non-blank lines, from 386,
2640
+ with the same facts still coming off the manifest.
2641
+
2642
+ Nothing lost information. `/llms-components.txt` was already exhaustive and stays
2643
+ exhaustive: one line per component with its full props signature and its import
2644
+ specifier, one per hook with its call signature, and a header stating the rule for
2645
+ reaching any component's page
2646
+ (`https://ui.cueplusplus.com/docs/components/<slug>.md`). The index stopped
2647
+ duplicating the sheet, which is the only thing it was doing with 80% of its lines.
2648
+
2649
+ If you point an agent at these files, point it at both, in this order: `/llms.txt`
2650
+ first — what the library is, the rules a props table cannot show, and which group
2651
+ a thing lives in — then `/llms-components.txt` before it writes a call, for the
2652
+ specifier and the full props literal. `/llms-full.txt` and `/kitchen-sink.md` are
2653
+ unchanged.
2654
+
2655
+ ## What did not change
2656
+
2657
+ No token moved. `@cueplusplus/tokens` is untouched by this release — the colour
2658
+ contract is the same 42 custom properties the last one shipped — and no existing
2659
+ component's props, structure or rendering changed. The root barrel is still
2660
+ untouched, so an app that never imports `/elements` gets no new peer, no new
2661
+ stylesheet and no new bytes; `heat-graph` and `lucide-react` are both optional
2662
+ and both reached only from this subpath.
2663
+
2664
+ `@cueplusplus/ui/elements.css` gains exactly one declaration — a
2665
+ `cue-elements-zoom-in-95` entrance class, namespaced like the other nine so that
2666
+ an app which also installs `tw-animate-css` never has two stylesheets quietly
2667
+ arguing over `.animate-in` by import order. It remains optional even for this
2668
+ subpath: skip it and the elements still render in cue's palette, you lose the
2669
+ motion and `skin="aui"` becomes a no-op.
2670
+
2671
+ Text is still text. Nothing in this group parses model output as markdown or
2672
+ HTML — 132 components and not one of them decides your markup for you. Sanitized
2673
+ markdown remains a documented, opt-in surface arriving later, with its own peer
2674
+ and its own entry.
2675
+
2676
+ `schemaVersion` is still `1`.
2677
+
2678
+ - 854ad96: ## A streaming layer under the Chat group
2679
+
2680
+ The Chat group renders a turn that has finished arriving. It has never had a
2681
+ story for one that is _still_ arriving: no token assembly, no cancelling a run
2682
+ mid-sentence, no regenerating a turn beside the one it replaces, and nowhere to
2683
+ put the model's own tool call while the rest of the answer is still coming.
2684
+
2685
+ `@cueplusplus/ui/agent-runtime` is that story, and it is one hook wide.
2686
+ `useAgentThread(runtime)` takes any `@assistant-ui/react` runtime — the one you
2687
+ build with `useExternalStoreRuntime(adapter)`, or the AI-SDK or LangGraph ones —
2688
+ and gives back the props `Message`, `MessageList` and `Composer` already take.
2689
+ Every field is named for the prop it feeds: `busy`, `disabled`, `onSend` and
2690
+ `onStop` to `Composer`; `role`, `agent`, `pending` and `text` to `Message`;
2691
+ `toolCalls[]` onto `ToolCallCard`; `branch` and `switchBranch` to
2692
+ `BranchPicker`.
2693
+
2694
+ Two components ship in the entry with it. **`ToolCallCard`** is the model's own
2695
+ call: the tool name in mono, an optional risk chip in `AskBox`'s exact
2696
+ vocabulary, the status spelled in words beside a spinner rather than in colour
2697
+ alone, and the arguments and result behind a disclosure that mounts and unmounts
2698
+ instead of animating a height. It is neither of its neighbours — `AskBox` is a
2699
+ question put to a human, `DelegationCard` is a sub-agent's work, and a tool call
2700
+ is the model's own action. **`BranchPicker`** is a `‹ 2 / 3 ›` stepper for an
2701
+ agent turn's footer: two ghost icon buttons around a mono readout that reads
2702
+ itself aloud as "branch 2 of 3", clamped so it can never point past either end,
2703
+ and drawing nothing at all while there is only one branch.
2704
+
2705
+ `@assistant-ui/react@^0.15.16` is an **optional** peer, declared and
2706
+ externalised the way `@xyflow/react` is under `/flow`. Import nothing from
2707
+ `/agent-runtime` and it costs you nothing: no install, no bundle, no resolution
2708
+ error — the root barrel's import graph never reaches it, and neither does
2709
+ `/chat`'s. Import the entry and you do need it. Nothing in the shipped modules
2710
+ imports `@assistant-ui/react` at runtime — the hook's own file names only its
2711
+ types — but you cannot build the `AssistantRuntime` the hook takes without it,
2712
+ so install it. Installing it also installs its own dependencies — `radix-ui`
2713
+ and several `@radix-ui/*` parts, `zustand`, `zod`, `assistant-stream`,
2714
+ `@assistant-ui/core` — about 16 MB on disk; the subpath boundary keeps all of
2715
+ it out of an app that never imports the entry.
2716
+
2717
+ ```tsx
2718
+ import { Composer, Message, MessageList } from "@cueplusplus/ui";
2719
+ import {
2720
+ BranchPicker,
2721
+ ToolCallCard,
2722
+ useAgentThread,
2723
+ } from "@cueplusplus/ui/agent-runtime";
2724
+ import {
2725
+ useExternalStoreRuntime,
2726
+ type ThreadMessageLike,
2727
+ } from "@assistant-ui/react";
2728
+
2729
+ // Outside the component: the runtime caches the conversion against this
2730
+ // function, and a new one every render is an update loop.
2731
+ const convertMessage = (m: ThreadMessageLike) => m;
2732
+
2733
+ const runtime = useExternalStoreRuntime({
2734
+ messages,
2735
+ isRunning,
2736
+ convertMessage,
2737
+ onNew,
2738
+ onCancel,
2739
+ onReload,
2740
+ setMessages,
2741
+ });
2742
+ const thread = useAgentThread(runtime, { agent: "planner" });
2743
+
2744
+ <MessageList aria-label="Thread">
2745
+ {thread.turns.map((t) => (
2746
+ <Message
2747
+ key={t.id}
2748
+ role={t.role}
2749
+ agent={t.agent}
2750
+ pending={t.pending}
2751
+ footer={
2752
+ t.branch.count > 1 && thread.switchBranch ? (
2753
+ <BranchPicker
2754
+ index={t.branch.index}
2755
+ count={t.branch.count}
2756
+ onNavigate={(d) => thread.switchBranch?.(t.id, d)}
2757
+ disabled={thread.busy}
2758
+ />
2759
+ ) : undefined
2760
+ }
2761
+ >
2762
+ {t.text}
2763
+ {t.toolCalls.map((c) => (
2764
+ <ToolCallCard
2765
+ key={c.id}
2766
+ tool={c.tool}
2767
+ args={c.args}
2768
+ result={c.result}
2769
+ status={c.status}
2770
+ />
2771
+ ))}
2772
+ </Message>
2773
+ ))}
2774
+ </MessageList>;
2775
+ <Composer
2776
+ onSend={thread.onSend}
2777
+ busy={thread.busy}
2778
+ disabled={thread.disabled || thread.busy}
2779
+ onStop={thread.onStop}
2780
+ />;
2781
+ ```
2782
+
2783
+ ## What did not change
2784
+
2785
+ The six Chat components — `AgentPile`, `AskBox`, `Composer`, `DelegationCard`,
2786
+ `Message`, `MessageList` — render what they rendered before, with the one
2787
+ exception below. Nothing is restyled and no prop moved.
2788
+
2789
+ Text is still text. `ToolCallCard` renders arguments and results verbatim, never
2790
+ as markdown and never as HTML, for the same reason `Message` does: an argument
2791
+ string is whatever a model decided to emit, and the moment a component parses it
2792
+ the model chooses your markup. The hook joins text parts into strings rather
2793
+ than handing you a node tree to trust. No markdown package is added, opt-in or
2794
+ otherwise.
2795
+
2796
+ Nothing of assistant-ui's rendering is adopted. The hook is a
2797
+ `useSyncExternalStore` over the runtime's thread and nothing else — no
2798
+ `AssistantRuntimeProvider` anywhere in your tree, no primitive mounted, no Radix
2799
+ DOM, no borrowed context. Its `asChild` composition model is not adapted and not
2800
+ exposed: this library's public API is still 100% `render`-prop. assistant-ui
2801
+ owns the state machine, this library owns 100% of the render.
2802
+
2803
+ ## The one fix inside Chat
2804
+
2805
+ **`MessageList` now follows a turn that is still growing.** Its re-pin ran on the
2806
+ number of children changing, so a _new_ turn kept the view at the bottom but
2807
+ tokens streaming into a bubble that was already there did not — the list quietly
2808
+ stopped following mid-sentence, exactly when following matters most. It now also
2809
+ watches the items container and re-pins **when that container grows**, while
2810
+ following and pinned.
2811
+
2812
+ Only growth moves the view. A tool-call card the reader folds away, an image
2813
+ that failed to load, a footer that disappears — anything that makes the list
2814
+ shorter leaves the scroll position exactly where it was. Scrolling up still lets
2815
+ go, and a turn that grows while you are up there offers the "jump to latest"
2816
+ pill rather than pulling you down. `follow={false}` switches the whole mode off,
2817
+ growth included.
2818
+
2819
+ This is new behaviour on a component you already ship, so it is worth a moment
2820
+ if you have a `MessageList` whose children change height in place: it will now
2821
+ stay at the bottom where before it drifted up. There is no new prop and no API
2822
+ change, and an engine without `ResizeObserver` behaves exactly as it did.
2823
+
2824
+ ## Things worth knowing
2825
+
2826
+ **A branch switch is host-owned under an ExternalStore runtime.** `switchBranch`
2827
+ does not move the thread itself. It arrives at your adapter's `setMessages` with
2828
+ the newly visible list, and your host writes it back into whatever holds the
2829
+ messages. A host that ignores `setMessages` gets a picker that clicks and does
2830
+ nothing — which looks like a bug in the picker and is not one.
2831
+
2832
+ **`onReload` takes two arguments**, `(parentId, config)`. Regenerating re-runs
2833
+ the turn's _parent_, which is what puts the new answer beside the old one rather
2834
+ than after it — and is what gives `BranchPicker` a second branch to show at all.
2835
+ The picker only moves between branches that already exist, so the control that
2836
+ asks for a new one is yours to draw: a ghost `Button` in the turn's footer
2837
+ calling `regenerate(turn.id)`, as the docs demo does.
2838
+
2839
+ **Pass `convertMessage`, and declare it outside the component.** Without it your
2840
+ messages have to already be complete runtime messages, and a missing `metadata`
2841
+ throws; `convertMessage: (m) => m` lets the runtime fill the defaults in around
2842
+ the messages you already have. Give it a stable identity, though: the runtime
2843
+ caches the conversion against that function, so an arrow written inline in the
2844
+ adapter re-converts the whole thread on every render — and the next time your
2845
+ message list changes, that becomes React's "Maximum update depth exceeded"
2846
+ rather than a slow render.
2847
+
2848
+ **`onStop`, `regenerate` and `switchBranch` are `undefined`** until the adapter
2849
+ has the matching callback — `onCancel`, `onReload`, `setMessages`. That is
2850
+ deliberate rather than defensive: `Composer` keeps drawing Send while `onStop`
2851
+ is missing, so a control that could not work is never drawn.
2852
+
2853
+ **The peer range is pinned on purpose.** `^0.15.16`, not `^0.15.0`: 0.15.9
2854
+ through 0.15.16 shipped inside twelve days. What this entry reads are the stable
2855
+ members of the runtime object — `thread.getState()`, `subscribe`, `append`,
2856
+ `cancelRun`, `getMessageById`, `switchToBranch` — and never an `unstable_*` one,
2857
+ but a floor that recent is worth saying out loud rather than discovering.
2858
+
2859
+ The live thread, the four tool-call states and the branch stepper are on the
2860
+ docs site under **Agent runtime**, at `/docs/components/agent-runtime`.
2861
+
2862
+ ### Patch Changes
2863
+
2864
+ - e5fc161: ## Everywhere the red is spent
2865
+
2866
+ `--cue-danger-fg` arrived with `RevertDock`'s confirm and stopped there. The two
2867
+ destructive controls the rest of the library is actually built out of never got
2868
+ it, so the token shipped alongside the defect it was authored to close.
2869
+
2870
+ Three fills change, and all three are contrast fixes rather than restyles:
2871
+
2872
+ - **`Button` `variant="danger"`** — `bg-danger text-white` becomes
2873
+ `bg-danger text-danger-fg`.
2874
+ - **`TwoStepButton`'s armed `danger` confirm** — the same swap, on the control
2875
+ that exists because the action is irreversible.
2876
+ - **`VoiceConversation`'s end-call button** — `bg-danger/90 text-accent-fg`
2877
+ becomes `bg-danger text-danger-fg`. That element is vendored from
2878
+ assistant-ui, so the fix is a rule in `scripts/elements-sync/rules.mjs` and
2879
+ the file is re-emitted by the sync; a hand edit would have lasted until the
2880
+ next SHA bump.
2881
+
2882
+ `text-white` on the red measures **2.99:1** on cue's dark block — the default
2883
+ theme, under AA text and under the 3:1 floor a non-text control gets — and it is
2884
+ under AA on eight of the ten dark blocks this system ships. `text-danger-fg`
2885
+ measures **7.03:1** in the same place and clears 4.5:1 on all twenty
2886
+ preset x mode blocks. `text-accent-fg` is the ink authored against
2887
+ `--cue-accent`, and on the red it lands anywhere from 3.58:1 to 6.67:1; the
2888
+ `/90` then let the surface through and took the pair down with it.
2889
+
2890
+ Nothing you pass changes. A `danger` button, an armed confirm and an end-call
2891
+ button are the same elements with the same props; the label on them is legible
2892
+ now on every theme in both modes.
2893
+
2894
+ ### The reason all three stayed green
2895
+
2896
+ The sweep that arrived with the token measures **the token pair** — twenty
2897
+ blocks of `danger-fg` on `danger` — and a token pair is only the answer for a
2898
+ control that asks for it. These three asked for something else and sat outside
2899
+ the measurement.
2900
+
2901
+ `test/destructive-ink.test.ts` reads the source instead. It finds every class
2902
+ string in the package that makes the red a ground and puts an ink on it, takes
2903
+ the ink out of that string, and measures it on that theme's red across all
2904
+ twenty blocks at 4.5:1. It refuses an alpha on the fill, because
2905
+ `--cue-danger-fg` on `bg-danger/90` falls to 3.94:1 on quotamate's dark over
2906
+ `--cue-sunken` and 3.97:1 over `--cue-bg` — and that the two grounds disagree at
2907
+ all is why the alpha is refused rather than measured. It fails on an ink whose
2908
+ colour it cannot read rather than skipping it. The list of fills is pinned by
2909
+ name: a new destructive fill has to choose its ink in the commit that paints
2910
+ it.
2911
+
2912
+ `TwoStepButton`'s `warn` confirm keeps the `text-black` literal through this
2913
+ change, and its JSDoc stops claiming more than it can. At this point there was
2914
+ no `--cue-warn-fg` to give it, that pairing floored at 3.58:1 on requestport's
2915
+ light block, and the sweep said in writing that the amber was the one it did not
2916
+ cover. _The amber carries its own ink too_, in this same release, is where that
2917
+ gets closed.
2918
+
2919
+ - 58eb41a: ## The red the sheets draw
2920
+
2921
+ The generative vocabulary fills a control with the red too. `GenerativeUI`
2922
+ renders `[data-aui="button"][data-aui-style="danger"]` from a model's spec, and
2923
+ that button painted `--aui-bg` — the page under the widget — on a solid
2924
+ `--aui-danger`. It reads **4.03:1** on terminal's dark block and **4.12:1** on
2925
+ hivehub's, both under AA, and **3.82:1** on the `aui` skin's light half, which
2926
+ is the number `elements.css` already writes down beside `--cue-danger-fg` as
2927
+ the reason that token exists.
2928
+
2929
+ It takes `--aui-danger-fg` now, one more line on the seam
2930
+ (`var(--cue-danger-fg, #000000)`, a literal fallback like every other reference
2931
+ in that block), and it clears 4.5:1 on all twenty shipped preset x mode blocks
2932
+ and on both halves of the island. Nothing a spec can say changes: a `danger`
2933
+ button is the same intrinsic with the same props.
2934
+
2935
+ ### Why the sweep beside it did not see this one
2936
+
2937
+ `test/destructive-ink.test.ts` reads class strings, and this sheet has none.
2938
+ `generative.css` is plain `[data-aui]` CSS on purpose — no Tailwind, no utility
2939
+ to grep — so a fill drawn there sat outside a guard whose own name said it
2940
+ found every one.
2941
+
2942
+ It reads the package's stylesheets too now. For every rule that makes
2943
+ `--cue-danger` a whole-value ground and puts a `color` on it, it resolves both
2944
+ through the `--aui-*` seam and measures the pair on the same twenty blocks at
2945
+ the same 4.5:1, refuses an ink it cannot resolve to a theme colour rather than
2946
+ skipping it, and pins the rules it found by selector. A `color-mix()` does not
2947
+ resolve, which is the same exemption the class sweep gives `bg-danger/10`.
2948
+
2949
+ One hole in the class half closed with it: `text-[#7f1d1d]` was read as "not a
2950
+ colour" and took its whole call site out of the corpus in silence. Only lengths
2951
+ are excluded now — the six this package actually writes — so an arbitrary ink
2952
+ on the red fails by name and asks to be taught.
2953
+
2954
+ - Updated dependencies [51e5046]
2955
+ - Updated dependencies [06eb4ac]
2956
+ - Updated dependencies [4e510e7]
2957
+ - @cueplusplus/tokens@0.5.0
2958
+
2959
+ ## 0.4.0
2960
+
2961
+ ### Minor Changes
2962
+
2963
+ - e558992: Ten container icons, for the navigation column that just learned to rail
2964
+
2965
+ A navigation column is a list of places, and a place is a word until the column
2966
+ narrows. `AppShell` can rail now — the column drops to `3.5rem` and every row
2967
+ keeps its glyph and loses its name — which turns "does this row have an icon"
2968
+ from a styling question into whether the row still exists. This library had a
2969
+ public component for the marks (`CueMark`, `CueLogotype`, `Plussie`) and nine
2970
+ private `_glyphs` modules for its own chevrons and carets, and nothing at all
2971
+ for the things a catalogue actually sorts into.
2972
+
2973
+ ```tsx
2974
+ import { CatalogueIcon, ChannelBetaIcon, SkillIcon } from "@cueplusplus/ui";
2975
+
2976
+ <Sidebar.Item icon={CatalogueIcon}>All skills</Sidebar.Item>
2977
+ <Sidebar.Item icon={ChannelBetaIcon}>Our skills · beta</Sidebar.Item>
2978
+ ```
2979
+
2980
+ | Icon | The place it marks |
2981
+ | --------------------- | ------------------------------------------------------ |
2982
+ | `CatalogueIcon` | everything there is, before anything has been narrowed |
2983
+ | `StackIcon` | one person's stack |
2984
+ | `StacksMatrixIcon` | every stack at once, one column each |
2985
+ | `AudienceIcon` | a named group of people something is published to |
2986
+ | `ChannelReleasedIcon` | the released channel |
2987
+ | `ChannelBetaIcon` | the beta channel |
2988
+ | `FolderIcon` | a shelf somebody filed by hand |
2989
+ | `McpServerIcon` | an MCP server in the registry |
2990
+ | `CliToolIcon` | a CLI tool in the registry |
2991
+ | `SkillIcon` | a skill — the thing all the others hold |
2992
+
2993
+ ## It is a set, which is a stronger claim than ten icons
2994
+
2995
+ They are read in one column, one after another, so a grid or a weight that
2996
+ drifts on any one of them is read as a mistake on _that row_ rather than as a
2997
+ style. Every one is drawn from a single shared chassis and the suite holds all
2998
+ three of its rules: one grid (`0 0 16 16` at `stroke-width` 1.5), no size of
2999
+ its own, no colour of its own. There is also a test that no two of them draw
3000
+ the same thing, because a set that ships one mark twice has a hole in it where
3001
+ a reader will look for a distinction.
3002
+
3003
+ Two pairs carry the weight of that. `ChannelReleasedIcon` and `ChannelBetaIcon`
3004
+ are **one ring told twice** — the same `r="5.5"` circle, filled solid and
3005
+ filled halfway — so nobody has to learn which of two unrelated pictures means
3006
+ "finished". And `StackIcon` is plates seen edge-on while `StacksMatrixIcon` is
3007
+ a table with a head rule and a name column, because the question the matrix
3008
+ answers has two axes and the question a stack answers has one.
3009
+
3010
+ ## Sized and inked by the caller, on purpose
3011
+
3012
+ No `size` prop, no `tone` prop. `className="size-icon-md"` reads
3013
+ `--cue-icon-md`, so a glyph tracks the density island it is in — an icon that
3014
+ measured itself would be the one thing in a compact subtree that did not move.
3015
+ Ink is `currentColor` throughout, so a glyph in a muted row is muted and the
3016
+ same glyph in the current row is not, with nothing to pass down.
3017
+
3018
+ They are decorative by default — `aria-hidden` — because they mark a row that
3019
+ already carries its name, and a second announcement of the same word is noise.
3020
+ Props spread last, so `aria-hidden={false} role="img"` with a `<title>` turns
3021
+ one back into a picture where a glyph really is the only name for something.
3022
+
3023
+ ## Why 16 and not 24
3024
+
3025
+ `lucide-react` draws on a 24 grid, and it remains the answer for everything
3026
+ outside this vocabulary — it is still an optional peer, every `icon` slot here
3027
+ still takes a component, and the two mix freely in one column. But `1.5` on a
3028
+ 24 grid renders at two-thirds the weight of `1.5` on a 16 grid in the same box,
3029
+ and every glyph this library already draws is on 16. Matching lucide's number
3030
+ would have meant not matching its own line, and the line is the thing a reader
3031
+ sees. An app that wants these to sit at exactly lucide's weight passes
3032
+ `strokeWidth` — it overrides, like every other attribute.
3033
+
3034
+ ## Ten exports rather than one `Icons` object
3035
+
3036
+ Deliberate, and it costs ten manifest entries instead of one with ten parts. A
3037
+ frozen namespace object does not tree-shake per member: a bundler cannot drop
3038
+ an unused property of an exported object literal, so a console that used two of
3039
+ these would have shipped all ten. Named exports of one module shake to exactly
3040
+ what is imported.
3041
+
3042
+ They ship in the root barrel with no subpath of their own, on the same ruling
3043
+ `brand/` records: the group pulls no optional peer and weighs two files.
3044
+ **Nothing here changes an existing component.** `packages/ui/manifest/**` is
3045
+ regenerated, not hand-edited.
3046
+
3047
+ - 85448d8: ## Three hairlines, derived from the foreground
3048
+
3049
+ A list is not a stack of boxes, and until now the token vocabulary had no way to
3050
+ say the difference.
3051
+
3052
+ `--cue-border` is an authored anchor: every preset picks one, and what every
3053
+ preset picks is a **box edge** — the line around a card, an input, a panel. It is
3054
+ the right weight for exactly that. Repeat it down thirty rows of a table and the
3055
+ page stops reading as a list and starts reading as thirty boxes stacked on each
3056
+ other. The line _between_ things wants to be lighter than the line _around_ a
3057
+ thing, and there was no token for it, so every surface that needed one either
3058
+ borrowed `--cue-border` and looked heavy, or invented a `color-mix()` of its own
3059
+ and stopped being themeable.
3060
+
3061
+ Three new tokens, in every preset, in both modes:
3062
+
3063
+ | Token | Value | What it is |
3064
+ | ------------------- | ------------------------------------------------------ | --------------------------------------------------------------------------------------------- |
3065
+ | `--cue-hair` | `color-mix(in oklch, var(--cue-fg) 6%, transparent)` | **Hairline rule** — between rows, between sections, under a caption |
3066
+ | `--cue-hair-strong` | `color-mix(in oklch, var(--cue-fg) 12%, transparent)` | **Strong hairline** — the same line where it carries weight: a table head, a quiet tag's edge |
3067
+ | `--cue-row-hover` | `color-mix(in oklch, var(--cue-fg) 3.5%, transparent)` | **Row hover wash** — the faintest fill that still reads as one row picked out of a list |
3068
+
3069
+ They are mixed from `--cue-fg`, not authored per preset, and that is the whole
3070
+ point of them. A share of the foreground keeps its relationship to the type it
3071
+ separates whatever the palette turned out to be — the rules lighten with the text
3072
+ in a light block, they hold at `terminal`'s cyan-on-black and at `luma`'s warm
3073
+ paper, and a preset added tomorrow gets all three for free without authoring a
3074
+ thing. Asking ten presets to hand-pick a hairline would have produced ten
3075
+ slightly different answers to a question nobody should have to answer.
3076
+
3077
+ They are emitted **inside** every theme block rather than once on `:root`, like
3078
+ every other derived colour here: a custom property's `var()` references are
3079
+ substituted at the element that declares it, so a `:root`-level mix would freeze
3080
+ the default preset's foreground into every nested theme.
3081
+
3082
+ ## Spending them
3083
+
3084
+ They are not Tailwind utilities, on the same ruling as `--cue-accent-soft`'s
3085
+ status siblings — arbitrary-property syntax reads the token directly:
3086
+
3087
+ ```tsx
3088
+ <div className="border-t border-(--cue-hair) hover:bg-(--cue-row-hover)" />
3089
+ ```
3090
+
3091
+ ## `--cue-row-hover` does not replace the surface ramp
3092
+
3093
+ Worth being exact, because there are now two answers to "how does a row show
3094
+ hover" and only one of them is new. A row that sits **on a surface** still lifts
3095
+ to the next rung — `hover:bg-surface-2` — and that is what this library's own
3096
+ rows do: `Sidebar.Item`, `Tree`, `Pagination`, `Toggle`, `NumberField`. None of
3097
+ them change, and none of them should.
3098
+
3099
+ `--cue-row-hover` is for rows drawn **directly on `--cue-bg`**, where the next
3100
+ rung of the ramp is a step you can see from across the room and the row you are
3101
+ pointing at ends up looking selected rather than hovered. If the ramp fits your
3102
+ row, use the ramp.
3103
+
3104
+ ## `createTheme()` derives them too — and now it cannot forget to
3105
+
3106
+ `@cueplusplus/ui` carries its own copy of the derivation table, because
3107
+ `createTheme()` runs in a browser over a palette somebody just typed while the
3108
+ token build runs at publish time over DTCG files under Style Dictionary, and
3109
+ neither package can import the other's. So a preset you generate gets the three
3110
+ hairlines exactly as a shipped preset does.
3111
+
3112
+ The copy was the real hazard here, and it was already sitting in the tree: **no
3113
+ test compared the two lists.** A derivation added to the build alone would have
3114
+ been missing from every theme a consumer generated, and the only symptom would
3115
+ have been an unstyled rule in somebody else's app weeks later. There is now a
3116
+ test that compares `DERIVED_TOKEN_TEMPLATES` against the compiled stylesheet in
3117
+ both directions, so a one-sided edit fails in this repository instead.
3118
+
3119
+ **Nothing changes for existing consumers.** Both packages gain tokens and neither
3120
+ loses or moves one; no component's rendered output differs.
3121
+
3122
+ - 0cf94cb: Chip takes a third variant, `tag` — a hairline and micro type, no fill at rest
3123
+
3124
+ A list row's name is usually followed by two or three flat facts about it: what
3125
+ kind of thing it is, which channel it came from, what scope it lives in, whether
3126
+ it needs an account before it will do anything. They are not states and nobody
3127
+ is going to click them. Until now the only shape this system offered for them
3128
+ was a `Chip`, and a chip is a container — a tone tint at low alpha inside a tone
3129
+ rim, sized for a status readout that has to be seen from across the room. Put
3130
+ three on a row and repeat the row thirty times and the list stops reading as a
3131
+ list. It reads as ninety small buttons, and the names they were annotating come
3132
+ second to them.
3133
+
3134
+ `variant="tag"` is the same recipe read quietly. No fill. A `--cue-hair-strong`
3135
+ rim instead of `--cue-border` — the token that landed for exactly this, the rule
3136
+ _between_ things rather than the edge _around_ one. Micro type, and the chip's
3137
+ `0.15em` tracking closed to `0.06em`, because tracking cut for label-size type
3138
+ holds a four-letter word at micro apart far enough to read as four letters.
3139
+
3140
+ ```tsx
3141
+ <Chip variant="tag">mcp</Chip>
3142
+ <Chip tone="accent" variant="tag">beta</Chip>
3143
+ <Chip tone="danger" variant="tag">needs auth</Chip>
3144
+ ```
3145
+
3146
+ ## What it is not
3147
+
3148
+ It is not a small chip. The height is still `h-chip` and the radius is still
3149
+ `--cue-radius-control`, so all three variants ride the density ladder together
3150
+ and a row that mixes a tag with a chip has one baseline rather than two. What
3151
+ changed is the volume, not the size — and that is the decision to make when
3152
+ choosing between them. A tag whose job is to be seen is a chip.
3153
+
3154
+ The six toned rims are unchanged at `/35` across all three variants, so a
3155
+ `danger` tag and a `danger` outline chip agree on what danger looks like, which
3156
+ is the whole point of a shared tone vocabulary. Only `neutral` moves, because
3157
+ the neutral line is the one that has two weights now.
3158
+
3159
+ An interactive tag brightens its rim on hover and never grows a fill. The
3160
+ existing tone hover rows are now keyed on the variant so that stays true; a
3161
+ `tinted` or `outline` chip hovers exactly as it did.
3162
+
3163
+ ## The invariant that made room for it
3164
+
3165
+ The recipe already refused to emit a fill and then cancel it — that is why the
3166
+ tinted fills live in `compoundVariants`. Adding a third variant extended the
3167
+ same rule to everything else that varies: the rim, the ink, the gutter, the
3168
+ tracking and the type size are each emitted from exactly one place now, so no
3169
+ call to `chipVariants` ever produces two utilities competing for one property.
3170
+
3171
+ That matters more than it looks. `chipVariants` is exported, and a caller who
3172
+ uses it without `cn` gets a raw class string — where the winner is decided by
3173
+ the order the rules happen to sit in the stylesheet, not by which utility was
3174
+ written last. `tailwind-merge` would not have saved that string either: it
3175
+ cannot see `h-chip h-4` as a conflict at all, because `chip` is a token name and
3176
+ not a scale step. So the base string gave up `px`, the type size and the
3177
+ tracking to the `variant` map, and `neutral` gave up its rim to a compound —
3178
+ `--cue-border` for the two chip variants, `--cue-hair-strong` for the tag. The
3179
+ suite holds the invariant over the whole 7 × 3 × 2 matrix.
3180
+
3181
+ **Nothing here changes an existing chip.** Every tinted and outline chip emits
3182
+ the same set of utilities it did before; only which line of the recipe they come
3183
+ from moved. `packages/ui/manifest/**` is regenerated, not hand-edited.
3184
+
3185
+ - 2a21229: AppShell rails its navigation column, and the header cell over it moves with it
3186
+
3187
+ A console's navigation is set once and then wanted out of the way, and the shape
3188
+ that answers that is an icon rail: the column narrows to a strip of glyphs and
3189
+ the work area takes the 10rem back. `Sidebar` has been able to do the narrowing
3190
+ since it shipped — controlled `collapsed`, `data-collapsed`, an `sr-only` label
3191
+ so an icon-only row keeps its accessible name, a width transition that switches
3192
+ off under `prefers-reduced-motion`. What it could not do was tell anything
3193
+ outside itself, and the thing that needs telling is the cell in the bar directly
3194
+ above it.
3195
+
3196
+ That cell did not exist here at all. `TitleBar` and `AppBar` are full-width
3197
+ strips with leading, centre and trailing slots; neither knows the column's
3198
+ width, and a console with an app mark over its navigation has been hand-rolling
3199
+ one. Hand-rolling it is where the defect lives: the cell is a child of the bar
3200
+ and the column is a child of the body, two different branches of the tree, and
3201
+ two boxes that are meant to read as one column will drift the first time one of
3202
+ them changes width. A rail changes it on every click.
3203
+
3204
+ ## One length, written once
3205
+
3206
+ `AppShell.Root` now writes `--cue-nav-width` on the frame — `navWidth`
3207
+ (`"14rem"`) while expanded, `railWidth` (`"3.5rem"`) while railed — and both
3208
+ boxes read it. Neither holds a width of its own, so railing rewrites one length
3209
+ and the cell and the column arrive together rather than a frame apart. It is
3210
+ also the handle a consumer's own CSS aligns to: a sticky rule, a toolbar offset,
3211
+ a drop shadow that should stop where the column does.
3212
+
3213
+ `3.5rem` is a `--cue-icon-md` glyph centred in a `--cue-control-md` row with a
3214
+ gutter either side that still reads as a column rather than a strip. It is half
3215
+ a rem wider than the `3rem` `Sidebar.Root` collapses to on its own, which is the
3216
+ one visible change to an existing console: a shell whose column is collapsed
3217
+ from its hairline rail is now 8px wider there. Set `railWidth="3rem"` on
3218
+ `AppShell.Root` to keep the old measure exactly.
3219
+
3220
+ ## The new surface
3221
+ - **`AppShell.Root`** takes `rail` / `defaultRail` / `onRailChange` — the same
3222
+ controlled-prop bargain `navOpen` already makes — plus `navWidth` and
3223
+ `railWidth`. It stamps `data-rail` on itself and declares `group/app-shell`
3224
+ beside it, so anything in the frame can answer in plain Tailwind:
3225
+ `group-data-[rail]/app-shell:hidden` on a wordmark, the way `Sidebar` already
3226
+ publishes `data-collapsed` for its own parts.
3227
+ - **`AppShell.Bar`** is the bar divided by the navigation column: a `brand` slot
3228
+ exactly the column's width, wearing the column's rim, and the rest of the bar
3229
+ beside it. Omit `brand` and it is one undivided strip — an empty bordered cell
3230
+ over the column is worse than no cell. It stays a `<div>`, like `TitleBar`
3231
+ beside it, so a frame that also carries an `AppBar` does not end up with two
3232
+ `banner` landmarks; a page that wants one says `role="banner"`.
3233
+ - **`AppShell.Sidebar`** hands the frame's rail down to `Sidebar.Root` as its
3234
+ controlled `collapsed` and writes it back whenever the column's own hairline
3235
+ rail is pressed, so the two states cannot disagree and there is nothing to
3236
+ keep in sync.
3237
+
3238
+ The rail is off by default and every part of this is additive. A `collapsed` or
3239
+ `defaultCollapsed` passed to `AppShell.Sidebar` is the pre-rail spelling and
3240
+ still means exactly what it did — the column keeps its own state and its own two
3241
+ lengths, and the frame stays out of it rather than half-driving a column it
3242
+ could not have seeded, because a child cannot seed its parent's state. Reach for
3243
+ `defaultRail` on the frame when both boxes should move. A `width` passed to
3244
+ `AppShell.Sidebar` likewise still wins, for the column alone, which is a
3245
+ misalignment worth knowing about before writing it.
3246
+
3247
+ ## What is deliberately not here
3248
+
3249
+ **The toggle.** `Sidebar.Rail` — the 4px hit strip the shell already draws on
3250
+ the column's edge — rails the frame now, and any other control is one you render
3251
+ and wire, because where a rail button lives is a decision about your bar. The
3252
+ guidance ships the pattern: an `IconButton` with `aria-pressed` rather than
3253
+ `aria-expanded` (the column is still there either way, so it is a two-state
3254
+ toggle and not a disclosure) and `aria-controls` pointed at the `<nav>`.
3255
+
3256
+ **Persistence.** A rail is set once and expected to be remembered, and _where_
3257
+ it is remembered is a decision about hydration and privacy that a layout
3258
+ component has no business making. Read it from a cookie on the server and the
3259
+ first painted frame is already railed; read it from `localStorage` in an effect
3260
+ and the column jumps once after hydration. That is the whole reason the choice
3261
+ is yours and not the shell's.
3262
+
3263
+ The `md` breakpoint now wears a third face — the header cell stops measuring
3264
+ itself from a column that below 48rem is not in the layout at all, which keeps
3265
+ 14rem of a 390px bar from being walled off for nothing. It is the same
3266
+ navigation-shape decision the column and the drawer trigger already make, and
3267
+ the library's breakpoint budget is still that one decision and nothing else.
3268
+
3269
+ ### Patch Changes
3270
+
3271
+ - ad9a0e9: ## One space in a `TimeField` was throwing away the server render
3272
+
3273
+ A segmented date or time field prints text that `Intl.DateTimeFormat` produced,
3274
+ and `Intl` answers from the ICU data of whatever is running it. Today, `en-US` at
3275
+ half past seven in the evening is `7:30 PM` from Node 24 (CLDR 48) and
3276
+ `7:30 PM` from Chrome 151 — a narrow no-break space against an ordinary one.
3277
+ Identical on screen. Not identical to React.
3278
+
3279
+ A hydration mismatch is not a warning. React discards the server's HTML and
3280
+ re-renders the whole document on the client, and that re-mounts `<html>` — which
3281
+ React treats as a _singleton_ and therefore strips every attribute off before
3282
+ re-applying the ones the server rendered. `data-theme`, `data-density`,
3283
+ `data-font`, `data-mode` and `color-scheme` are not among them: they are written
3284
+ by `prepaintScript()`, client-side, before React exists. So the document spends
3285
+ the whole client render wearing bare `:root` — the library's default preset, no
3286
+ density, no pairing — and then snaps back.
3287
+
3288
+ Measured on this design system's own kitchen sink, in headless Chrome with the
3289
+ cache disabled: **208 ms** with no axis on the document, 6,423 elements resolving
3290
+ to a monospace nobody had selected, and **40 KB** of webfont fetched for it that
3291
+ then painted zero glyphs — on _every_ pairing, including the two that download
3292
+ nothing at all.
3293
+
3294
+ The literal separators now carry their text in an element of their own, marked
3295
+ `suppressHydrationWarning`. React only honours that flag on the element directly
3296
+ containing the differing text, and React Aria's `filterDOMProps` drops it before
3297
+ the segment's own element sees it, which is why the extra element exists. Only
3298
+ literal segments are wrapped — they are `aria-hidden` and not focusable. The
3299
+ value segments React Aria makes `contentEditable` are untouched, and digits and
3300
+ `AM`/`PM` are spelled the same by every ICU there has been.
3301
+
3302
+ **If you style literal separators**, `[data-type="literal"]` still selects them
3303
+ and still carries the text; a child-element selector under one now finds a
3304
+ `<span>`.
3305
+
3306
+ ## `ThemeProvider` stops waiting a commit to dress the document
3307
+
3308
+ Related, and found on the way. The `<html>` stamp used to wait for the provider's
3309
+ persisted preferences to be restored — a commit later — because on the first one
3310
+ its state is still the props, and writing those would put the app's defaults on a
3311
+ document the visitor had already chosen for. Waiting closed that hole by leaving
3312
+ another one: a subtree could be measured before the stamp landed.
3313
+
3314
+ It now writes on the first commit, from the same storage `prepaintScript()` read,
3315
+ and it writes in an _insertion_ effect, which React runs before any layout effect
3316
+ in the tree — so nothing measures the document before it is dressed. A value that
3317
+ is already correct is not rewritten, so the usual case is no DOM mutation at all.
3318
+
3319
+ No API moved. An app that ships the pre-paint script and the provider together,
3320
+ as the getting-started page has always said to, sees only that the first frame is
3321
+ the right one more often.
3322
+
3323
+ - Updated dependencies [85448d8]
3324
+ - @cueplusplus/tokens@0.4.0
3325
+
3326
+ ## 0.3.0
3327
+
3328
+ ### Minor Changes
3329
+
3330
+ - 31e3607: Ship the linked RequestPort preset release across tokens, UI, and brand tokens.
3331
+
3332
+ The release adds the typed RequestPort preset and its generated dark, light, and
3333
+ system selectors; system SSR, prepaint, provider, and portal behavior; the
3334
+ `persistPreferences={false}` and authoritative prepaint options contracts; the
3335
+ typed `fontFamilies` provider/portal override; the deterministic package
3336
+ manifest and companion checksums; and the accessible `TableScrollRegion`.
3337
+
3338
+ All three linked tarballs receive the same minor version and changelog entry.
3339
+ The Version Packages path regenerates the package manifest after applying those
3340
+ versions and rejects stale or nondeterministic output before publication.
3341
+
3342
+ ### Patch Changes
3343
+
3344
+ - Updated dependencies [31e3607]
3345
+ - @cueplusplus/tokens@0.3.0
3346
+
3347
+ ## 0.2.0
3348
+
3349
+ ### Minor Changes
3350
+
3351
+ - 33a95c4: Typeface is a third axis, beside theme and density.
3352
+
3353
+ `data-font` selects one of eight font pairings — a sans and a monospace — the
3354
+ same way `data-theme` selects a palette and `data-density` selects a geometry.
3355
+ `<ThemeProvider font>`, `useTheme().font` / `setFont`, persistence beside the
3356
+ other two, the pre-paint script, and every portal root carry it.
3357
+
3358
+ ```tsx
3359
+ <ThemeProvider theme="cue" density="normal" font="plex">
3360
+ <App />
3361
+ </ThemeProvider>
3362
+ ```
3363
+
3364
+ | id | sans | mono | what your app has to do |
3365
+ | -------- | ---------------------- | ------------------- | ---------------------------------- |
3366
+ | `system` | the platform's UI sans | **the theme's own** | nothing — this is the default |
3367
+ | `geist` | Geist | JetBrains Mono | deliver two faces |
3368
+ | `inter` | Inter | IBM Plex Mono | deliver two faces |
3369
+ | `plex` | IBM Plex Sans | IBM Plex Mono | deliver two faces |
3370
+ | `roboto` | Roboto | Roboto Mono | deliver two faces |
3371
+ | `source` | Source Sans 3 | Source Code Pro | deliver two faces |
3372
+ | `apple` | SF Pro Text | SF Mono | nothing — and nothing you _may_ do |
3373
+ | `office` | Calibri (Carlito) | Cascadia Mono | nothing — and nothing you _may_ do |
3374
+
3375
+ New exports from `@cueplusplus/tokens`: `FONTS`, `FontName`, `DEFAULT_FONT`,
3376
+ `FONT_PAIRINGS`, `FONT_FACES`, `FontDelivery`, `FontPairing`, `faceProperty`.
3377
+ `prepaintScript()` takes a fourth argument, the default pairing.
3378
+
3379
+ ## You have to load the fonts. We will not.
3380
+
3381
+ **This package ships no font file and no `@font-face`, and that is deliberate.**
3382
+ A library that injected one would be choosing your network requests, your
3383
+ content-security policy and your font licensing, in a stylesheet you imported
3384
+ for its colours. What a pairing emits instead is a hook with the family name as
3385
+ its fallback:
3386
+
3387
+ ```css
3388
+ [data-font="inter"] {
3389
+ --cue-font-sans: var(--cue-face-inter, Inter), ui-sans-serif, system-ui, …;
3390
+ --cue-font-mono:
3391
+ var(--cue-face-ibm-plex-mono, "IBM Plex Mono"), ui-monospace, …;
3392
+ }
3393
+ ```
3394
+
3395
+ Assign nothing and the stack still resolves — a local install first, then the
3396
+ platform — so no pairing ever renders as nothing. Assign the property and
3397
+ everything painted from the token follows. With `next/font`, the property name
3398
+ _is_ the option:
3399
+
3400
+ ```tsx
3401
+ const inter = Inter({ subsets: ["latin"], display: "swap", preload: false, variable: "--cue-face-inter" });
3402
+ <html className={inter.variable}>
3403
+ ```
3404
+
3405
+ `preload: false` matters: with preloading on, a site offering five webfont
3406
+ pairings makes every visitor download all of them to render in one.
3407
+ `FONT_PAIRINGS[name].faces` is the list of properties one pairing needs;
3408
+ `FONT_FACES` is every face at once, as `property → family`.
3409
+
3410
+ **`apple` and `office` are stacks and must stay stacks.** SF Pro and SF Mono are
3411
+ Apple-licensed and may not be served as webfonts; Calibri is Microsoft's and
3412
+ ships with Windows and Office rather than with anybody's site (Carlito is its
3413
+ metric-compatible libre twin, and is in the stack). They light up where
3414
+ installed and fall through cleanly where they are not. Do not bundle them.
3415
+
3416
+ ## A theme's monospace still applies — until somebody picks a pairing
3417
+
3418
+ Seven presets name a monospace of their own, and seven pairings name one, so
3419
+ one token was claimed twice.
3420
+ Neither axis writes it now: a theme publishes `--cue-font-theme-mono`, a pairing
3421
+ publishes `--cue-font-pairing-mono`, and `--cue-font-mono` resolves the two,
3422
+ pairing first. **A chosen pairing wins**, at whatever element each was stamped
3423
+ on — a pairing is what a person picked out of a menu and a preset's mono is what
3424
+ its author chose in the absence of a person. The default pairing, `system`,
3425
+ declines the question, so an app that never touches the axis keeps exactly the
3426
+ monospace it has today.
3427
+
3428
+ ## Two things that change without you asking
3429
+
3430
+ **The base sans no longer names `Geist`.** It named a font this package has
3431
+ never delivered, so for almost every visitor it resolved to the next entry
3432
+ anyway. `--cue-font-sans` and `--cue-font-display` are now the platform stack;
3433
+ Geist is the head of the `geist` pairing, which your app can actually deliver.
3434
+ If you want it back, deliver it: `font="geist"` plus the two `--cue-face-*`
3435
+ assignments.
3436
+
3437
+ **A generated theme's mono moves with it.** `createTheme({ fonts: { mono } })`
3438
+ now writes `--cue-font-theme-mono`. Nothing to change unless you read the
3439
+ generated `tokens` map by key.
3440
+
3441
+ ## Migrating
3442
+
3443
+ Nothing is required. Two things are worth doing:
3444
+
3445
+ 1. **Pass the pairing to the pre-paint script**, as its fourth argument, if your
3446
+ app's default is not `system`:
3447
+ `prepaintScript(DEFAULT_STORAGE_KEY, "cue", "normal", "geist")`. A theme that
3448
+ arrives one frame late is a flash of the wrong colour; a _family_ that
3449
+ arrives one frame late reflows every line under the reader.
3450
+ 2. **If your own CSS overrides `--cue-font-mono`** under a `[data-theme]` block
3451
+ of your own, move it to `--cue-font-theme-mono` — otherwise your override
3452
+ beats the typeface picker and yours is the one theme it cannot move.
3453
+
3454
+ `font` is a root-level prop: there is no font island, and a nested provider
3455
+ forwards the axis (and `setFont`) to the root. A specimen that genuinely wants
3456
+ one face beside another needs nothing from this library but `data-font` on a
3457
+ `<div>`.
3458
+
3459
+ - 038837d: `AppShell`: the console frame, and the mobile navigation that goes with it.
3460
+
3461
+ Every console screen is the same five parts in the same arrangement — title bar,
3462
+ navigation column, toolbar, one scrolling work area, status bar — and until now
3463
+ each one arranged them by hand. `AppShell` is that arrangement, with the
3464
+ arithmetic that actually holds it up: `h-dvh` rather than `min-h-dvh`, an
3465
+ unbroken `min-h-0` chain from the frame down to the scroller, `min-w-0` on the
3466
+ content column, and a scroller that is not the document, so the chrome stays put
3467
+ with no `sticky` anywhere.
3468
+
3469
+ ```tsx
3470
+ import {
3471
+ AppShell,
3472
+ Sidebar,
3473
+ StatusBar,
3474
+ TitleBar,
3475
+ Toolbar,
3476
+ } from "@cueplusplus/ui";
3477
+
3478
+ <AppShell.Root>
3479
+ <TitleBar center="main-stage.cue">
3480
+ <AppShell.NavTrigger />
3481
+ </TitleBar>
3482
+ <AppShell.Body>
3483
+ <AppShell.Sidebar aria-label="Console" drawerTitle="Console">
3484
+ <Sidebar.Section label="Show">
3485
+ <Sidebar.Item icon={List} active>
3486
+ Cues
3487
+ </Sidebar.Item>
3488
+ </Sidebar.Section>
3489
+ </AppShell.Sidebar>
3490
+ <AppShell.Content>
3491
+ <Toolbar.Root aria-label="Playback">…</Toolbar.Root>
3492
+ <AppShell.Scroller>…</AppShell.Scroller>
3493
+ </AppShell.Content>
3494
+ <AppShell.Aside aria-label="Inspector">…</AppShell.Aside>
3495
+ </AppShell.Body>
3496
+ <StatusBar>…</StatusBar>
3497
+ </AppShell.Root>;
3498
+ ```
3499
+
3500
+ Seven parts: `Root` (the frame), `Body` (the middle row), `Sidebar` (the
3501
+ navigation), `NavTrigger` (the control that opens it on a phone), `Content` (the
3502
+ `<main>` column — pass `main={false}` when the shell is embedded in a page that
3503
+ already has one), `Scroller` (the only element that may scroll) and `Aside` (a
3504
+ trailing column with a scroller of its own). `TitleBar` and `StatusBar` go in as
3505
+ ordinary children: both are already `shrink-0`, so a wrapper would only repeat
3506
+ that.
3507
+
3508
+ **`AppShell` is the one component in this library with a viewport breakpoint.**
3509
+ `AppShell.Sidebar` renders the same children twice — as the 14rem column at
3510
+ Tailwind's stock `md` (48rem) and up, and as a `Drawer` below it, opened by
3511
+ `AppShell.NavTrigger` from the bar. It is a CSS-only dual render, both shapes in
3512
+ the markup with one hidden per viewport: no `useMediaQuery`, nothing to measure,
3513
+ and a first server-rendered frame that is already correct. The switch decides
3514
+ navigation shape and nothing else; everything else in the library stays
3515
+ intrinsically responsive, and a later component that wants a breakpoint is a new
3516
+ decision rather than a precedent this one set.
3517
+
3518
+ `PageShell` and `Container` now offer the same four measures.
3519
+
3520
+ `narrow` (42rem) and `wide` (54rem) are unchanged. `broad` (84rem) is new on
3521
+ both: the application measure, wide enough for a two-column documentation page
3522
+ or a device frame. `full` is new on `PageShell`, which had no uncapped rung at
3523
+ all while `Container` did. Nothing existing moves — `narrow` is still the
3524
+ default on both.
3525
+
3526
+ ```tsx
3527
+ <PageShell width="broad" title="Components">…</PageShell>
3528
+ <PageShell width="full" title="Gallery">…</PageShell>
3529
+ ```
3530
+
3531
+ - 7eeb1c4: New component group, `brand`: the CUE++ marks, as components rather than files.
3532
+
3533
+ `CueMark` is the circular mark, `CueLogotype` is the wordmark and mark locked up
3534
+ at 778:367, and `Plussie` is the character — the same circle and the same two
3535
+ `+` glyphs the mark draws, out of the same geometry module, with the eyes in
3536
+ their own groups so a pose can move them. All three come from the root barrel
3537
+ (`@cueplusplus/ui`), pull no optional peer, and are static markup, so a
3538
+ server-rendered header costs no client bundle.
3539
+
3540
+ ```tsx
3541
+ import { CueLogotype, CueMark, Plussie } from "@cueplusplus/ui";
3542
+
3543
+ <CueMark size="sm" />
3544
+ <CueLogotype size="lg" title="CUE++" />
3545
+ <Plussie size={96} expression="wink" />
3546
+ ```
3547
+
3548
+ Props on all three: `size` (`"sm" | "md" | "lg"`, or a height in px), which sets
3549
+ the height and lets the artwork's ratio decide the width; `tone`
3550
+ (`"inherit" | "fg" | "accent"`, default `"inherit"` = `currentColor`); and an
3551
+ optional `title`, which turns a decorative `aria-hidden` mark into a labelled
3552
+ `role="img"`. `Plussie` adds `expression` (`default`, `blink`, `wink`,
3553
+ `look-left`, `look-right`, `look-up`, `look-down` — each one a still from the
3554
+ character's own animations) and `idle`, a CSS-only blink that is off by default
3555
+ and switched off again under `prefers-reduced-motion`.
3556
+
3557
+ There is no light/dark pair to choose between. `@cueplusplus/brand-tokens` still
3558
+ ships the four source SVGs as files for the places that need a URL — a favicon,
3559
+ an `<img>`, an email — and those hard-code `white`, `black` and `#1E1E1E`.
3560
+ These components take their ink from the token layer instead, so one element is
3561
+ correct in all eight presets and both modes. The path data is the same artwork,
3562
+ copied verbatim.
3563
+
3564
+ - b86f482: The density ladder is five rungs, and the two names in the middle have moved.
3565
+
3566
+ `ultra-compact` · `normal` · `large` was never a ladder anyone could reason
3567
+ about: the rung called `normal` is what every other design system calls
3568
+ _compact_, the rung called `large` is what everyone else calls _normal_, and
3569
+ above that there was nothing at all — no level for a touch panel, a stage
3570
+ monitor, or a reader who needs the whole screen a size up. This renames the two
3571
+ misnamed rungs to what they always were and adds the two that were missing.
3572
+
3573
+ **Minor rather than major because the package is pre-1.0** — 0.x's breaking rung
3574
+ is the minor one, and this is breaking.
3575
+
3576
+ **Read the migration before you upgrade.** One of its four steps is a prop
3577
+ rename a compiler catches. The other three are not: CSS of your own keyed on
3578
+ `[data-density]`, a label map with one entry per level, and a persisted
3579
+ preference all keep working after this release and quietly mean something else.
3580
+
3581
+ ## The mapping
3582
+
3583
+ | you wrote | you now write | geometry |
3584
+ | --------------- | ----------------- | ---------------------------------------------------- |
3585
+ | `ultra-compact` | `ultra-compact` | unchanged |
3586
+ | `normal` | **`compact`** | **unchanged** — the name moved, not a pixel |
3587
+ | `large` | **`normal`** | **unchanged** — the name moved, not a pixel |
3588
+ | — | **`large`** | new: a rung above what the ladder used to top out at |
3589
+ | — | **`ultra-large`** | new: the top, and the mirror of `ultra-compact` |
3590
+
3591
+ Nothing that existed changed size. `<ThemeProvider density="normal">` used to
3592
+ render 24px controls and now renders 32px ones — not because the geometry moved
3593
+ but because `normal` now names the rung above the one it used to name.
3594
+
3595
+ ## Migrating
3596
+
3597
+ Four things move. Only the first and the last are visible to a compiler; the two
3598
+ in the middle are the ones that ship a wrong-looking screen with a green build.
3599
+
3600
+ ### 1. Rename every level you name, one step down the ladder
3601
+
3602
+ Everywhere you write a density — the provider, a `<Density>` island, a prop you pass
3603
+ through, a test:
3604
+
3605
+ ```diff
3606
+ -<ThemeProvider theme="cue" density="normal">
3607
+ +<ThemeProvider theme="cue" density="compact">
3608
+ ```
3609
+
3610
+ ```diff
3611
+ -<Density density="large">
3612
+ +<Density density="normal">
3613
+ ```
3614
+
3615
+ `ultra-compact` is unchanged and needs no edit. Do the two renames in that
3616
+ order, or in one pass — `normal` → `compact` and `large` → `normal` — and your
3617
+ screens render exactly as they did before.
3618
+
3619
+ **If you never passed a `density`, this step is a no-op.** The default moved
3620
+ from `normal` to `compact`, which is the same geometry it always was: an app
3621
+ that never chose a level renders identically before and after.
3622
+
3623
+ ### 2. Rename your own `[data-density]` CSS — and add blocks for the two new rungs
3624
+
3625
+ **This is the half that fails silently.** If your app keys any of its own CSS on
3626
+ `data-density` — a per-rung scalar you multiply your lengths by, a per-rung
3627
+ override, anything — those selectors still match a valid attribute value after
3628
+ this release. They just match a _different rung_. Nothing throws, nothing logs,
3629
+ no build fails: your rows simply stop matching the controls inside them.
3630
+
3631
+ ```diff
3632
+ :root { --app-type: 1; --app-space: 1; }
3633
+ -[data-density="normal"] { --app-type: 1; --app-space: 1; }
3634
+ -[data-density="large"] { --app-type: 1.111; --app-space: 1.333; }
3635
+ +[data-density="compact"] { --app-type: 1; --app-space: 1; }
3636
+ +[data-density="normal"] { --app-type: 1.111; --app-space: 1.333; }
3637
+ +/* and two blocks that did not exist before — continue your own step, or read
3638
+ + the ratio off the library's ladder: --cue-density is 0.85 / 1 / 1.3 / 1.6 / 1.9
3639
+ + and the spacing sequence shifts one place per rung. */
3640
+ +[data-density="large"] { --app-type: …; --app-space: …; }
3641
+ +[data-density="ultra-large"] { --app-type: …; --app-space: …; }
3642
+ ```
3643
+
3644
+ What each rung does if you rename the props and stop there:
3645
+
3646
+ | rung | if you only renamed the props | what the reader sees |
3647
+ | --------------- | ----------------------------------------------- | ------------------------------------------------------------------------------- |
3648
+ | `ultra-compact` | your `ultra-compact` block still matches | correct |
3649
+ | `compact` | **no block matches** — falls through to `:root` | your scalars sit at their base values while the library's geometry is a rung up |
3650
+ | `normal` | your old `normal` block matches | your scalars are a rung behind the library's |
3651
+ | `large` | your old `large` block matches | your scalars are two rungs behind |
3652
+ | `ultra-large` | **no block matches** — falls through to `:root` | base scalars against the largest geometry the ladder has |
3653
+
3654
+ A rung with no block of yours does not fall back to the nearest one. It falls
3655
+ through to whatever `:root` declares, which is almost always the smallest value
3656
+ you own. Grep your stylesheets for `data-density` before you upgrade:
3657
+
3658
+ ```sh
3659
+ rg 'data-density' --glob '!node_modules'
3660
+ ```
3661
+
3662
+ ### 3. Fix anything that enumerates the ladder
3663
+
3664
+ A label map, a switcher, a segmented control, a settings screen, a persisted
3665
+ preference — anything with one entry per density level is now missing two and
3666
+ mis-naming two. A map keyed by the old three names does not error; it returns
3667
+ `undefined`, and most switchers render the raw slug:
3668
+
3669
+ ```diff
3670
+ const DENSITY_LABELS = {
3671
+ "ultra-compact": "Ultra compact",
3672
+ - normal: "Normal",
3673
+ - large: "Large",
3674
+ + compact: "Compact",
3675
+ + normal: "Normal",
3676
+ + large: "Large",
3677
+ + "ultra-large": "Ultra large",
3678
+ };
3679
+ ```
3680
+
3681
+ `DENSITIES` from `@cueplusplus/tokens` is the list to derive from rather than
3682
+ restate — it is five entries now and it will be right next time too. If your map
3683
+ is typed `Record<Density, string>` TypeScript will fail the build for you; if it
3684
+ is a plain object literal, nothing will.
3685
+
3686
+ **A persisted preference that says `"normal"` is now a bigger screen.** The
3687
+ provider stores the level under `localStorage` and validates it against the
3688
+ ladder, so `"normal"` is still a valid value — it just means a different rung.
3689
+ Migrate the stored value the same way you migrate the code, or accept that
3690
+ returning users move up one rung once.
3691
+
3692
+ ### 4. `density-ultra:` is now `density-ultra-compact:`
3693
+
3694
+ The stylesheet registers one Tailwind variant per rung, each named after the
3695
+ rung it matches: `density-ultra-compact:`, `density-compact:`,
3696
+ `density-normal:`, `density-large:`, `density-ultra-large:`. `density-ultra:` is
3697
+ gone, because with an `ultra-large` rung on the ladder "ultra" names neither end
3698
+ of it — and an unknown variant is a build error, so this one tells you.
3699
+ `density-large:` keeps its name and changes meaning with its rung, which does
3700
+ not.
3701
+
3702
+ ## The two new rungs
3703
+
3704
+ Every axis continues the step the three existing rungs already walk, rather than
3705
+ scaling anything by a factor:
3706
+
3707
+ | | ultra-compact | compact | normal | **large** | **ultra-large** |
3708
+ | ---------------- | ------------- | ------- | -------- | ------------ | --------------- |
3709
+ | `control-md` | 20px | 24px | 32px | **40px** | **48px** |
3710
+ | `chip-h` | 16px | 18px | 22px | **26px** | **30px** |
3711
+ | `icon-md` | 10px | 12px | 16px | **20px** | **24px** |
3712
+ | `text-body` | 10px | 11px | 13px | **15px** | **17px** |
3713
+ | `pad-row` | 2 × 6px | 4 × 8px | 6 × 12px | **8 × 16px** | **10 × 20px** |
3714
+ | `chrome-toolbar` | 32px | 36px | 44px | **52px** | **60px** |
3715
+ | `--cue-density` | 0.85 | 1 | 1.3 | **1.6** | **1.9** |
3716
+
3717
+ `large` is a touch and presentation rung: 40px controls clear the WCAG 2.2
3718
+ SC 2.5.8 target floor with room to spare. `ultra-large` is for a wall display, a
3719
+ kiosk, a stage monitor read from two metres, and for a low-vision reader who
3720
+ wants the whole interface a size up rather than the browser's zoom.
3721
+
3722
+ The `@media (pointer: coarse)` re-raise still applies to `ultra-compact` alone,
3723
+ and still raises it to exactly the same heights it always did — `compact`'s.
3724
+ `compact` is the rung the raise lands on, so raising it too would mean promoting
3725
+ it to `normal` and moving today's default geometry on every touch device.
3726
+
3727
+ ## The type ladder got a top
3728
+
3729
+ `text-title` and `text-emphasis` are bigger at every rung. `text-micro`,
3730
+ `text-label`, `text-ui` and `text-body` are untouched — consoles are built out
3731
+ of those four and none of them moves.
3732
+
3733
+ | | ultra-compact | compact | normal | large | ultra-large |
3734
+ | --------------- | ------------- | ------------- | ------------- | -------- | ----------- |
3735
+ | `text-emphasis` | 11 → **13px** | 12 → **14px** | 14 → **17px** | **20px** | **23px** |
3736
+ | `text-title` | 12 → **16px** | 13 → **18px** | 16 → **22px** | **26px** | **30px** |
3737
+
3738
+ A page heading was 1.18× a paragraph, which is not a hierarchy — it is six sizes
3739
+ within four points of each other, and every screen built on it came out flat. It
3740
+ is now 1.6–1.76×, and the whole ladder spans 2× to 2.7× from `micro` to `title`
3741
+ depending on the rung. If you set a page title in `text-title` it will look like
3742
+ one now; if you were compensating with a hand-written size, delete the
3743
+ compensation.
3744
+
3745
+ ## Also
3746
+
3747
+ `prepaintScript()` takes a third argument, `defaultDensity`, alongside
3748
+ `defaultTheme`. An app whose provider names a level other than `compact` should
3749
+ pass the same level here, or the first frame paints at one rung and every frame
3750
+ after it at another:
3751
+
3752
+ ```tsx
3753
+ prepaintScript(DEFAULT_STORAGE_KEY, "cue", "normal");
3754
+ ```
3755
+
3756
+ - a382b68: A ninth theme preset, `quotamate`, with a light pair.
3757
+
3758
+ `THEMES` gains `"quotamate"` and `ThemeName` widens with it, so a consumer can write
3759
+ `theme="quotamate"` without a cast. `dist/themes/quotamate.css` is built alongside the other
3760
+ eight, and `THEME_PRESETS.quotamate` is available to `createTheme()` and the configurator.
3761
+
3762
+ ```tsx
3763
+ <ThemeProvider theme="quotamate" density="ultra-compact" mode="dark">
3764
+ ```
3765
+
3766
+ A blue-black ramp at hue 240 under a cyan accent (`#00e5e5`). Not a new design — quotamate is
3767
+ a shipping product whose palette was already fixed in a private stylesheet, so the values are
3768
+ the same colours in hex that it authored in HSL. Adopting the preset is a no-op for the
3769
+ running product rather than a restyle, which is the only reason it can be adopted at all.
3770
+
3771
+ **Its status hues are deliberately not the accent**, which is where this preset differs from
3772
+ `cue`. quotamate's whole job is to say whether an account is usable right now, so `ok`, `warn`
3773
+ and `danger` have to stay separable at a glance from the colour that means "interactive" — a
3774
+ dashboard where "healthy" and "clickable" are the same green cannot be read quickly.
3775
+
3776
+ Light drops the accent to a deepened teal (`#0d7d7d`) rather than the near-black the product
3777
+ itself used. `#00e5e5` on `#fcfcfc` fails contrast for text and for a hairline rim alike, so
3778
+ the product's instinct was right; an accent that is merely dark, though, loses the brand.
3779
+ Same hue, enough chroma to read as the product's colour, dark enough to carry white text on a
3780
+ filled control.
3781
+
3782
+ Two tests encoded "eight presets" and were corrected rather than bumped:
3783
+
3784
+ - `build.test.mjs` listed the theme names, which is the point of that assertion.
3785
+ - `disjoint.test.mjs` asserted `sources === 16` as a stand-in for "each preset has a dark and
3786
+ a light source". A count is a poor proxy — eight darks and eight lights is 16, and so is
3787
+ nine darks and seven lights — and it failed a geometry test for a reason unrelated to
3788
+ geometry. It now asserts the pairing directly and will not go stale on the tenth preset.
3789
+
3790
+ - 236d09b: The bars: `AppBar`, `Footer`, and one overflow behaviour for every strip that can
3791
+ outgrow its box.
3792
+
3793
+ ### One overflow behaviour, not three bespoke fixes
3794
+
3795
+ `Toolbar` had neither wrap nor scroll and its buttons are `shrink-0
3796
+ whitespace-nowrap`, so a bar wider than its container ran off the edge with no
3797
+ way to reach it. `SegmentedControl` did the same with five segments in a page
3798
+ header. Both now scroll, from one module, with the affordance
3799
+ `ScrollableTabsList` has shipped since the tab strip was written: a fade mask at
3800
+ whichever edge still hides something, and a chevron there — because what sits at
3801
+ an overflowing edge is usually the gap _between_ two controls, and fading empty
3802
+ space produces nothing anyone can see.
3803
+
3804
+ ```tsx
3805
+ <Toolbar.Root aria-label="Playback" /> // scrolls by default
3806
+ <Toolbar.Root aria-label="Playback" overflow="clip" /> // the old behaviour
3807
+ <SegmentedControl aria-label="View" items={items} overflow="clip" />
3808
+ ```
3809
+
3810
+ Not wrapping: every bar here is one rung of the chrome ladder tall and sits in a
3811
+ layout that has already subtracted that height, so a bar that quietly becomes
3812
+ two rows steals it from the work area. Not a "more" menu: `Toolbar` takes
3813
+ arbitrary children and there is no honest conversion from one into a menu item,
3814
+ so it would need a parallel data model, a measuring pass on every resize, and it
3815
+ would lift the surplus controls out of Base UI's roving-focus group. A bar whose
3816
+ controls fit renders exactly as it did before — no mask, no chevrons, no
3817
+ compositing layer.
3818
+
3819
+ `AppBar.Nav` and `ScrollableTabsList` use the same module, so the library has one
3820
+ implementation of this and four consumers.
3821
+
3822
+ ### `AppBar` — the horizontal bar
3823
+
3824
+ ```tsx
3825
+ <AppBar.Root>
3826
+ <AppBar.Brand render={<NextLink href="/" />}>
3827
+ <Eyebrow>@cueplusplus/ui</Eyebrow>
3828
+ <Chip tone="accent" variant="outline">
3829
+ 0.1.0
3830
+ </Chip>
3831
+ </AppBar.Brand>
3832
+
3833
+ <AppBar.Nav aria-label="Sections">
3834
+ <AppBar.Link render={<NextLink href="/docs" />}>
3835
+ Getting started
3836
+ </AppBar.Link>
3837
+ <AppBar.Separator />
3838
+ <AppBar.Link render={<NextLink href="/gallery" />} register="strong">
3839
+ Gallery
3840
+ </AppBar.Link>
3841
+ <AppBar.Link
3842
+ external
3843
+ render={<a href="https://skills.cueplusplus.com" />}
3844
+ >
3845
+ Skills
3846
+ </AppBar.Link>
3847
+ </AppBar.Nav>
3848
+
3849
+ <AppBar.Actions>
3850
+ <ThemePicker />
3851
+ </AppBar.Actions>
3852
+ </AppBar.Root>
3853
+ ```
3854
+
3855
+ Sticky, `--cue-chrome-toolbar` tall, brand and actions `shrink-0` with the
3856
+ navigation between them giving up the width. `AppBar.Link` is set in one of two
3857
+ registers — `muted` is furniture, `strong` is a destination — and `active` is a
3858
+ third thing rather than a louder register: accent ink _and_ `aria-current="page"`.
3859
+
3860
+ This is a **bar**, not a menu: `NavigationMenu` goes inside `AppBar.Nav` when a
3861
+ section has children. It is also not `TitleBar`, which is window furniture.
3862
+
3863
+ **It adds no viewport breakpoint.** `AppShell` still holds the library's only
3864
+ one, for navigation shape. A horizontal bar never faces that question: at 390px
3865
+ it is the same bar with a shorter window onto the same links.
3866
+
3867
+ `app-bar.tsx` carries no `"use client"` directive, so the bar renders from a
3868
+ server component — which a site header has to, because that is where a framework
3869
+ reads its data. The client boundary is around the navigation's scroller alone.
3870
+
3871
+ ### `Footer` — the landmark, the hairline, the measure
3872
+
3873
+ ```tsx
3874
+ <Footer
3875
+ meta={
3876
+ <>
3877
+ <span>© 2026 CUE++</span>
3878
+ <Link href="/legal">Legal</Link>
3879
+ </>
3880
+ }
3881
+ >
3882
+ <Grid minItemWidth="12rem" gap={5}>
3883
+ <Stack gap={2}>
3884
+ <Eyebrow>Docs</Eyebrow>
3885
+ <Link href="/docs">Getting started</Link>
3886
+ </Stack>
3887
+ </Grid>
3888
+ </Footer>
3889
+ ```
3890
+
3891
+ The `contentinfo` landmark, a full-bleed hairline, a centred column on the same
3892
+ four measures `Container` and `PageShell` offer (`broad` by default), and a
3893
+ `meta` slot whose fine print hangs under a rule of its own.
3894
+
3895
+ Deliberately **not** a link-list component. A complex footer is `Grid` + `Stack`
3896
+ - `Link` inside this one — components that already exist, already read the
3897
+ density ladder, and already handle a column that is a paragraph rather than a
3898
+ list. `Grid`'s `minItemWidth` collapses the columns on a phone with no breakpoint
3899
+ and no prop for how many to draw. Static markup, no `"use client"`.
3900
+
3901
+ - 2c6aca1: `AppShell` and `Sidebar`: the four defects the shell's first independent consumer found, and the landmark it was announcing twice.
3902
+
3903
+ `AppShell` was drawn from the console specimens. The documentation site is the
3904
+ first thing outside them to wear it, and putting a real 30-row navigation and a
3905
+ few hundred visually-hidden labels through it turned up four things the
3906
+ specimens never could. All four are fixed in the library rather than at the call
3907
+ site, which is the whole point of a shell.
3908
+
3909
+ **Minor rather than major because the package is pre-1.0** — 0.x's breaking rung
3910
+ is the minor one, and two of these changes are breaking. The migration is at the
3911
+ bottom.
3912
+
3913
+ **`AppShell.Root` keeps its own `documentScrolls: false` promise.** The frame is
3914
+ now `relative overflow-clip`. The flex arithmetic it already had only governs
3915
+ boxes in the flow; an absolutely positioned descendant is laid out against its
3916
+ containing block, and a frame that is neither positioned nor clipping is not
3917
+ one — so the _initial_ containing block was, and the descendant's scrollable
3918
+ overflow landed on `<html>`. `.sr-only` is `position: absolute`, so this was an
3919
+ everyday case rather than an exotic one: a page carrying 308 hidden labels
3920
+ measured a 7,068px document inside a 900px frame with every visible box on
3921
+ screen, and a browser scrollbar that scrolled nothing. `clip` rather than
3922
+ `hidden`, so the frame is not a scroll container something can shove sideways
3923
+ with no scrollbar to put it back. The emitters are fixed too — `StatusDot`,
3924
+ `OTPField`, `Message`, `AskBox` and `Sidebar.Item` now position the box their
3925
+ hidden label is laid out against, so the leak stops at source as well as at the
3926
+ frame.
3927
+
3928
+ **One navigation, one landmark.** `Sidebar.Root` rendered `<aside>` while
3929
+ `AppShell.Sidebar`'s drawer half rendered `<nav>`, so the same navigation
3930
+ announced as two different landmarks depending on the viewport — and a call site
3931
+ whose column is the site's primary navigation had to hand-patch
3932
+ `role="navigation"` over the part. Both halves now take one `landmark` prop:
3933
+
3934
+ ```tsx
3935
+ <AppShell.Sidebar aria-label="Console">…</AppShell.Sidebar> {/* <nav>, both shapes */}
3936
+ <Sidebar.Root aria-label="Filters" landmark="complementary">…</Sidebar.Root> {/* <aside> */}
3937
+ ```
3938
+
3939
+ `"navigation"` is the default, because a column of `Sidebar.Item`s with one of
3940
+ them marked `aria-current="page"` is primary navigation and not complementary.
3941
+
3942
+ **The column scrolls, and the rail does not scroll with it.** `Sidebar.Root` had
3943
+ no scroller at all, so a navigation taller than the frame simply lost its last
3944
+ rows; the fix is an inner scroller rather than `overflow-y-auto` on the root,
3945
+ because `Sidebar.Rail` is `absolute inset-y-0` and would otherwise slide away
3946
+ with the third screenful. Its containing block is the root and the scroller is
3947
+ the element inside it, so the rail spans the whole column and holds its edge
3948
+ however far the sections are scrolled. Measured: a 828px column of navigation in
3949
+ a 318px frame, scrolled 400px, rail unmoved and unclipped.
3950
+
3951
+ **`AppShell.NavTrigger` is a target a finger can hit.** It defaulted to
3952
+ `size="sm"` — 20px at normal density — on a control that renders _only_ below
3953
+ the `md` breakpoint, which is to say only on a touch screen, under the 24×24
3954
+ floor WCAG 2.2 SC 2.5.8 sets. The default is `size="md"` now: 24px at normal and
3955
+ large, and 24px at ultra-compact too, where the token layer's
3956
+ `@media (pointer: coarse)` rule already lifts the control ladder. It is also
3957
+ `shrink-0`, because every other slot in `AppBar` is and this was the one that
3958
+ gave — measured at 14px wide in a crowded 390px bar.
3959
+
3960
+ `Footer` gained no code, but its guidance was wrong in a way that cost a site its
3961
+ footer: it listed "inside `AppShell`" under _when not to use_. Inside a shell a
3962
+ footer belongs to the content scroller, at the end of the work area — bounded by
3963
+ the content column, scrolling with the page. What does not belong is a footer as
3964
+ a sibling of the navigation column. `Footer` and `StatusBar` are different
3965
+ objects and a shell can carry both.
3966
+
3967
+ ### Migrating
3968
+ - **`Sidebar.Root` renders `<nav>`.** A stylesheet or a test selecting
3969
+ `aside[data-slot="sidebar"]` needs `[data-slot="sidebar"]`, and a column that
3970
+ really is complementary needs `landmark="complementary"`. A call site passing
3971
+ `role="navigation"` to work around the old element can drop it.
3972
+ - **`Sidebar.Root` has one more element inside it.** Its children are wrapped in
3973
+ `[data-slot="sidebar-scroller"]`, which now carries the column's padding and
3974
+ gap. A `> *` selector aimed at the sections needs re-aiming; `overflow-y-auto`
3975
+ passed in at the call site can go.
3976
+ - **`AppShell.NavTrigger` is 4px bigger.** Pass `size="sm"` back if a bar really
3977
+ needs the old height, and read WCAG 2.5.8 first.
3978
+
3979
+ ### Patch Changes
3980
+
3981
+ - 6ce0c7d: The release notes now ship inside the package.
3982
+
3983
+ `files` was `dist` alone, so the `CHANGELOG.md` this repository writes on every
3984
+ release stayed in a private git repository and reached nobody: `npm pack` left it
3985
+ out, and the tarball a consumer installs from GitHub Packages carried the code
3986
+ with no record of what had changed in it. Anyone asking "what moved between the
3987
+ version I have and the version I am upgrading to" had to have access to the
3988
+ source repository to find out.
3989
+
3990
+ `CHANGELOG.md` is now on the `files` allowlist of all three published packages,
3991
+ so it lands in `node_modules/@cueplusplus/<package>/CHANGELOG.md` beside the code
3992
+ it describes. `packages/release` has the test that keeps it there.
3993
+
3994
+ No code changes and no API changes — the same `dist`, plus one file.
3995
+
3996
+ The same notes are published, alongside the CUE++ agent skills' own, at
3997
+ <https://skills.cueplusplus.com/releases>.
3998
+
3999
+ - Updated dependencies [33a95c4]
4000
+ - Updated dependencies [b86f482]
4001
+ - Updated dependencies [a382b68]
4002
+ - Updated dependencies [6ce0c7d]
4003
+ - @cueplusplus/tokens@0.2.0
4004
+
4005
+ ## 0.1.0
4006
+
4007
+ ### Minor Changes
4008
+
4009
+ - First release of the CUE++ design system.
4010
+
4011
+ `@cueplusplus/tokens` ships eight theme presets (`cue`, `terminal`, `signal`, `venu`, `hivehub`,
4012
+ `dusk`, `luma`, `snuffle`) in dark and light, three density levels (`ultra-compact`, `normal`,
4013
+ `large`), and the Tailwind v4 `@theme inline` mapping — colour scoped to `[data-theme]`, geometry to
4014
+ `[data-density]`, never mixed.
4015
+
4016
+ `@cueplusplus/ui` ships 146 components on Base UI: the core entry plus the `/layout`, `/forms`,
4017
+ `/overlays`, `/chrome`, `/instruments`, `/chat`, `/theming`, `/configurator`, `/color`, `/date`
4018
+ subpaths and the four opt-in ones (`/charts`, `/dmx`, `/midi`, `/flow`) whose heavy dependencies stay
4019
+ optional peers. `ThemeProvider`, the density islands, `createTheme()` with its contrast report and the
4020
+ floating `ThemeConfigurator` come with it.
4021
+
4022
+ `@cueplusplus/brand-tokens` ships the CUE++ brand layer — the white-alpha ramp, the mono stack, the
4023
+ eyebrow and motion treatments, and the wordmark and Plussie marks.
4024
+
4025
+ ### Patch Changes
4026
+
4027
+ - Updated dependencies
4028
+ - @cueplusplus/tokens@0.1.0