@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.
- package/CHANGELOG.md +4028 -0
- package/LICENSE +21 -0
- package/NOTICE.md +34 -0
- package/README.md +237 -0
- package/dist/agent-runtime/_glyphs.js +43 -0
- package/dist/agent-runtime/branch-picker.d.ts +45 -0
- package/dist/agent-runtime/branch-picker.js +87 -0
- package/dist/agent-runtime/index.d.ts +4 -0
- package/dist/agent-runtime/index.js +4 -0
- package/dist/agent-runtime/tool-call-card.d.ts +69 -0
- package/dist/agent-runtime/tool-call-card.js +187 -0
- package/dist/agent-runtime/use-agent-thread.d.ts +164 -0
- package/dist/agent-runtime/use-agent-thread.js +251 -0
- package/dist/brand/_chassis.d.ts +54 -0
- package/dist/brand/_chassis.js +81 -0
- package/dist/brand/_geometry.js +41 -0
- package/dist/brand/cue-logotype.d.ts +29 -0
- package/dist/brand/cue-logotype.js +60 -0
- package/dist/brand/cue-mark.d.ts +30 -0
- package/dist/brand/cue-mark.js +61 -0
- package/dist/brand/index.d.ts +5 -0
- package/dist/brand/plussie.d.ts +67 -0
- package/dist/brand/plussie.js +125 -0
- package/dist/charts/_token.js +39 -0
- package/dist/charts/container.d.ts +58 -0
- package/dist/charts/container.js +77 -0
- package/dist/charts/index.d.ts +5 -0
- package/dist/charts/index.js +5 -0
- package/dist/charts/ramps.d.ts +140 -0
- package/dist/charts/ramps.js +615 -0
- package/dist/charts/theme.d.ts +112 -0
- package/dist/charts/theme.js +165 -0
- package/dist/charts/tones.d.ts +111 -0
- package/dist/charts/tones.js +127 -0
- package/dist/chat/_glyphs.js +55 -0
- package/dist/chat/agent-color.d.ts +84 -0
- package/dist/chat/agent-color.js +101 -0
- package/dist/chat/agent-pile.d.ts +55 -0
- package/dist/chat/agent-pile.js +121 -0
- package/dist/chat/ask-box.d.ts +88 -0
- package/dist/chat/ask-box.js +341 -0
- package/dist/chat/composer.d.ts +66 -0
- package/dist/chat/composer.js +144 -0
- package/dist/chat/delegation-card.d.ts +60 -0
- package/dist/chat/delegation-card.js +129 -0
- package/dist/chat/index.d.ts +8 -0
- package/dist/chat/index.js +8 -0
- package/dist/chat/message-list.d.ts +45 -0
- package/dist/chat/message-list.js +141 -0
- package/dist/chat/message.d.ts +47 -0
- package/dist/chat/message.js +108 -0
- package/dist/chrome/_band.js +54 -0
- package/dist/chrome/_drag.js +59 -0
- package/dist/chrome/_edge-scroller.d.ts +45 -0
- package/dist/chrome/_edge-scroller.js +149 -0
- package/dist/chrome/_glyphs.js +119 -0
- package/dist/chrome/_status-bar-item.js +85 -0
- package/dist/chrome/_status-bar.d.ts +15 -0
- package/dist/chrome/_status-bar.js +171 -0
- package/dist/chrome/_tabs-scroll.d.ts +67 -0
- package/dist/chrome/_tabs-scroll.js +73 -0
- package/dist/chrome/_tree.d.ts +75 -0
- package/dist/chrome/_tree.js +49 -0
- package/dist/chrome/app-bar.d.ts +103 -0
- package/dist/chrome/app-bar.js +175 -0
- package/dist/chrome/app-shell.d.ts +243 -0
- package/dist/chrome/app-shell.js +266 -0
- package/dist/chrome/band.d.ts +106 -0
- package/dist/chrome/band.js +97 -0
- package/dist/chrome/empty-state.d.ts +37 -0
- package/dist/chrome/empty-state.js +57 -0
- package/dist/chrome/eyebrow.d.ts +29 -0
- package/dist/chrome/eyebrow.js +36 -0
- package/dist/chrome/footer.d.ts +88 -0
- package/dist/chrome/footer.js +83 -0
- package/dist/chrome/index.d.ts +24 -0
- package/dist/chrome/index.js +22 -0
- package/dist/chrome/inspector.d.ts +61 -0
- package/dist/chrome/inspector.js +121 -0
- package/dist/chrome/navigation-menu.d.ts +80 -0
- package/dist/chrome/navigation-menu.js +121 -0
- package/dist/chrome/page-shell.d.ts +38 -0
- package/dist/chrome/page-shell.js +76 -0
- package/dist/chrome/panel.d.ts +55 -0
- package/dist/chrome/panel.js +67 -0
- package/dist/chrome/row.d.ts +82 -0
- package/dist/chrome/row.js +106 -0
- package/dist/chrome/scroll-area.d.ts +39 -0
- package/dist/chrome/scroll-area.js +70 -0
- package/dist/chrome/section-header.d.ts +42 -0
- package/dist/chrome/section-header.js +79 -0
- package/dist/chrome/segmented-control.d.ts +77 -0
- package/dist/chrome/segmented-control.js +95 -0
- package/dist/chrome/status-bar.d.ts +129 -0
- package/dist/chrome/status-bar.js +120 -0
- package/dist/chrome/tabs.d.ts +80 -0
- package/dist/chrome/tabs.js +132 -0
- package/dist/chrome/title-bar.d.ts +47 -0
- package/dist/chrome/title-bar.js +43 -0
- package/dist/chrome/toolbar.d.ts +68 -0
- package/dist/chrome/toolbar.js +83 -0
- package/dist/chrome/tree.d.ts +98 -0
- package/dist/chrome/tree.js +248 -0
- package/dist/color/_checker.js +14 -0
- package/dist/color/_convert.d.ts +52 -0
- package/dist/color/_convert.js +66 -0
- package/dist/color/_glyphs.js +28 -0
- package/dist/color/_thumb.js +15 -0
- package/dist/color/_value.js +28 -0
- package/dist/color/color-area.d.ts +27 -0
- package/dist/color/color-area.js +40 -0
- package/dist/color/color-field.d.ts +32 -0
- package/dist/color/color-field.js +43 -0
- package/dist/color/color-picker.d.ts +71 -0
- package/dist/color/color-picker.js +178 -0
- package/dist/color/color-slider.d.ts +31 -0
- package/dist/color/color-slider.js +51 -0
- package/dist/color/color-swatch.d.ts +32 -0
- package/dist/color/color-swatch.js +44 -0
- package/dist/color/index.d.ts +7 -0
- package/dist/color/index.js +7 -0
- package/dist/configurator/_export.d.ts +78 -0
- package/dist/configurator/_export.js +302 -0
- package/dist/configurator/_glyphs.js +92 -0
- package/dist/configurator/_overrides.d.ts +266 -0
- package/dist/configurator/_overrides.js +541 -0
- package/dist/configurator/configurator.d.ts +71 -0
- package/dist/configurator/configurator.js +284 -0
- package/dist/configurator/export-dialog.d.ts +30 -0
- package/dist/configurator/export-dialog.js +121 -0
- package/dist/configurator/index.d.ts +8 -0
- package/dist/configurator/index.js +8 -0
- package/dist/configurator/panel-sections.d.ts +86 -0
- package/dist/configurator/panel-sections.js +352 -0
- package/dist/configurator/token-editor.d.ts +74 -0
- package/dist/configurator/token-editor.js +152 -0
- package/dist/configurator/use-zoom.d.ts +87 -0
- package/dist/configurator/use-zoom.js +113 -0
- package/dist/date/_glyphs.js +34 -0
- package/dist/date/_segments.js +101 -0
- package/dist/date/_trigger.js +31 -0
- package/dist/date/calendar.d.ts +25 -0
- package/dist/date/calendar.js +125 -0
- package/dist/date/date-field.d.ts +34 -0
- package/dist/date/date-field.js +41 -0
- package/dist/date/date-picker.d.ts +69 -0
- package/dist/date/date-picker.js +89 -0
- package/dist/date/date-range-picker.d.ts +65 -0
- package/dist/date/date-range-picker.js +92 -0
- package/dist/date/index.d.ts +6 -0
- package/dist/date/index.js +6 -0
- package/dist/date/time-field.d.ts +29 -0
- package/dist/date/time-field.js +36 -0
- package/dist/dmx/_ramp.d.ts +77 -0
- package/dist/dmx/_ramp.js +169 -0
- package/dist/dmx/_readout.js +53 -0
- package/dist/dmx/_universe.d.ts +105 -0
- package/dist/dmx/_universe.js +112 -0
- package/dist/dmx/channel-matrix.d.ts +81 -0
- package/dist/dmx/channel-matrix.js +285 -0
- package/dist/dmx/dmx-bar.d.ts +50 -0
- package/dist/dmx/dmx-bar.js +107 -0
- package/dist/dmx/dmx-strip.d.ts +41 -0
- package/dist/dmx/dmx-strip.js +196 -0
- package/dist/dmx/index.d.ts +7 -0
- package/dist/dmx/index.js +7 -0
- package/dist/dmx/universe-grid.d.ts +61 -0
- package/dist/dmx/universe-grid.js +182 -0
- package/dist/elements/activity-graph.d.ts +40 -0
- package/dist/elements/activity-graph.js +81 -0
- package/dist/elements/agent-card.d.ts +49 -0
- package/dist/elements/agent-card.js +91 -0
- package/dist/elements/agent-handoff.d.ts +24 -0
- package/dist/elements/agent-handoff.js +53 -0
- package/dist/elements/agent-mode-badge.d.ts +72 -0
- package/dist/elements/agent-mode-badge.js +110 -0
- package/dist/elements/agent-plan.d.ts +24 -0
- package/dist/elements/agent-plan.js +74 -0
- package/dist/elements/agent-status.d.ts +29 -0
- package/dist/elements/agent-status.js +46 -0
- package/dist/elements/agent-surface.d.ts +65 -0
- package/dist/elements/agent-surface.js +68 -0
- package/dist/elements/animated-number.d.ts +86 -0
- package/dist/elements/animated-number.js +118 -0
- package/dist/elements/approval-card.d.ts +40 -0
- package/dist/elements/approval-card.js +78 -0
- package/dist/elements/artifact-card.d.ts +30 -0
- package/dist/elements/artifact-card.js +58 -0
- package/dist/elements/background-inbox.d.ts +39 -0
- package/dist/elements/background-inbox.js +60 -0
- package/dist/elements/canvas-split.d.ts +77 -0
- package/dist/elements/canvas-split.js +138 -0
- package/dist/elements/chart.d.ts +41 -0
- package/dist/elements/chart.js +117 -0
- package/dist/elements/chat-panel.d.ts +46 -0
- package/dist/elements/chat-panel.js +104 -0
- package/dist/elements/checkpoint-history.d.ts +31 -0
- package/dist/elements/checkpoint-history.js +65 -0
- package/dist/elements/clamp.d.ts +90 -0
- package/dist/elements/clamp.js +109 -0
- package/dist/elements/code-diff.d.ts +42 -0
- package/dist/elements/code-diff.js +69 -0
- package/dist/elements/code-runner.d.ts +38 -0
- package/dist/elements/code-runner.js +84 -0
- package/dist/elements/command-palette.d.ts +48 -0
- package/dist/elements/command-palette.js +129 -0
- package/dist/elements/compaction-row.d.ts +63 -0
- package/dist/elements/compaction-row.js +110 -0
- package/dist/elements/comparison-card.d.ts +47 -0
- package/dist/elements/comparison-card.js +69 -0
- package/dist/elements/composer.d.ts +274 -0
- package/dist/elements/composer.js +505 -0
- package/dist/elements/computer-use.d.ts +40 -0
- package/dist/elements/computer-use.js +92 -0
- package/dist/elements/confidence-marker.d.ts +39 -0
- package/dist/elements/confidence-marker.js +64 -0
- package/dist/elements/connection-state.d.ts +29 -0
- package/dist/elements/connection-state.js +66 -0
- package/dist/elements/context-breakdown.d.ts +30 -0
- package/dist/elements/context-breakdown.js +86 -0
- package/dist/elements/context-usage.d.ts +99 -0
- package/dist/elements/context-usage.js +225 -0
- package/dist/elements/conversation-search.d.ts +48 -0
- package/dist/elements/conversation-search.js +88 -0
- package/dist/elements/cost-meter.d.ts +33 -0
- package/dist/elements/cost-meter.js +75 -0
- package/dist/elements/data-table.d.ts +31 -0
- package/dist/elements/data-table.js +67 -0
- package/dist/elements/day-separator.d.ts +34 -0
- package/dist/elements/day-separator.js +50 -0
- package/dist/elements/diagram.d.ts +34 -0
- package/dist/elements/diagram.js +75 -0
- package/dist/elements/document-reference.d.ts +37 -0
- package/dist/elements/document-reference.js +66 -0
- package/dist/elements/draft-restore.d.ts +27 -0
- package/dist/elements/draft-restore.js +54 -0
- package/dist/elements/edit-message.d.ts +36 -0
- package/dist/elements/edit-message.js +77 -0
- package/dist/elements/elements.css +459 -0
- package/dist/elements/elicitation-form.d.ts +48 -0
- package/dist/elements/elicitation-form.js +106 -0
- package/dist/elements/empty-state.d.ts +52 -0
- package/dist/elements/empty-state.js +98 -0
- package/dist/elements/end-of-turn-summary.d.ts +60 -0
- package/dist/elements/end-of-turn-summary.js +75 -0
- package/dist/elements/error-state.d.ts +29 -0
- package/dist/elements/error-state.js +55 -0
- package/dist/elements/feedback-dialog.d.ts +36 -0
- package/dist/elements/feedback-dialog.js +81 -0
- package/dist/elements/file-tree.d.ts +45 -0
- package/dist/elements/file-tree.js +84 -0
- package/dist/elements/flow-graph.d.ts +45 -0
- package/dist/elements/flow-graph.js +78 -0
- package/dist/elements/generative/chart.js +200 -0
- package/dist/elements/generative/faults.js +111 -0
- package/dist/elements/generative/icons.d.ts +41 -0
- package/dist/elements/generative/icons.js +190 -0
- package/dist/elements/generative/intrinsics.js +483 -0
- package/dist/elements/generative/spec.d.ts +110 -0
- package/dist/elements/generative/spec.js +366 -0
- package/dist/elements/generative.css +1145 -0
- package/dist/elements/generative.d.ts +144 -0
- package/dist/elements/generative.js +126 -0
- package/dist/elements/guardrail-notice.d.ts +29 -0
- package/dist/elements/guardrail-notice.js +63 -0
- package/dist/elements/image-generation.d.ts +18 -0
- package/dist/elements/image-generation.js +64 -0
- package/dist/elements/index.d.ts +116 -0
- package/dist/elements/index.js +115 -0
- package/dist/elements/inline-citation.d.ts +43 -0
- package/dist/elements/inline-citation.js +93 -0
- package/dist/elements/job-progress.d.ts +49 -0
- package/dist/elements/job-progress.js +74 -0
- package/dist/elements/launcher-bubble.d.ts +34 -0
- package/dist/elements/launcher-bubble.js +72 -0
- package/dist/elements/live-region-announcer.d.ts +130 -0
- package/dist/elements/live-region-announcer.js +230 -0
- package/dist/elements/loading-state.d.ts +27 -0
- package/dist/elements/loading-state.js +36 -0
- package/dist/elements/map-answer.d.ts +38 -0
- package/dist/elements/map-answer.js +93 -0
- package/dist/elements/markdown.d.ts +131 -0
- package/dist/elements/markdown.js +528 -0
- package/dist/elements/math-block.d.ts +51 -0
- package/dist/elements/math-block.js +70 -0
- package/dist/elements/mcp-server-panel.d.ts +41 -0
- package/dist/elements/mcp-server-panel.js +115 -0
- package/dist/elements/memory-chips.d.ts +30 -0
- package/dist/elements/memory-chips.js +43 -0
- package/dist/elements/message-actions.d.ts +38 -0
- package/dist/elements/message-actions.js +71 -0
- package/dist/elements/message-attachment.d.ts +48 -0
- package/dist/elements/message-attachment.js +63 -0
- package/dist/elements/message-branches.d.ts +25 -0
- package/dist/elements/message-branches.js +65 -0
- package/dist/elements/message-pair.d.ts +33 -0
- package/dist/elements/message-pair.js +65 -0
- package/dist/elements/message-queue.d.ts +31 -0
- package/dist/elements/message-queue.js +80 -0
- package/dist/elements/message-timing.d.ts +33 -0
- package/dist/elements/message-timing.js +37 -0
- package/dist/elements/mobile-composer.d.ts +44 -0
- package/dist/elements/mobile-composer.js +88 -0
- package/dist/elements/model-picker.d.ts +38 -0
- package/dist/elements/model-picker.js +69 -0
- package/dist/elements/number-ticker.d.ts +18 -0
- package/dist/elements/number-ticker.js +47 -0
- package/dist/elements/onboarding.d.ts +30 -0
- package/dist/elements/onboarding.js +75 -0
- package/dist/elements/permission-grant.d.ts +35 -0
- package/dist/elements/permission-grant.js +86 -0
- package/dist/elements/permission-scopes.d.ts +215 -0
- package/dist/elements/permission-scopes.js +249 -0
- package/dist/elements/prompt-library.d.ts +43 -0
- package/dist/elements/prompt-library.js +117 -0
- package/dist/elements/queue-dock.d.ts +101 -0
- package/dist/elements/queue-dock.js +132 -0
- package/dist/elements/quota-banner.d.ts +26 -0
- package/dist/elements/quota-banner.js +66 -0
- package/dist/elements/quote-reply.d.ts +44 -0
- package/dist/elements/quote-reply.js +79 -0
- package/dist/elements/range.d.ts +29 -0
- package/dist/elements/range.js +45 -0
- package/dist/elements/read-aloud.d.ts +45 -0
- package/dist/elements/read-aloud.js +75 -0
- package/dist/elements/reasoning-effort.d.ts +36 -0
- package/dist/elements/reasoning-effort.js +67 -0
- package/dist/elements/reasoning-panel.d.ts +46 -0
- package/dist/elements/reasoning-panel.js +75 -0
- package/dist/elements/recommendation-card.d.ts +45 -0
- package/dist/elements/recommendation-card.js +83 -0
- package/dist/elements/regenerate-menu.d.ts +45 -0
- package/dist/elements/regenerate-menu.js +51 -0
- package/dist/elements/replay.d.ts +217 -0
- package/dist/elements/replay.js +498 -0
- package/dist/elements/research-report.d.ts +38 -0
- package/dist/elements/research-report.js +69 -0
- package/dist/elements/retrieval-chunks.d.ts +43 -0
- package/dist/elements/retrieval-chunks.js +81 -0
- package/dist/elements/revert-dock.d.ts +159 -0
- package/dist/elements/revert-dock.js +327 -0
- package/dist/elements/reviewable-diff.d.ts +45 -0
- package/dist/elements/reviewable-diff.js +110 -0
- package/dist/elements/risk-badge.d.ts +52 -0
- package/dist/elements/risk-badge.js +91 -0
- package/dist/elements/schedule-card.d.ts +42 -0
- package/dist/elements/schedule-card.js +90 -0
- package/dist/elements/score-breakdown.d.ts +47 -0
- package/dist/elements/score-breakdown.js +82 -0
- package/dist/elements/scroll-anchor.d.ts +32 -0
- package/dist/elements/scroll-anchor.js +123 -0
- package/dist/elements/settings-panel.d.ts +52 -0
- package/dist/elements/settings-panel.js +107 -0
- package/dist/elements/shared-conversation.d.ts +39 -0
- package/dist/elements/shared-conversation.js +68 -0
- package/dist/elements/sources.d.ts +33 -0
- package/dist/elements/sources.js +61 -0
- package/dist/elements/speaker-identity.d.ts +32 -0
- package/dist/elements/speaker-identity.js +51 -0
- package/dist/elements/spec-sheet.d.ts +31 -0
- package/dist/elements/spec-sheet.js +45 -0
- package/dist/elements/stopped-run.d.ts +22 -0
- package/dist/elements/stopped-run.js +50 -0
- package/dist/elements/streaming-text.d.ts +31 -0
- package/dist/elements/streaming-text.js +45 -0
- package/dist/elements/subagent-list.d.ts +37 -0
- package/dist/elements/subagent-list.js +79 -0
- package/dist/elements/suggestions.d.ts +30 -0
- package/dist/elements/suggestions.js +36 -0
- package/dist/elements/surfaces.d.ts +85 -0
- package/dist/elements/surfaces.js +108 -0
- package/dist/elements/tail-status.d.ts +131 -0
- package/dist/elements/tail-status.js +94 -0
- package/dist/elements/terminal-block.d.ts +29 -0
- package/dist/elements/terminal-block.js +54 -0
- package/dist/elements/thinking-indicator.d.ts +18 -0
- package/dist/elements/thinking-indicator.js +36 -0
- package/dist/elements/thread-list.d.ts +35 -0
- package/dist/elements/thread-list.js +68 -0
- package/dist/elements/thread-search.d.ts +42 -0
- package/dist/elements/thread-search.js +99 -0
- package/dist/elements/timeline.d.ts +37 -0
- package/dist/elements/timeline.js +46 -0
- package/dist/elements/todo-list.d.ts +42 -0
- package/dist/elements/todo-list.js +59 -0
- package/dist/elements/tool-call.d.ts +41 -0
- package/dist/elements/tool-call.js +85 -0
- package/dist/elements/tool-error.d.ts +39 -0
- package/dist/elements/tool-error.js +76 -0
- package/dist/elements/tool-group.d.ts +45 -0
- package/dist/elements/tool-group.js +71 -0
- package/dist/elements/tool-timeline.d.ts +65 -0
- package/dist/elements/tool-timeline.js +88 -0
- package/dist/elements/trace-waterfall.d.ts +37 -0
- package/dist/elements/trace-waterfall.js +69 -0
- package/dist/elements/transcript-rules.d.ts +67 -0
- package/dist/elements/transcript-rules.js +95 -0
- package/dist/elements/turn-footer.d.ts +74 -0
- package/dist/elements/turn-footer.js +101 -0
- package/dist/elements/typing-indicator.d.ts +16 -0
- package/dist/elements/typing-indicator.js +51 -0
- package/dist/elements/verdict-row.d.ts +89 -0
- package/dist/elements/verdict-row.js +150 -0
- package/dist/elements/vocabulary.d.ts +612 -0
- package/dist/elements/vocabulary.js +611 -0
- package/dist/elements/voice-conversation.d.ts +49 -0
- package/dist/elements/voice-conversation.js +123 -0
- package/dist/elements/web-preview.d.ts +32 -0
- package/dist/elements/web-preview.js +66 -0
- package/dist/elements/web-search.d.ts +37 -0
- package/dist/elements/web-search.js +66 -0
- package/dist/elements/work-collapse.d.ts +103 -0
- package/dist/elements/work-collapse.js +131 -0
- package/dist/flow/_frame.d.ts +52 -0
- package/dist/flow/_frame.js +38 -0
- package/dist/flow/controls.d.ts +41 -0
- package/dist/flow/controls.js +53 -0
- package/dist/flow/flow.css +121 -0
- package/dist/flow/handles.d.ts +40 -0
- package/dist/flow/handles.js +61 -0
- package/dist/flow/index.d.ts +7 -0
- package/dist/flow/index.js +7 -0
- package/dist/flow/node-card.d.ts +71 -0
- package/dist/flow/node-card.js +102 -0
- package/dist/flow/signal-edge.d.ts +60 -0
- package/dist/flow/signal-edge.js +108 -0
- package/dist/flow/use-flow-theme.d.ts +69 -0
- package/dist/flow/use-flow-theme.js +109 -0
- package/dist/forms/_chassis.d.ts +164 -0
- package/dist/forms/_chassis.js +159 -0
- package/dist/forms/_glyphs.js +143 -0
- package/dist/forms/_listbox.js +55 -0
- package/dist/forms/autocomplete.d.ts +61 -0
- package/dist/forms/autocomplete.js +88 -0
- package/dist/forms/button-group.d.ts +47 -0
- package/dist/forms/button-group.js +52 -0
- package/dist/forms/checkbox.d.ts +51 -0
- package/dist/forms/checkbox.js +98 -0
- package/dist/forms/combobox.d.ts +73 -0
- package/dist/forms/combobox.js +108 -0
- package/dist/forms/env-var-input.d.ts +74 -0
- package/dist/forms/env-var-input.js +116 -0
- package/dist/forms/field.d.ts +89 -0
- package/dist/forms/field.js +102 -0
- package/dist/forms/file-upload.d.ts +113 -0
- package/dist/forms/file-upload.js +232 -0
- package/dist/forms/index.d.ts +26 -0
- package/dist/forms/index.js +26 -0
- package/dist/forms/input-group.d.ts +36 -0
- package/dist/forms/input-group.js +59 -0
- package/dist/forms/input.d.ts +45 -0
- package/dist/forms/input.js +36 -0
- package/dist/forms/multi-select.d.ts +62 -0
- package/dist/forms/multi-select.js +107 -0
- package/dist/forms/number-field.d.ts +39 -0
- package/dist/forms/number-field.js +65 -0
- package/dist/forms/otp-field.d.ts +54 -0
- package/dist/forms/otp-field.js +59 -0
- package/dist/forms/password-input.d.ts +46 -0
- package/dist/forms/password-input.js +62 -0
- package/dist/forms/radio-group.d.ts +50 -0
- package/dist/forms/radio-group.js +64 -0
- package/dist/forms/rating.d.ts +69 -0
- package/dist/forms/rating.js +103 -0
- package/dist/forms/scrub-input.d.ts +81 -0
- package/dist/forms/scrub-input.js +249 -0
- package/dist/forms/search-input.d.ts +54 -0
- package/dist/forms/search-input.js +112 -0
- package/dist/forms/select.d.ts +75 -0
- package/dist/forms/select.js +91 -0
- package/dist/forms/slider.d.ts +44 -0
- package/dist/forms/slider.js +65 -0
- package/dist/forms/switch.d.ts +36 -0
- package/dist/forms/switch.js +71 -0
- package/dist/forms/tags-input.d.ts +69 -0
- package/dist/forms/tags-input.js +148 -0
- package/dist/forms/textarea.d.ts +39 -0
- package/dist/forms/textarea.js +37 -0
- package/dist/forms/toggle-group.d.ts +41 -0
- package/dist/forms/toggle-group.js +56 -0
- package/dist/forms/toggle.d.ts +47 -0
- package/dist/forms/toggle.js +100 -0
- package/dist/icons/containers.d.ts +89 -0
- package/dist/icons/containers.js +295 -0
- package/dist/icons/index.d.ts +2 -0
- package/dist/index.d.ts +138 -0
- package/dist/index.js +124 -0
- package/dist/instruments/_ansi.js +116 -0
- package/dist/instruments/_data-row.d.ts +68 -0
- package/dist/instruments/_data-row.js +139 -0
- package/dist/instruments/_glyphs.js +90 -0
- package/dist/instruments/_ledger-disclosure.js +103 -0
- package/dist/instruments/_ledger.d.ts +21 -0
- package/dist/instruments/_ledger.js +5 -0
- package/dist/instruments/_motion.js +56 -0
- package/dist/instruments/_rem.js +21 -0
- package/dist/instruments/app-window-frame.d.ts +48 -0
- package/dist/instruments/app-window-frame.js +76 -0
- package/dist/instruments/code-block.d.ts +35 -0
- package/dist/instruments/code-block.js +173 -0
- package/dist/instruments/copy-button.d.ts +41 -0
- package/dist/instruments/copy-button.js +112 -0
- package/dist/instruments/data-row.d.ts +94 -0
- package/dist/instruments/data-row.js +151 -0
- package/dist/instruments/data-table.d.ts +168 -0
- package/dist/instruments/data-table.js +212 -0
- package/dist/instruments/data-tree.d.ts +126 -0
- package/dist/instruments/data-tree.js +303 -0
- package/dist/instruments/group-bar.d.ts +63 -0
- package/dist/instruments/group-bar.js +122 -0
- package/dist/instruments/index.d.ts +18 -0
- package/dist/instruments/index.js +18 -0
- package/dist/instruments/ledger.d.ts +204 -0
- package/dist/instruments/ledger.js +204 -0
- package/dist/instruments/log-viewer.d.ts +74 -0
- package/dist/instruments/log-viewer.js +215 -0
- package/dist/instruments/meter.d.ts +81 -0
- package/dist/instruments/meter.js +96 -0
- package/dist/instruments/progress.d.ts +61 -0
- package/dist/instruments/progress.js +79 -0
- package/dist/instruments/seam-grid.d.ts +66 -0
- package/dist/instruments/seam-grid.js +78 -0
- package/dist/instruments/sparkline.d.ts +39 -0
- package/dist/instruments/sparkline.js +85 -0
- package/dist/instruments/stat.d.ts +35 -0
- package/dist/instruments/stat.js +59 -0
- package/dist/instruments/table.d.ts +170 -0
- package/dist/instruments/table.js +237 -0
- package/dist/instruments/terminal-frame.d.ts +89 -0
- package/dist/instruments/terminal-frame.js +256 -0
- package/dist/instruments/two-step-button.d.ts +47 -0
- package/dist/instruments/two-step-button.js +166 -0
- package/dist/instruments/usage-chart.d.ts +55 -0
- package/dist/instruments/usage-chart.js +235 -0
- package/dist/layout/_collapse.js +46 -0
- package/dist/layout/_glyphs.js +45 -0
- package/dist/layout/_ground.d.ts +22 -0
- package/dist/layout/_ground.js +9 -0
- package/dist/layout/accordion.d.ts +52 -0
- package/dist/layout/accordion.js +81 -0
- package/dist/layout/bento.d.ts +144 -0
- package/dist/layout/bento.js +266 -0
- package/dist/layout/breadcrumb.d.ts +35 -0
- package/dist/layout/breadcrumb.js +67 -0
- package/dist/layout/card.d.ts +51 -0
- package/dist/layout/card.js +76 -0
- package/dist/layout/carousel.d.ts +96 -0
- package/dist/layout/carousel.js +211 -0
- package/dist/layout/collapsible.d.ts +74 -0
- package/dist/layout/collapsible.js +126 -0
- package/dist/layout/container.d.ts +27 -0
- package/dist/layout/container.js +43 -0
- package/dist/layout/description-list.d.ts +24 -0
- package/dist/layout/description-list.js +37 -0
- package/dist/layout/frames.d.ts +70 -0
- package/dist/layout/frames.js +298 -0
- package/dist/layout/grid.d.ts +40 -0
- package/dist/layout/grid.js +48 -0
- package/dist/layout/index.d.ts +19 -0
- package/dist/layout/index.js +18 -0
- package/dist/layout/item.d.ts +65 -0
- package/dist/layout/item.js +100 -0
- package/dist/layout/link.d.ts +48 -0
- package/dist/layout/link.js +63 -0
- package/dist/layout/pagination.d.ts +54 -0
- package/dist/layout/pagination.js +121 -0
- package/dist/layout/preview.d.ts +177 -0
- package/dist/layout/preview.js +628 -0
- package/dist/layout/resizable.d.ts +38 -0
- package/dist/layout/resizable.js +68 -0
- package/dist/layout/sidebar.d.ts +105 -0
- package/dist/layout/sidebar.js +158 -0
- package/dist/layout/stack.d.ts +46 -0
- package/dist/layout/stack.js +62 -0
- package/dist/layout/stepper.d.ts +49 -0
- package/dist/layout/stepper.js +81 -0
- package/dist/layout/timeline.d.ts +43 -0
- package/dist/layout/timeline.js +63 -0
- package/dist/lib/aria-class.js +24 -0
- package/dist/lib/cn.js +18 -0
- package/dist/lib/compose.js +38 -0
- package/dist/lib/cva.d.ts +18 -0
- package/dist/lib/cva.js +65 -0
- package/dist/midi/_notes.d.ts +101 -0
- package/dist/midi/_notes.js +183 -0
- package/dist/midi/_ruler.d.ts +100 -0
- package/dist/midi/_ruler.js +105 -0
- package/dist/midi/_time.d.ts +107 -0
- package/dist/midi/_time.js +130 -0
- package/dist/midi/index.d.ts +9 -0
- package/dist/midi/index.js +9 -0
- package/dist/midi/musical-time-input.d.ts +48 -0
- package/dist/midi/musical-time-input.js +154 -0
- package/dist/midi/piano-keyboard.d.ts +50 -0
- package/dist/midi/piano-keyboard.js +197 -0
- package/dist/midi/spectrum-visualizer.d.ts +65 -0
- package/dist/midi/spectrum-visualizer.js +213 -0
- package/dist/midi/threshold-rail.d.ts +72 -0
- package/dist/midi/threshold-rail.js +214 -0
- package/dist/midi/timeline-ruler.d.ts +83 -0
- package/dist/midi/timeline-ruler.js +152 -0
- package/dist/overlays/_debounce.js +34 -0
- package/dist/overlays/_glyphs.js +59 -0
- package/dist/overlays/_surface.d.ts +70 -0
- package/dist/overlays/_surface.js +70 -0
- package/dist/overlays/_wheel.js +42 -0
- package/dist/overlays/alert-dialog.d.ts +67 -0
- package/dist/overlays/alert-dialog.js +117 -0
- package/dist/overlays/command-palette.d.ts +93 -0
- package/dist/overlays/command-palette.js +193 -0
- package/dist/overlays/context-menu.d.ts +64 -0
- package/dist/overlays/context-menu.js +78 -0
- package/dist/overlays/dialog.d.ts +72 -0
- package/dist/overlays/dialog.js +124 -0
- package/dist/overlays/drawer.d.ts +75 -0
- package/dist/overlays/drawer.js +151 -0
- package/dist/overlays/dropdown-menu.d.ts +79 -0
- package/dist/overlays/dropdown-menu.js +156 -0
- package/dist/overlays/hover-card.d.ts +58 -0
- package/dist/overlays/hover-card.js +91 -0
- package/dist/overlays/index.d.ts +15 -0
- package/dist/overlays/index.js +15 -0
- package/dist/overlays/info-tip.d.ts +41 -0
- package/dist/overlays/info-tip.js +45 -0
- package/dist/overlays/menubar.d.ts +72 -0
- package/dist/overlays/menubar.js +100 -0
- package/dist/overlays/popover.d.ts +66 -0
- package/dist/overlays/popover.js +114 -0
- package/dist/overlays/sheet.d.ts +78 -0
- package/dist/overlays/sheet.js +131 -0
- package/dist/overlays/toast.d.ts +106 -0
- package/dist/overlays/toast.js +146 -0
- package/dist/overlays/tooltip.d.ts +57 -0
- package/dist/overlays/tooltip.js +95 -0
- package/dist/primitives/avatar.d.ts +51 -0
- package/dist/primitives/avatar.js +93 -0
- package/dist/primitives/button.d.ts +58 -0
- package/dist/primitives/button.js +104 -0
- package/dist/primitives/chip.d.ts +73 -0
- package/dist/primitives/chip.js +238 -0
- package/dist/primitives/icon-button.d.ts +41 -0
- package/dist/primitives/icon-button.js +59 -0
- package/dist/primitives/index.d.ts +10 -0
- package/dist/primitives/index.js +10 -0
- package/dist/primitives/kbd.d.ts +17 -0
- package/dist/primitives/kbd.js +24 -0
- package/dist/primitives/separator.d.ts +25 -0
- package/dist/primitives/separator.js +30 -0
- package/dist/primitives/skeleton.d.ts +20 -0
- package/dist/primitives/skeleton.js +27 -0
- package/dist/primitives/spinner.d.ts +26 -0
- package/dist/primitives/spinner.js +31 -0
- package/dist/primitives/status-dot.d.ts +51 -0
- package/dist/primitives/status-dot.js +57 -0
- package/dist/styles.css +97 -0
- package/dist/system/agent-skin.d.ts +27 -0
- package/dist/system/agent-skin.js +20 -0
- package/dist/system/density.d.ts +73 -0
- package/dist/system/density.js +111 -0
- package/dist/system/index.d.ts +12 -0
- package/dist/system/index.js +10 -0
- package/dist/system/overrides.d.ts +80 -0
- package/dist/system/overrides.js +277 -0
- package/dist/system/portal.d.ts +56 -0
- package/dist/system/portal.js +193 -0
- package/dist/system/prepaint.d.ts +134 -0
- package/dist/system/prepaint.js +120 -0
- package/dist/system/theme-provider.d.ts +290 -0
- package/dist/system/theme-provider.js +543 -0
- package/dist/system/theme-registry.d.ts +53 -0
- package/dist/system/theme-registry.js +66 -0
- package/dist/system/use-density.d.ts +50 -0
- package/dist/system/use-density.js +232 -0
- package/dist/system/use-isomorphic-layout-effect.js +33 -0
- package/dist/system/use-theme.d.ts +30 -0
- package/dist/system/use-theme.js +36 -0
- package/dist/system/vocabulary.d.ts +16 -0
- package/dist/system/vocabulary.js +125 -0
- package/dist/theming/contrast.d.ts +2 -0
- package/dist/theming/contrast.js +2 -0
- package/dist/theming/create-theme.d.ts +195 -0
- package/dist/theming/create-theme.js +524 -0
- package/dist/theming/index.d.ts +4 -0
- package/dist/theming/index.js +4 -0
- package/dist/theming/serialize.d.ts +167 -0
- package/dist/theming/serialize.js +241 -0
- package/manifest/components/accordion.json +173 -0
- package/manifest/components/activity-graph.json +104 -0
- package/manifest/components/agent-card.json +150 -0
- package/manifest/components/agent-handoff.json +104 -0
- package/manifest/components/agent-mode-badge.json +102 -0
- package/manifest/components/agent-pile.json +147 -0
- package/manifest/components/agent-plan.json +74 -0
- package/manifest/components/agent-status.json +87 -0
- package/manifest/components/agent-surface.json +93 -0
- package/manifest/components/alert-dialog.json +215 -0
- package/manifest/components/animated-number.json +75 -0
- package/manifest/components/app-bar.json +297 -0
- package/manifest/components/app-shell.json +526 -0
- package/manifest/components/app-window-frame.json +124 -0
- package/manifest/components/approval-card.json +136 -0
- package/manifest/components/artifact-card.json +93 -0
- package/manifest/components/ask-box.json +288 -0
- package/manifest/components/audience-icon.json +46 -0
- package/manifest/components/autocomplete.json +262 -0
- package/manifest/components/avatar-group.json +83 -0
- package/manifest/components/avatar.json +113 -0
- package/manifest/components/background-inbox.json +79 -0
- package/manifest/components/band.json +104 -0
- package/manifest/components/bento.json +306 -0
- package/manifest/components/branch-picker.json +146 -0
- package/manifest/components/breadcrumb.json +85 -0
- package/manifest/components/button-group.json +91 -0
- package/manifest/components/button.json +163 -0
- package/manifest/components/calendar.json +65 -0
- package/manifest/components/canvas-split-body.json +66 -0
- package/manifest/components/canvas-split-document.json +55 -0
- package/manifest/components/canvas-split-header.json +97 -0
- package/manifest/components/canvas-split-line.json +66 -0
- package/manifest/components/canvas-split-message.json +70 -0
- package/manifest/components/canvas-split-thread.json +55 -0
- package/manifest/components/canvas-split.json +61 -0
- package/manifest/components/card.json +152 -0
- package/manifest/components/carousel.json +242 -0
- package/manifest/components/catalogue-icon.json +46 -0
- package/manifest/components/channel-beta-icon.json +46 -0
- package/manifest/components/channel-matrix.json +239 -0
- package/manifest/components/channel-released-icon.json +46 -0
- package/manifest/components/chart-container.json +127 -0
- package/manifest/components/chart-ramp.json +82 -0
- package/manifest/components/chart-swatch.json +72 -0
- package/manifest/components/chart-tooltip-content.json +140 -0
- package/manifest/components/chart.json +117 -0
- package/manifest/components/chat-empty-state.json +55 -0
- package/manifest/components/chat-panel-assistant-message.json +52 -0
- package/manifest/components/chat-panel-composer.json +74 -0
- package/manifest/components/chat-panel-messages.json +52 -0
- package/manifest/components/chat-panel-typing.json +52 -0
- package/manifest/components/chat-panel-user-message.json +52 -0
- package/manifest/components/chat-panel.json +58 -0
- package/manifest/components/checkbox-group.json +70 -0
- package/manifest/components/checkbox.json +106 -0
- package/manifest/components/checkpoint-history.json +91 -0
- package/manifest/components/chip.json +174 -0
- package/manifest/components/clamp.json +125 -0
- package/manifest/components/cli-tool-icon.json +46 -0
- package/manifest/components/code-block.json +107 -0
- package/manifest/components/code-diff.json +104 -0
- package/manifest/components/code-runner.json +124 -0
- package/manifest/components/collapsible.json +133 -0
- package/manifest/components/color-area.json +62 -0
- package/manifest/components/color-field.json +91 -0
- package/manifest/components/color-picker.json +235 -0
- package/manifest/components/color-slider.json +84 -0
- package/manifest/components/color-swatch.json +79 -0
- package/manifest/components/colors-section.json +70 -0
- package/manifest/components/combobox.json +265 -0
- package/manifest/components/command-palette.json +222 -0
- package/manifest/components/compaction-row.json +128 -0
- package/manifest/components/comparison-card.json +98 -0
- package/manifest/components/composer-actions.json +65 -0
- package/manifest/components/composer-attach-button.json +65 -0
- package/manifest/components/composer-attachment-chip.json +83 -0
- package/manifest/components/composer-attachments.json +60 -0
- package/manifest/components/composer-bar.json +76 -0
- package/manifest/components/composer-command-item.json +85 -0
- package/manifest/components/composer-context.json +71 -0
- package/manifest/components/composer-input.json +77 -0
- package/manifest/components/composer-menu-item.json +75 -0
- package/manifest/components/composer-menu.json +89 -0
- package/manifest/components/composer-model-item.json +82 -0
- package/manifest/components/composer-model-trigger.json +81 -0
- package/manifest/components/composer-person-item.json +86 -0
- package/manifest/components/composer-send.json +86 -0
- package/manifest/components/composer-toolbar.json +65 -0
- package/manifest/components/composer-voice-button.json +71 -0
- package/manifest/components/composer-voice.json +81 -0
- package/manifest/components/composer.json +238 -0
- package/manifest/components/computer-use.json +98 -0
- package/manifest/components/confidence-marker.json +86 -0
- package/manifest/components/connection-state.json +103 -0
- package/manifest/components/container.json +75 -0
- package/manifest/components/context-breakdown.json +72 -0
- package/manifest/components/context-menu.json +134 -0
- package/manifest/components/context-usage.json +98 -0
- package/manifest/components/conversation-search.json +111 -0
- package/manifest/components/copy-button.json +118 -0
- package/manifest/components/cost-meter.json +84 -0
- package/manifest/components/cue-logotype.json +110 -0
- package/manifest/components/cue-mark.json +111 -0
- package/manifest/components/cue-portal-frame.json +65 -0
- package/manifest/components/data-row.json +222 -0
- package/manifest/components/data-table-pagination.json +97 -0
- package/manifest/components/data-table-toolbar.json +127 -0
- package/manifest/components/data-tree.json +185 -0
- package/manifest/components/date-field.json +93 -0
- package/manifest/components/date-picker.json +255 -0
- package/manifest/components/date-range-picker.json +255 -0
- package/manifest/components/day-separator.json +63 -0
- package/manifest/components/delegation-card.json +196 -0
- package/manifest/components/density.json +93 -0
- package/manifest/components/description-list.json +91 -0
- package/manifest/components/diagram.json +126 -0
- package/manifest/components/dialog.json +222 -0
- package/manifest/components/disclosure.json +134 -0
- package/manifest/components/dmx-bar.json +107 -0
- package/manifest/components/dmx-strip.json +117 -0
- package/manifest/components/document-reference.json +108 -0
- package/manifest/components/draft-restore.json +96 -0
- package/manifest/components/drawer.json +204 -0
- package/manifest/components/dropdown-menu.json +275 -0
- package/manifest/components/edit-message.json +131 -0
- package/manifest/components/elements-command-palette.json +122 -0
- package/manifest/components/elements-composer.json +71 -0
- package/manifest/components/elements-data-table.json +75 -0
- package/manifest/components/elements-timeline.json +77 -0
- package/manifest/components/elicitation-form.json +130 -0
- package/manifest/components/empty-state-composer.json +71 -0
- package/manifest/components/empty-state-greeting.json +49 -0
- package/manifest/components/empty-state-suggestion.json +60 -0
- package/manifest/components/empty-state-suggestions.json +49 -0
- package/manifest/components/empty-state.json +111 -0
- package/manifest/components/end-of-turn-summary.json +89 -0
- package/manifest/components/env-var-input.json +194 -0
- package/manifest/components/error-state.json +93 -0
- package/manifest/components/export-dialog.json +101 -0
- package/manifest/components/eyebrow.json +59 -0
- package/manifest/components/feedback-dialog.json +133 -0
- package/manifest/components/field-description.json +66 -0
- package/manifest/components/field-error.json +87 -0
- package/manifest/components/field-label.json +66 -0
- package/manifest/components/field.json +82 -0
- package/manifest/components/file-tree.json +96 -0
- package/manifest/components/file-upload.json +204 -0
- package/manifest/components/flow-background.json +50 -0
- package/manifest/components/flow-controls.json +50 -0
- package/manifest/components/flow-graph.json +86 -0
- package/manifest/components/folder-icon.json +46 -0
- package/manifest/components/footer.json +133 -0
- package/manifest/components/frac.json +68 -0
- package/manifest/components/frames.json +210 -0
- package/manifest/components/generation-loader.json +78 -0
- package/manifest/components/generative-ui.json +125 -0
- package/manifest/components/grid.json +132 -0
- package/manifest/components/group-bar.json +122 -0
- package/manifest/components/guardrail-notice.json +109 -0
- package/manifest/components/hover-card.json +189 -0
- package/manifest/components/icon-button.json +106 -0
- package/manifest/components/image-generation.json +73 -0
- package/manifest/components/info-tip.json +133 -0
- package/manifest/components/inline-citation.json +90 -0
- package/manifest/components/input-group.json +112 -0
- package/manifest/components/input.json +121 -0
- package/manifest/components/inspector.json +199 -0
- package/manifest/components/item.json +163 -0
- package/manifest/components/job-progress.json +116 -0
- package/manifest/components/kbd.json +52 -0
- package/manifest/components/launcher-bubble.json +130 -0
- package/manifest/components/ledger.json +330 -0
- package/manifest/components/link.json +94 -0
- package/manifest/components/live-region-announcer.json +95 -0
- package/manifest/components/log-viewer.json +172 -0
- package/manifest/components/map-answer.json +97 -0
- package/manifest/components/markdown-text.json +126 -0
- package/manifest/components/math-block.json +84 -0
- package/manifest/components/mcp-server-icon.json +46 -0
- package/manifest/components/mcp-server-panel.json +102 -0
- package/manifest/components/memory-chips.json +73 -0
- package/manifest/components/menubar.json +184 -0
- package/manifest/components/message-actions.json +125 -0
- package/manifest/components/message-attachments.json +71 -0
- package/manifest/components/message-branches.json +83 -0
- package/manifest/components/message-list.json +121 -0
- package/manifest/components/message-pair.json +107 -0
- package/manifest/components/message-queue.json +85 -0
- package/manifest/components/message-timing.json +69 -0
- package/manifest/components/message.json +149 -0
- package/manifest/components/meter.json +150 -0
- package/manifest/components/mobile-composer.json +163 -0
- package/manifest/components/model-picker.json +87 -0
- package/manifest/components/multi-select.json +255 -0
- package/manifest/components/musical-time-input.json +141 -0
- package/manifest/components/navigation-menu.json +269 -0
- package/manifest/components/node-card.json +144 -0
- package/manifest/components/node-handle.json +84 -0
- package/manifest/components/number-field.json +131 -0
- package/manifest/components/number-ticker.json +67 -0
- package/manifest/components/onboarding.json +100 -0
- package/manifest/components/otp-field.json +163 -0
- package/manifest/components/page-shell.json +129 -0
- package/manifest/components/pagination.json +108 -0
- package/manifest/components/panel-header.json +89 -0
- package/manifest/components/panel.json +71 -0
- package/manifest/components/password-input.json +171 -0
- package/manifest/components/permission-grant.json +115 -0
- package/manifest/components/permission-scopes.json +184 -0
- package/manifest/components/piano-keyboard.json +149 -0
- package/manifest/components/plussie.json +140 -0
- package/manifest/components/popover.json +231 -0
- package/manifest/components/preset-section.json +80 -0
- package/manifest/components/preview.json +294 -0
- package/manifest/components/progress.json +114 -0
- package/manifest/components/prompt-library.json +122 -0
- package/manifest/components/queue-dock.json +139 -0
- package/manifest/components/quota-banner.json +113 -0
- package/manifest/components/quote-reply.json +129 -0
- package/manifest/components/radio-group.json +83 -0
- package/manifest/components/radio.json +94 -0
- package/manifest/components/rating.json +188 -0
- package/manifest/components/read-aloud.json +139 -0
- package/manifest/components/reasoning-effort.json +95 -0
- package/manifest/components/reasoning-panel.json +135 -0
- package/manifest/components/recommendation-card.json +133 -0
- package/manifest/components/regenerate-menu.json +107 -0
- package/manifest/components/replay-player.json +124 -0
- package/manifest/components/research-report.json +87 -0
- package/manifest/components/resizable.json +91 -0
- package/manifest/components/retrieval-chunks.json +98 -0
- package/manifest/components/revert-dock.json +197 -0
- package/manifest/components/reviewable-diff.json +113 -0
- package/manifest/components/risk-badge.json +89 -0
- package/manifest/components/row.json +122 -0
- package/manifest/components/schedule-card.json +121 -0
- package/manifest/components/score-breakdown.json +108 -0
- package/manifest/components/scroll-anchor.json +84 -0
- package/manifest/components/scroll-area.json +114 -0
- package/manifest/components/scrollable-tabs-list.json +91 -0
- package/manifest/components/scrub-input.json +268 -0
- package/manifest/components/seam-cell.json +91 -0
- package/manifest/components/seam-grid.json +84 -0
- package/manifest/components/seam-list.json +56 -0
- package/manifest/components/search-input.json +195 -0
- package/manifest/components/section-header.json +140 -0
- package/manifest/components/segmented-control.json +178 -0
- package/manifest/components/select.json +253 -0
- package/manifest/components/separator.json +56 -0
- package/manifest/components/settings-panel.json +152 -0
- package/manifest/components/shape-section.json +70 -0
- package/manifest/components/shared-conversation.json +107 -0
- package/manifest/components/sheet.json +210 -0
- package/manifest/components/shimmer-label.json +61 -0
- package/manifest/components/sidebar.json +245 -0
- package/manifest/components/signal-edge.json +56 -0
- package/manifest/components/skeleton.json +46 -0
- package/manifest/components/skill-icon.json +46 -0
- package/manifest/components/slider.json +106 -0
- package/manifest/components/sources.json +97 -0
- package/manifest/components/sparkline.json +89 -0
- package/manifest/components/speaker-identity.json +66 -0
- package/manifest/components/spec-sheet.json +93 -0
- package/manifest/components/spectrum-visualizer.json +147 -0
- package/manifest/components/spinner.json +71 -0
- package/manifest/components/stack-icon.json +46 -0
- package/manifest/components/stack.json +143 -0
- package/manifest/components/stacks-matrix-icon.json +46 -0
- package/manifest/components/stat.json +111 -0
- package/manifest/components/status-bar.json +233 -0
- package/manifest/components/status-dot.json +97 -0
- package/manifest/components/stepper.json +107 -0
- package/manifest/components/stopped-run.json +97 -0
- package/manifest/components/streaming-text.json +82 -0
- package/manifest/components/sub.json +45 -0
- package/manifest/components/subagent-list.json +106 -0
- package/manifest/components/suggestions.json +106 -0
- package/manifest/components/sup.json +45 -0
- package/manifest/components/swap-label.json +86 -0
- package/manifest/components/switch.json +102 -0
- package/manifest/components/table-scroll-region.json +78 -0
- package/manifest/components/table.json +306 -0
- package/manifest/components/tabs.json +156 -0
- package/manifest/components/tags-input.json +255 -0
- package/manifest/components/tail-status.json +138 -0
- package/manifest/components/terminal-block.json +111 -0
- package/manifest/components/terminal-frame.json +146 -0
- package/manifest/components/textarea.json +104 -0
- package/manifest/components/theme-configurator.json +158 -0
- package/manifest/components/theme-provider.json +228 -0
- package/manifest/components/thinking-indicator.json +69 -0
- package/manifest/components/thread-list.json +87 -0
- package/manifest/components/thread-search.json +108 -0
- package/manifest/components/threshold-rail.json +223 -0
- package/manifest/components/time-boundary.json +68 -0
- package/manifest/components/time-field.json +93 -0
- package/manifest/components/timeline-ruler.json +178 -0
- package/manifest/components/timeline.json +131 -0
- package/manifest/components/title-bar.json +97 -0
- package/manifest/components/toast.json +140 -0
- package/manifest/components/todo-list.json +76 -0
- package/manifest/components/toggle-group.json +134 -0
- package/manifest/components/toggle.json +140 -0
- package/manifest/components/token-editor.json +106 -0
- package/manifest/components/tool-call-card.json +268 -0
- package/manifest/components/tool-call.json +147 -0
- package/manifest/components/tool-error.json +141 -0
- package/manifest/components/tool-group.json +97 -0
- package/manifest/components/tool-timeline.json +147 -0
- package/manifest/components/toolbar.json +192 -0
- package/manifest/components/tooltip.json +199 -0
- package/manifest/components/trace-waterfall.json +84 -0
- package/manifest/components/tree-visibility-toggle.json +99 -0
- package/manifest/components/tree.json +182 -0
- package/manifest/components/turn-footer.json +133 -0
- package/manifest/components/two-step-button.json +150 -0
- package/manifest/components/typing-indicator.json +60 -0
- package/manifest/components/universe-grid.json +155 -0
- package/manifest/components/unread-divider.json +58 -0
- package/manifest/components/usage-chart.json +162 -0
- package/manifest/components/verdict-row.json +106 -0
- package/manifest/components/voice-conversation.json +141 -0
- package/manifest/components/web-preview.json +105 -0
- package/manifest/components/web-search.json +108 -0
- package/manifest/components/work-collapse.json +125 -0
- package/manifest/fixtures.json +776 -0
- package/manifest/manifest.json +5037 -0
- package/manifest/tokens.json +288 -0
- 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
|