@cueplusplus/ui 0.20.1 → 0.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (183) hide show
  1. package/CHANGELOG.md +115 -0
  2. package/dist/chat/agent-pile.js +2 -2
  3. package/dist/chrome/_status-bar.js +1 -1
  4. package/dist/chrome/app-shell.js +1 -1
  5. package/dist/chrome/navigation-menu.js +3 -3
  6. package/dist/chrome/row.js +1 -1
  7. package/dist/chrome/scroll-area.js +1 -1
  8. package/dist/chrome/section-header.js +1 -1
  9. package/dist/chrome/segmented-control.js +1 -1
  10. package/dist/chrome/tabs.js +1 -1
  11. package/dist/chrome/toolbar.js +1 -1
  12. package/dist/chrome/tree.js +3 -3
  13. package/dist/date/calendar.js +3 -3
  14. package/dist/dmx/channel-matrix.js +1 -1
  15. package/dist/dmx/universe-grid.js +1 -1
  16. package/dist/elements/agent-handoff.js +3 -3
  17. package/dist/elements/agent-plan.js +1 -1
  18. package/dist/elements/agent-status.js +1 -1
  19. package/dist/elements/agent-surface.d.ts +9 -6
  20. package/dist/elements/agent-surface.js +3 -2
  21. package/dist/elements/approval-card.d.ts +29 -3
  22. package/dist/elements/approval-card.js +18 -8
  23. package/dist/elements/artifact-card.js +2 -2
  24. package/dist/elements/canvas-split.js +3 -3
  25. package/dist/elements/chart.js +1 -1
  26. package/dist/elements/chat-panel.js +2 -2
  27. package/dist/elements/checkpoint-history.js +1 -1
  28. package/dist/elements/code-diff.js +1 -1
  29. package/dist/elements/code-runner.js +1 -1
  30. package/dist/elements/composer.d.ts +8 -2
  31. package/dist/elements/composer.js +20 -12
  32. package/dist/elements/computer-use.js +2 -2
  33. package/dist/elements/confidence-marker.js +1 -1
  34. package/dist/elements/connection-state.js +2 -2
  35. package/dist/elements/context-breakdown.js +1 -1
  36. package/dist/elements/context-usage.js +1 -1
  37. package/dist/elements/conversation-search.js +2 -2
  38. package/dist/elements/cost-meter.js +1 -1
  39. package/dist/elements/data-table.js +1 -1
  40. package/dist/elements/diagram.js +1 -1
  41. package/dist/elements/draft-restore.js +2 -2
  42. package/dist/elements/edit-message.js +1 -1
  43. package/dist/elements/elements.css +134 -0
  44. package/dist/elements/elicitation-form.d.ts +8 -1
  45. package/dist/elements/elicitation-form.js +24 -16
  46. package/dist/elements/empty-state.js +3 -3
  47. package/dist/elements/error-state.js +2 -2
  48. package/dist/elements/event-feed.d.ts +88 -0
  49. package/dist/elements/event-feed.js +190 -0
  50. package/dist/elements/feedback-dialog.js +2 -2
  51. package/dist/elements/file-tree.js +1 -1
  52. package/dist/elements/flow-graph.js +2 -2
  53. package/dist/elements/generative.css +13 -0
  54. package/dist/elements/generative.d.ts +8 -6
  55. package/dist/elements/generative.js +4 -3
  56. package/dist/elements/image-generation.js +2 -2
  57. package/dist/elements/index.d.ts +7 -3
  58. package/dist/elements/index.js +5 -2
  59. package/dist/elements/inline-citation.js +1 -1
  60. package/dist/elements/job-progress.js +1 -1
  61. package/dist/elements/launcher-bubble.js +3 -3
  62. package/dist/elements/loading-state.js +1 -1
  63. package/dist/elements/map-answer.js +1 -1
  64. package/dist/elements/math-block.js +1 -1
  65. package/dist/elements/mcp-server-panel.js +3 -3
  66. package/dist/elements/memory-chips.js +1 -1
  67. package/dist/elements/message-attachment.js +3 -3
  68. package/dist/elements/message-branches.js +1 -1
  69. package/dist/elements/message-pair.js +2 -2
  70. package/dist/elements/message-queue.js +1 -1
  71. package/dist/elements/message-timing.js +1 -1
  72. package/dist/elements/mobile-composer.js +1 -1
  73. package/dist/elements/model-picker.js +1 -1
  74. package/dist/elements/number-ticker.js +1 -1
  75. package/dist/elements/onboarding.js +3 -3
  76. package/dist/elements/permission-grant.js +3 -3
  77. package/dist/elements/prompt-library.js +1 -1
  78. package/dist/elements/question-card.d.ts +50 -0
  79. package/dist/elements/question-card.js +117 -0
  80. package/dist/elements/queue-dock.js +1 -1
  81. package/dist/elements/quota-banner.js +1 -1
  82. package/dist/elements/quote-reply.js +3 -3
  83. package/dist/elements/read-aloud.js +2 -2
  84. package/dist/elements/reasoning-effort.js +2 -2
  85. package/dist/elements/reasoning-panel.js +3 -3
  86. package/dist/elements/recommendation-card.js +2 -2
  87. package/dist/elements/regenerate-menu.js +1 -1
  88. package/dist/elements/request-delivery.js +31 -0
  89. package/dist/elements/research-report.js +1 -1
  90. package/dist/elements/retrieval-chunks.js +3 -3
  91. package/dist/elements/revert-dock.js +1 -1
  92. package/dist/elements/reviewable-diff.js +4 -4
  93. package/dist/elements/schedule-card.js +2 -2
  94. package/dist/elements/score-breakdown.js +2 -2
  95. package/dist/elements/scroll-anchor.js +2 -2
  96. package/dist/elements/settings-panel.js +3 -3
  97. package/dist/elements/sources.js +1 -1
  98. package/dist/elements/spec-sheet.js +1 -1
  99. package/dist/elements/stopped-run.js +2 -2
  100. package/dist/elements/streaming-text.js +2 -2
  101. package/dist/elements/subagent-list.js +4 -4
  102. package/dist/elements/suggestions.js +1 -1
  103. package/dist/elements/surfaces.d.ts +6 -6
  104. package/dist/elements/surfaces.js +7 -7
  105. package/dist/elements/tail-status.js +1 -1
  106. package/dist/elements/terminal-block.js +1 -1
  107. package/dist/elements/thinking-indicator.js +1 -1
  108. package/dist/elements/timeline.js +1 -1
  109. package/dist/elements/todo-list.js +1 -1
  110. package/dist/elements/tool-call.d.ts +18 -2
  111. package/dist/elements/tool-call.js +20 -11
  112. package/dist/elements/tool-error.js +2 -2
  113. package/dist/elements/tool-group.d.ts +11 -3
  114. package/dist/elements/tool-group.js +62 -38
  115. package/dist/elements/tool-state.d.ts +13 -0
  116. package/dist/elements/tool-state.js +44 -0
  117. package/dist/elements/tool-timeline.js +2 -2
  118. package/dist/elements/trace-waterfall.js +2 -2
  119. package/dist/elements/unsupported-request-card.d.ts +31 -0
  120. package/dist/elements/unsupported-request-card.js +80 -0
  121. package/dist/elements/vocabulary.d.ts +50 -1
  122. package/dist/elements/vocabulary.js +50 -1
  123. package/dist/elements/voice-conversation.js +5 -5
  124. package/dist/elements/web-preview.js +1 -1
  125. package/dist/elements/web-search.js +2 -2
  126. package/dist/elements/work-collapse.js +1 -1
  127. package/dist/forms/file-upload.js +1 -1
  128. package/dist/forms/rating.js +1 -1
  129. package/dist/forms/select.js +1 -1
  130. package/dist/forms/switch.js +2 -2
  131. package/dist/forms/toggle.js +1 -1
  132. package/dist/index.d.ts +3 -1
  133. package/dist/index.js +3 -1
  134. package/dist/instruments/_ledger-disclosure.js +1 -1
  135. package/dist/instruments/data-tree.js +1 -1
  136. package/dist/instruments/meter.js +1 -1
  137. package/dist/instruments/seam-grid.js +1 -1
  138. package/dist/layout/_collapse.js +3 -3
  139. package/dist/layout/bento.js +1 -1
  140. package/dist/layout/breadcrumb.js +1 -1
  141. package/dist/layout/carousel.js +1 -1
  142. package/dist/layout/collapsible.js +1 -1
  143. package/dist/layout/index.d.ts +3 -1
  144. package/dist/layout/index.js +3 -1
  145. package/dist/layout/item.js +1 -1
  146. package/dist/layout/link.js +1 -1
  147. package/dist/layout/pagination.js +1 -1
  148. package/dist/layout/pane-seam.d.ts +75 -0
  149. package/dist/layout/pane-seam.js +127 -0
  150. package/dist/layout/resizable.d.ts +2 -1
  151. package/dist/layout/resizable.js +3 -2
  152. package/dist/layout/sidebar.js +3 -3
  153. package/dist/layout/split-pane.d.ts +74 -0
  154. package/dist/layout/split-pane.js +135 -0
  155. package/dist/layout/stepper.js +1 -1
  156. package/dist/lib/auto-grow.js +42 -0
  157. package/dist/overlays/_surface.d.ts +4 -4
  158. package/dist/overlays/_surface.js +4 -4
  159. package/dist/overlays/drawer.js +1 -1
  160. package/dist/overlays/menubar.js +1 -1
  161. package/dist/overlays/sheet.js +1 -1
  162. package/dist/overlays/toast.js +1 -1
  163. package/dist/primitives/button.js +1 -1
  164. package/dist/primitives/chip.d.ts +1 -1
  165. package/dist/system/agent-skin.d.ts +11 -5
  166. package/manifest/components/agent-surface.json +5 -4
  167. package/manifest/components/approval-card.json +45 -7
  168. package/manifest/components/composer-input.json +16 -2
  169. package/manifest/components/elicitation-form.json +16 -1
  170. package/manifest/components/event-feed.json +171 -0
  171. package/manifest/components/event-row.json +116 -0
  172. package/manifest/components/generative-ui.json +4 -3
  173. package/manifest/components/pane-seam.json +171 -0
  174. package/manifest/components/question-card.json +157 -0
  175. package/manifest/components/resizable.json +1 -1
  176. package/manifest/components/split-pane.json +224 -0
  177. package/manifest/components/tool-call.json +46 -2
  178. package/manifest/components/tool-group.json +15 -3
  179. package/manifest/components/unsupported-request-card.json +117 -0
  180. package/manifest/fixtures.json +16 -0
  181. package/manifest/manifest.json +88 -10
  182. package/manifest/tokens.json +9 -2
  183. package/package.json +2 -2
package/CHANGELOG.md CHANGED
@@ -1,5 +1,120 @@
1
1
  # @cueplusplus/ui
2
2
 
3
+ ## 0.21.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 4476be5: **`ComposerInput` takes a `size`.** `md`, the default, is the composer as it was. `sm` is the
8
+ smaller rung for a compact console: a 36px floor, tighter padding and body type. The element states
9
+ its size as `data-size`, so a caller no longer overrides two utilities to get a smaller field.
10
+ - 46b191e: **`ComposerInput` holds more than one line.** It is a `<textarea>` now. It grows with what it holds,
11
+ to a cap: `field-sizing: content` where the browser has it, and measured where it does not. Enter
12
+ still sends, Shift+Enter breaks the line, and accepting an IME candidate still does neither.
13
+
14
+ ### Migrating
15
+ - **Its props are a textarea's.** `ComponentProps<"textarea">` replaces `ComponentProps<"input">`.
16
+ A `ref` typed `HTMLInputElement`, or an `onChange` handler typed
17
+ `ChangeEvent<HTMLInputElement>`, becomes `HTMLTextAreaElement`. Input-only attributes such as
18
+ `type` no longer apply.
19
+ - **Enter's default is prevented before `onSubmit` runs**, so sending never adds a newline. A
20
+ handler of your own that calls `event.preventDefault()` on Enter still stops the send, as before.
21
+ - **A `ref` you pass replaces the one that measures the field.** It then grows by CSS alone,
22
+ which covers every browser that supports `field-sizing`.
23
+
24
+ - 90c02ab: **`AgentSurface` can wear Cursor's agent pane: `skin="cursor"`.** Before this, Cursor's palette was
25
+ only a private gallery study, so a consumer that wanted the look copied the values and owned the drift.
26
+ `@cueplusplus/ui/elements.css` now carries them under `[data-cue-skin="cursor"]`, in the same three
27
+ blocks assistant-ui's skin uses. The light and dark halves follow `data-mode`, including `system`.
28
+
29
+ - **Colour at every fidelity.** The skin re-points every token the elements family reads, including
30
+ Cursor's own status hues, and draws the focus ring in Cursor's blue.
31
+ - **Corners and faces at `fidelity="upstream"`.** A 6 / 8 / 12px radius ladder, and Inter and
32
+ Geist Mono first in the font stacks. The library ships neither face: load them yourself, or
33
+ the stacks fall through to the platform's. Cursor's type sizes and spacing were never recorded,
34
+ so those stay cue's at both fidelities.
35
+ - **`GenerativeUI`** takes `skin="cursor"` too, with Cursor's corners on its card, field and button.
36
+
37
+ The study's `--clone-*` marks (the user bubble, the brand mark, the git colours) are not part of the
38
+ skin. Nothing in the library reads them. A consumer carrying a copy of the study can drop the
39
+ palette and wrap the subtree in `<AgentSurface skin="cursor">` instead.
40
+
41
+ - 74b66a8: **`EventFeed` and `EventRow`: a filterable activity feed** (#103), in `@cueplusplus/ui/elements`. Each
42
+ `FeedEvent` is `{ id, kind, title, preview?, timestamp, tone? }`, drawn as a time, a toned dot, the kind,
43
+ the title and a one-line preview. The filter chips are built from the kinds present, in the order each
44
+ first appears and with its count, controlled (`filter`, `onFilterChange`) or not. The rows are a
45
+ listbox: j and k, or the arrows, move the selection through the rows the filter keeps, and Home and End
46
+ jump to the ends. The detail stays the host's, for `SplitPane`'s detail slot. `EventRow` draws one event
47
+ for a list of your own: it is an `option` inside a feed, takes `role="option"` in a listbox of yours, and
48
+ claims no role drawn alone. The copy is `EVENT_FEED_LABELS`.
49
+ - 74b66a8: **`PaneSeam`: one accessible seam that resizes one pane, with no dependency** (cue-ui-55). It ships from
50
+ the core barrel, so a side pane no longer needs the optional `react-resizable-panels` peer. It owns a
51
+ number, not a layout: apply `value` to the pane's width or height.
52
+
53
+ It is a focusable `role="separator"` that says its size and its bounds (`aria-valuenow`, `-valuemin`,
54
+ `-valuemax`) under a required `label`. The arrows move it by `step` (16px), Shift by `largeStep` (64px),
55
+ and Home and End jump to `min` and `max`. Pointer drags use pointer capture, and every value is clamped.
56
+ `edge` says which side the pane is on and flips under `dir="rtl"`; `orientation="horizontal"` resizes a
57
+ height. `Resizable` is unchanged.
58
+
59
+ - b60b39e: **The request cards offer only the decisions the agent offered, and say where an answer has got to**
60
+ (cue-ui-57).
61
+
62
+ - **`ApprovalCard` takes `options` and `onDecide`.** Each option is an `{ id, label, tone? }` and draws
63
+ one button, in order, so the card offers exactly what the agent listed. Without `options` it draws
64
+ the default buttons as before, except **Always allow, which is now drawn only when `onAlwaysAllow` is
65
+ set**. A card that offered a standing grant nothing could honour was making a false statement.
66
+ - **`delivery` on `ApprovalCard`, `ElicitationForm` and the new `QuestionCard`**: `sending`, `sent` or
67
+ `failed`. While the answer is on its way, and once it has arrived, the card offers nothing to press,
68
+ so it cannot answer twice. `failed` says so and offers the decisions again. `sent` says the answer
69
+ arrived and does not claim the tool then ran: an outcome `state` still says that, and wins once set.
70
+ - **`QuestionCard`**, for a free-text question. Enter sends, Shift+Enter breaks the line, and Skip is
71
+ drawn only when `onDismiss` is set. A failed delivery puts the answer back in the field.
72
+ - **`UnsupportedRequestCard`**, for a request this client cannot answer. It says so, prints the
73
+ request's `kind` in mono, and offers no answer.
74
+ - The copy is data: `DELIVERY_LABELS`, `QUESTION_LABELS` and `UNSUPPORTED_REQUEST_LABELS`, with
75
+ `REQUEST_DELIVERIES` and the `RequestDelivery` type.
76
+
77
+ ### Migrating
78
+ - **A card that relied on Always allow being drawn without a handler** loses that button. It did
79
+ nothing when pressed. Pass `onAlwaysAllow` to keep it.
80
+
81
+ - 74b66a8: **`SplitPane`: a list beside its detail, stacking into a drill-in when narrow** (#102). Side by side while
82
+ the detail would be at least twice the list, measured on the frame with a `ResizeObserver` so it works in
83
+ any column; `stackBelow` names a width instead. When stacked it shows the list, or, with `detailOpen`, the
84
+ detail under a `Back to …` button, and Escape inside the detail calls `onBack` too. The list resizes on a
85
+ `PaneSeam` (`listSize`, `minListSize`, `maxListSize`), and `collapsible` folds it to a thin tab. Selection
86
+ and the list's own keyboard stay with the list.
87
+ - 605d3a3: **Tool calls say how they ended, draw inline, and name their empty results.**
88
+
89
+ - `ToolCall` takes `state`: `running`, `done`, `failed` or `refused`. Each settled state ends the
90
+ row with its own mark: a check, a cross in the danger ink, or a muted ban. A failed call no
91
+ longer shows the success check. `running` still works, read only when `state` is absent, and is
92
+ deprecated.
93
+ - `ToolGroup`'s `GroupedToolState` gains `refused`, counted in the header like `failed`. While a
94
+ call runs, a collapsed group keeps its own label and shows the running command on a faint line
95
+ under it, rather than rewriting the header on every argument.
96
+ - `ToolCall` and `ToolGroup` take `variant="inline"`: a denser row that spans its column, with no
97
+ card or paper, so a transcript of many calls reads as a list. `card` stays the default.
98
+ - `ToolCall` takes `absence`: `none`, `refused` or `unanswered`. An empty `result` then says
99
+ "No result", "Refused" or "Not answered" instead of one blank panel.
100
+ - `ToolState` and `ToolAbsence` are exported from `@cueplusplus/ui/elements`.
101
+ - `@cueplusplus/ui/elements/markdown` still needs only `react-markdown` and `rehype-sanitize`.
102
+
103
+ ### Patch Changes
104
+
105
+ - 6c80e70: **A duration ladder.** `@cueplusplus/tokens` declares seven motion tokens, `--cue-duration-instant`
106
+ (100ms), `-fast` (150ms), `-brisk` (200ms), `-base` (300ms), `-slow` (500ms), `-slower` (700ms) and
107
+ `-slowest` (1000ms), and bridges each into Tailwind, so `duration-fast` is a utility that reads the
108
+ token at the element. Override one in your CSS and every component that spends that rung retimes
109
+ with it.
110
+
111
+ `@cueplusplus/ui` now spends those rungs everywhere it used a numeric `duration-*`, at the same
112
+ values, so nothing moves on screen. `--cue-transition-fast` and `--cue-transition-base` are
113
+ unchanged.
114
+
115
+ - Updated dependencies [6c80e70]
116
+ - @cueplusplus/tokens@0.21.0
117
+
3
118
  ## 0.20.1
4
119
 
5
120
  ### Patch Changes
@@ -72,7 +72,7 @@ const AgentPile = React.forwardRef(function AgentPile({ className, agents, root,
72
72
  "data-working": agent.working === true ? "" : void 0,
73
73
  "data-asking": agent.asking === true ? "" : void 0,
74
74
  "data-selected": selected === agent.slug ? "" : void 0,
75
- className: cn("relative inline-flex rounded-full ring-1 ring-bg transition-[margin] duration-150 ease-out motion-reduce:transition-none", index === 0 ? null : spacing, agent.working === true ? "z-10" : null),
75
+ className: cn("relative inline-flex rounded-full ring-1 ring-bg transition-[margin] duration-fast ease-out motion-reduce:transition-none", index === 0 ? null : spacing, agent.working === true ? "z-10" : null),
76
76
  style: { "--agent-color": agentColor(agent.slug, { root }) },
77
77
  children: [
78
78
  onSelect === void 0 ? /* @__PURE__ */ jsx("span", {
@@ -113,7 +113,7 @@ const AgentPile = React.forwardRef(function AgentPile({ className, agents, root,
113
113
  }, agent.slug);
114
114
  }), overflow > 0 ? /* @__PURE__ */ jsx("span", {
115
115
  "data-slot": "agent-pile-overflow",
116
- className: cn("relative inline-flex shrink-0 items-center justify-center rounded-full border border-border bg-surface-3 font-mono leading-none text-fg-muted ring-1 ring-bg transition-[margin] duration-150 ease-out motion-reduce:transition-none", AVATAR_SIZES[size], spacing),
116
+ className: cn("relative inline-flex shrink-0 items-center justify-center rounded-full border border-border bg-surface-3 font-mono leading-none text-fg-muted ring-1 ring-bg transition-[margin] duration-fast ease-out motion-reduce:transition-none", AVATAR_SIZES[size], spacing),
117
117
  children: `+${overflow}`
118
118
  }) : null]
119
119
  });
@@ -64,7 +64,7 @@ const ITEM_BASE = "inline-flex min-w-0 shrink-0 items-center gap-(--cue-space-2)
64
64
  * never match `:disabled` — the pseudo-class is defined on form controls — so
65
65
  * without the data twin a disabled link would read as perfectly live.
66
66
  */
67
- const INTERACTIVE_CHASSIS = `h-full cursor-pointer border-0 bg-transparent py-0 ${LABEL_FACE} text-(length:--cue-text-label) outline-none transition-colors duration-150 ${FOCUS_RING_INSET} disabled:pointer-events-none disabled:opacity-40 data-[disabled]:pointer-events-none data-[disabled]:opacity-40 motion-reduce:transition-none`;
67
+ const INTERACTIVE_CHASSIS = `h-full cursor-pointer border-0 bg-transparent py-0 ${LABEL_FACE} text-(length:--cue-text-label) outline-none transition-colors duration-fast ${FOCUS_RING_INSET} disabled:pointer-events-none disabled:opacity-40 data-[disabled]:pointer-events-none data-[disabled]:opacity-40 motion-reduce:transition-none`;
68
68
  /**
69
69
  * A quiet control's gutter, and the negative margin that pays for it.
70
70
  *
@@ -144,7 +144,7 @@ const AppShell = {
144
144
  brand === void 0 ? null : /* @__PURE__ */ jsx("div", {
145
145
  "data-slot": "app-shell-brand",
146
146
  ...dragProps,
147
- className: cn("flex w-auto min-w-0 shrink-0 items-center gap-(--cue-space-3) border-r border-border px-(--cue-pad-row-x) transition-[width] duration-150 ease-out md:w-(--cue-nav-width) motion-reduce:transition-none", dragClasses, brandClassName),
147
+ className: cn("flex w-auto min-w-0 shrink-0 items-center gap-(--cue-space-3) border-r border-border px-(--cue-pad-row-x) transition-[width] duration-fast ease-out md:w-(--cue-nav-width) motion-reduce:transition-none", dragClasses, brandClassName),
148
148
  children: /* @__PURE__ */ jsx("div", {
149
149
  "data-slot": "app-shell-brand-content",
150
150
  className: clusterClasses,
@@ -67,10 +67,10 @@ const NavigationMenu$1 = {
67
67
  return /* @__PURE__ */ jsxs(NavigationMenu.Trigger, {
68
68
  ref,
69
69
  "data-slot": "navigation-menu-trigger",
70
- className: cn(`inline-flex ${NAV_TRIGGER_HEIGHT} cursor-default items-center gap-(--cue-space-2) rounded-control ${NAV_TRIGGER_PAD} ${NAV_TRIGGER_VOICE} whitespace-nowrap ${NAV_TRIGGER_INK} no-underline transition-colors duration-150 outline-none select-none ${NAV_TRIGGER_HOVER_INK} ${FOCUS_RING_INSET} ${NAV_OPEN_GROUND} data-[popup-open]:text-fg`, className),
70
+ className: cn(`inline-flex ${NAV_TRIGGER_HEIGHT} cursor-default items-center gap-(--cue-space-2) rounded-control ${NAV_TRIGGER_PAD} ${NAV_TRIGGER_VOICE} whitespace-nowrap ${NAV_TRIGGER_INK} no-underline transition-colors duration-fast outline-none select-none ${NAV_TRIGGER_HOVER_INK} ${FOCUS_RING_INSET} ${NAV_OPEN_GROUND} data-[popup-open]:text-fg`, className),
71
71
  ...elementProps,
72
72
  children: [children, caret ? /* @__PURE__ */ jsx(NavigationMenu.Icon, {
73
- className: "flex items-center transition-transform duration-150 data-[popup-open]:rotate-180 motion-reduce:transition-none",
73
+ className: "flex items-center transition-transform duration-fast data-[popup-open]:rotate-180 motion-reduce:transition-none",
74
74
  children: /* @__PURE__ */ jsx(CaretDownGlyph, { className: `size-icon-sm ${NAV_CARET_INK}` })
75
75
  }) : null]
76
76
  });
@@ -89,7 +89,7 @@ const NavigationMenu$1 = {
89
89
  "data-slot": "navigation-menu-link",
90
90
  "data-active": active === true ? "" : void 0,
91
91
  "aria-current": active === true ? "page" : void 0,
92
- className: cn(`flex cursor-pointer flex-col gap-(--cue-space-1) rounded-control px-(--cue-space-3) py-(--cue-space-2) text-(length:--cue-text-ui) text-fg-muted no-underline transition-colors duration-150 outline-none select-none hover:bg-surface-3 hover:text-fg ${FOCUS_RING_INSET} data-[active]:text-accent`, CONTROL_FACE, className),
92
+ className: cn(`flex cursor-pointer flex-col gap-(--cue-space-1) rounded-control px-(--cue-space-3) py-(--cue-space-2) text-(length:--cue-text-ui) text-fg-muted no-underline transition-colors duration-fast outline-none select-none hover:bg-surface-3 hover:text-fg ${FOCUS_RING_INSET} data-[active]:text-accent`, CONTROL_FACE, className),
93
93
  ...elementProps,
94
94
  children: [/* @__PURE__ */ jsx("span", { children }), description === void 0 ? null : /* @__PURE__ */ jsx("span", {
95
95
  className: "text-(length:--cue-text-label) leading-(--cue-leading-ui) text-fg-subtle",
@@ -29,7 +29,7 @@ const rowVariants = cva(`flex w-full items-center gap-(--cue-space-3) px-(--cue-
29
29
  false: `border-t border-border first:border-t-0 [[data-slot=panel-header]+&]:border-t-0 ${ROW_RULE_STYLE} ${ROW_BLOCK_PAD}`
30
30
  },
31
31
  interactive: {
32
- true: `cursor-pointer text-left transition-colors duration-150 outline-none hover:bg-surface-2 ${FOCUS_RING_INSET_2} disabled:pointer-events-none disabled:opacity-40`,
32
+ true: `cursor-pointer text-left transition-colors duration-fast outline-none hover:bg-surface-2 ${FOCUS_RING_INSET_2} disabled:pointer-events-none disabled:opacity-40`,
33
33
  false: ""
34
34
  }
35
35
  },
@@ -12,7 +12,7 @@ import { ScrollArea } from "@base-ui/react/scroll-area";
12
12
  * A console packs panels edge to edge, and a permanent 12px trough on every one
13
13
  * of them is 12px of chrome per panel that says nothing.
14
14
  */
15
- const SCROLLBAR_CLASSES = `flex touch-none justify-center rounded-full opacity-0 transition-opacity delay-300 duration-150 select-none data-[hovering]:opacity-100 data-[hovering]:delay-0 data-[scrolling]:opacity-100 data-[scrolling]:delay-0 motion-reduce:transition-none ${SCROLLBAR_SIZE_H} data-[orientation=horizontal]:flex-col ${SCROLLBAR_SIZE_V}`;
15
+ const SCROLLBAR_CLASSES = `flex touch-none justify-center rounded-full opacity-0 transition-opacity delay-300 duration-fast select-none data-[hovering]:opacity-100 data-[hovering]:delay-0 data-[scrolling]:opacity-100 data-[scrolling]:delay-0 motion-reduce:transition-none ${SCROLLBAR_SIZE_H} data-[orientation=horizontal]:flex-col ${SCROLLBAR_SIZE_V}`;
16
16
  /**
17
17
  * A scrolling region with the system's own scrollbar instead of the platform's.
18
18
  *
@@ -40,7 +40,7 @@ const SectionHeader = React.forwardRef(function SectionHeader({ className, label
40
40
  const headContent = /* @__PURE__ */ jsxs(Fragment, { children: [
41
41
  collapsible ? /* @__PURE__ */ jsx(ChevronRightGlyph, {
42
42
  "data-slot": "section-header-chevron",
43
- className: cn("size-icon-sm shrink-0 text-fg-subtle transition-transform duration-150 motion-reduce:transition-none", open ? "rotate-90" : "rotate-0")
43
+ className: cn("size-icon-sm shrink-0 text-fg-subtle transition-transform duration-fast motion-reduce:transition-none", open ? "rotate-90" : "rotate-0")
44
44
  }) : null,
45
45
  /* @__PURE__ */ jsx("span", {
46
46
  "data-slot": "section-header-label",
@@ -28,7 +28,7 @@ const ITEM_SIZES = {
28
28
  * track and its inset — through the segment names: `_style-segmented-control.ts`
29
29
  * for the pressed segment, and `_style-segment.ts` for the half `Tabs` shares.
30
30
  */
31
- const ITEM_CLASSES = `inline-flex shrink-0 cursor-pointer select-none items-center justify-center gap-(--cue-space-2) whitespace-nowrap ${SEGMENT_RADIUS} ${CONTROL_FACE} ${SEGMENT_IDLE_INK} transition-colors duration-150 outline-none hover:text-fg ${SEGMENT_PRESSED} ${FOCUS_RING_INSET} disabled:pointer-events-none disabled:opacity-40 data-[disabled]:pointer-events-none data-[disabled]:opacity-40`;
31
+ const ITEM_CLASSES = `inline-flex shrink-0 cursor-pointer select-none items-center justify-center gap-(--cue-space-2) whitespace-nowrap ${SEGMENT_RADIUS} ${CONTROL_FACE} ${SEGMENT_IDLE_INK} transition-colors duration-fast outline-none hover:text-fg ${SEGMENT_PRESSED} ${FOCUS_RING_INSET} disabled:pointer-events-none disabled:opacity-40 data-[disabled]:pointer-events-none data-[disabled]:opacity-40`;
32
32
  /**
33
33
  * A one-of-N switch drawn as a row of segments: the console's view picker.
34
34
  *
@@ -49,7 +49,7 @@ const TabsTab = React.forwardRef(function TabsTab({ className, ...elementProps }
49
49
  return /* @__PURE__ */ jsx(Tabs.Tab, {
50
50
  ref,
51
51
  "data-slot": "tabs-tab",
52
- className: cn(`relative inline-flex h-control-md shrink-0 cursor-pointer items-center justify-center gap-(--cue-space-2) ${TAB_SHAPE} ${SEGMENT_PAD} text-(length:--cue-text-ui) whitespace-nowrap ${SEGMENT_IDLE_INK} transition-colors duration-150 outline-none select-none`, CONTROL_FACE, `${TAB_HOVER_INK} ${FOCUS_RING_INSET_2}`, "data-[disabled]:pointer-events-none data-[disabled]:opacity-40", "after:absolute after:inset-x-0 after:-bottom-px after:h-px after:bg-transparent after:content-['']", `${TAB_ACTIVE} ${TAB_UNDERLINE}`, className),
52
+ className: cn(`relative inline-flex h-control-md shrink-0 cursor-pointer items-center justify-center gap-(--cue-space-2) ${TAB_SHAPE} ${SEGMENT_PAD} text-(length:--cue-text-ui) whitespace-nowrap ${SEGMENT_IDLE_INK} transition-colors duration-fast outline-none select-none`, CONTROL_FACE, `${TAB_HOVER_INK} ${FOCUS_RING_INSET_2}`, "data-[disabled]:pointer-events-none data-[disabled]:opacity-40", "after:absolute after:inset-x-0 after:-bottom-px after:h-px after:bg-transparent after:content-['']", `${TAB_ACTIVE} ${TAB_UNDERLINE}`, className),
53
53
  ...elementProps
54
54
  });
55
55
  });
@@ -57,7 +57,7 @@ const Toolbar$1 = {
57
57
  return /* @__PURE__ */ jsxs(Toolbar.Button, {
58
58
  ref,
59
59
  "data-slot": "toolbar-button",
60
- className: cn(`inline-flex h-control-md shrink-0 cursor-pointer items-center justify-center gap-(--cue-space-2) rounded-control px-(--cue-space-3) font-mono text-(length:--cue-text-ui) whitespace-nowrap ${GHOST_INK} transition-colors duration-150 outline-none select-none hover:bg-surface-2 hover:text-fg ${FOCUS_RING_INSET} data-[disabled]:pointer-events-none data-[disabled]:opacity-40`, className),
60
+ className: cn(`inline-flex h-control-md shrink-0 cursor-pointer items-center justify-center gap-(--cue-space-2) rounded-control px-(--cue-space-3) font-mono text-(length:--cue-text-ui) whitespace-nowrap ${GHOST_INK} transition-colors duration-fast outline-none select-none hover:bg-surface-2 hover:text-fg ${FOCUS_RING_INSET} data-[disabled]:pointer-events-none data-[disabled]:opacity-40`, className),
61
61
  ...elementProps,
62
62
  children: [Icon === void 0 ? null : /* @__PURE__ */ jsx(Icon, {
63
63
  "aria-hidden": "true",
@@ -163,7 +163,7 @@ const Tree = React.forwardRef(function Tree({ className, items, expanded: expand
163
163
  setExpanded(item.id, !row.expanded);
164
164
  },
165
165
  className: "flex size-icon-sm shrink-0 cursor-pointer items-center justify-center text-fg-subtle",
166
- children: /* @__PURE__ */ jsx(ChevronRightGlyph, { className: cn("size-icon-sm transition-transform duration-150 motion-reduce:transition-none", row.expanded ? "rotate-90" : null) })
166
+ children: /* @__PURE__ */ jsx(ChevronRightGlyph, { className: cn("size-icon-sm transition-transform duration-fast motion-reduce:transition-none", row.expanded ? "rotate-90" : null) })
167
167
  }) : /* @__PURE__ */ jsx("span", {
168
168
  "aria-hidden": "true",
169
169
  className: "size-icon-sm shrink-0"
@@ -190,7 +190,7 @@ const Tree = React.forwardRef(function Tree({ className, items, expanded: expand
190
190
  rowControls === void 0 ? null : /* @__PURE__ */ jsx("span", {
191
191
  "data-slot": "tree-row-controls",
192
192
  onClick: (event) => event.stopPropagation(),
193
- className: "flex shrink-0 items-center gap-(--cue-space-1) opacity-0 transition-opacity duration-150 group-hover/tree-row:opacity-100 group-focus-within/tree-row:opacity-100 motion-reduce:transition-none",
193
+ className: "flex shrink-0 items-center gap-(--cue-space-1) opacity-0 transition-opacity duration-fast group-hover/tree-row:opacity-100 group-focus-within/tree-row:opacity-100 motion-reduce:transition-none",
194
194
  children: rowControls(item, row)
195
195
  })
196
196
  ]
@@ -240,7 +240,7 @@ const TreeVisibilityToggle = React.forwardRef(function TreeVisibilityToggle({ cl
240
240
  if (event.defaultPrevented) return;
241
241
  onValueChange(NEXT_VISIBILITY[value]);
242
242
  },
243
- className: cn(`flex size-icon-md shrink-0 cursor-pointer items-center justify-center rounded-control border-0 bg-transparent p-0 outline-none transition-colors duration-150 hover:text-fg ${FOCUS_RING} motion-reduce:transition-none data-[state=visible]:text-fg-subtle data-[state=ghost]:text-info data-[state=hidden]:text-warn`, className),
243
+ className: cn(`flex size-icon-md shrink-0 cursor-pointer items-center justify-center rounded-control border-0 bg-transparent p-0 outline-none transition-colors duration-fast hover:text-fg ${FOCUS_RING} motion-reduce:transition-none data-[state=visible]:text-fg-subtle data-[state=ghost]:text-info data-[state=hidden]:text-warn`, className),
244
244
  ...elementProps,
245
245
  children: /* @__PURE__ */ jsx(Glyph, { className: "size-icon-sm" })
246
246
  });
@@ -35,15 +35,15 @@ const CALENDAR_CLASSES = {
35
35
  month_caption: `flex ${DAY_HEIGHT} items-center justify-center`,
36
36
  caption_label: `${LABEL_FACE} ${CAPTION_SIZE_LABEL} ${CAPTION_WEIGHT} ${LABEL_TRACKING} text-fg-subtle ${LABEL_CASE}`,
37
37
  nav: `absolute inset-x-0 top-0 flex ${DAY_HEIGHT} items-center justify-between`,
38
- button_previous: `flex ${DAY_SIZE} cursor-pointer items-center justify-center rounded-control border-0 bg-transparent p-0 ${GLYPH_INK_SUBTLE} outline-none transition-colors duration-150 hover:text-fg ${FOCUS_RING} disabled:pointer-events-none disabled:opacity-30 motion-reduce:transition-none`,
39
- button_next: `flex ${DAY_SIZE} cursor-pointer items-center justify-center rounded-control border-0 bg-transparent p-0 ${GLYPH_INK_SUBTLE} outline-none transition-colors duration-150 hover:text-fg ${FOCUS_RING} disabled:pointer-events-none disabled:opacity-30 motion-reduce:transition-none`,
38
+ button_previous: `flex ${DAY_SIZE} cursor-pointer items-center justify-center rounded-control border-0 bg-transparent p-0 ${GLYPH_INK_SUBTLE} outline-none transition-colors duration-fast hover:text-fg ${FOCUS_RING} disabled:pointer-events-none disabled:opacity-30 motion-reduce:transition-none`,
39
+ button_next: `flex ${DAY_SIZE} cursor-pointer items-center justify-center rounded-control border-0 bg-transparent p-0 ${GLYPH_INK_SUBTLE} outline-none transition-colors duration-fast hover:text-fg ${FOCUS_RING} disabled:pointer-events-none disabled:opacity-30 motion-reduce:transition-none`,
40
40
  chevron: `size-icon-sm fill-current ${GLYPH_OPACITY}`,
41
41
  month_grid: "border-collapse border-spacing-0",
42
42
  weekdays: "",
43
43
  weekday: `${DAY_SIZE} p-0 text-center align-middle ${LABEL_FACE} ${CAPTION_SIZE_MICRO} ${CAPTION_WEIGHT_NORMAL} ${LABEL_TRACKING_TIGHT} text-fg-subtle ${LABEL_CASE}`,
44
44
  week: "",
45
45
  day: "p-0 text-center align-middle",
46
- day_button: `${DAY_SIZE} cursor-pointer rounded-control border-0 bg-transparent p-0 font-mono text-(length:--cue-text-ui) tabular-nums ${DAY_INK} outline-none transition-colors duration-150 hover:bg-surface-3 hover:text-fg ${FOCUS_RING} disabled:pointer-events-none disabled:opacity-30 motion-reduce:transition-none`,
46
+ day_button: `${DAY_SIZE} cursor-pointer rounded-control border-0 bg-transparent p-0 font-mono text-(length:--cue-text-ui) tabular-nums ${DAY_INK} outline-none transition-colors duration-fast hover:bg-surface-3 hover:text-fg ${FOCUS_RING} disabled:pointer-events-none disabled:opacity-30 motion-reduce:transition-none`,
47
47
  today: "[&_button]:font-medium [&_button]:ring-1 [&_button]:ring-inset [&_button]:ring-accent/60",
48
48
  outside: "[&_button]:text-fg-subtle [&_button]:opacity-50",
49
49
  disabled: "opacity-40",
@@ -280,7 +280,7 @@ function ControlButton({ pressed, onClick, children }) {
280
280
  type: "button",
281
281
  "aria-pressed": pressed,
282
282
  onClick,
283
- className: `flex h-control-sm cursor-pointer items-center rounded-control px-(--cue-space-3) font-mono text-(length:--cue-text-label) text-fg-muted outline-none transition-colors duration-150 hover:text-fg ${FOCUS_RING} aria-pressed:bg-accent-soft aria-pressed:text-accent motion-reduce:transition-none`,
283
+ className: `flex h-control-sm cursor-pointer items-center rounded-control px-(--cue-space-3) font-mono text-(length:--cue-text-label) text-fg-muted outline-none transition-colors duration-fast hover:text-fg ${FOCUS_RING} aria-pressed:bg-accent-soft aria-pressed:text-accent motion-reduce:transition-none`,
284
284
  children
285
285
  });
286
286
  }
@@ -111,7 +111,7 @@ const UniverseGrid = React.forwardRef(function UniverseGrid({ className, allocat
111
111
  "data-conflict": block.conflict ? "true" : void 0,
112
112
  "aria-label": `${block.label}: channels ${block.start} to ${block.end}`,
113
113
  onClick: () => onSelect(block.allocation),
114
- className: cn(`absolute inset-y-0 min-w-[2px] cursor-pointer rounded-[2px] outline-none transition-opacity duration-150 hover:opacity-85 ${FOCUS_RING} motion-reduce:transition-none`, BLOCK_TONES[block.tone], block.conflict ? "z-10 ring-1 ring-danger" : void 0),
114
+ className: cn(`absolute inset-y-0 min-w-[2px] cursor-pointer rounded-[2px] outline-none transition-opacity duration-fast hover:opacity-85 ${FOCUS_RING} motion-reduce:transition-none`, BLOCK_TONES[block.tone], block.conflict ? "z-10 ring-1 ring-danger" : void 0),
115
115
  style: {
116
116
  left: `${block.left}%`,
117
117
  width: `${block.width}%`
@@ -22,12 +22,12 @@ function AgentHandoff({ from, to, reason, carried, settled, className, ...props
22
22
  className: "flex items-center gap-2",
23
23
  children: [
24
24
  /* @__PURE__ */ jsxs("span", {
25
- className: cn(field, "text-fg-muted flex items-center gap-1.5 rounded-full px-2.5 py-1 text-(length:--cue-text-ui) transition-opacity duration-500", settled && "opacity-45"),
25
+ className: cn(field, "text-fg-muted flex items-center gap-1.5 rounded-full px-2.5 py-1 text-(length:--cue-text-ui) transition-opacity duration-slow", settled && "opacity-45"),
26
26
  children: [/* @__PURE__ */ jsx(BotIcon, { className: "size-3" }), from]
27
27
  }),
28
- /* @__PURE__ */ jsx(ArrowRightIcon, { className: cn("size-3.5 shrink-0 transition-colors duration-500", settled ? "text-fg-subtle" : "text-stream") }),
28
+ /* @__PURE__ */ jsx(ArrowRightIcon, { className: cn("size-3.5 shrink-0 transition-colors duration-slow", settled ? "text-fg-subtle" : "text-stream") }),
29
29
  /* @__PURE__ */ jsxs("span", {
30
- className: cn("flex items-center gap-1.5 rounded-full px-2.5 py-1 text-(length:--cue-text-ui) transition-colors duration-500", settled ? cn(field, "text-fg-muted") : "bg-stream/15 text-stream"),
30
+ className: cn("flex items-center gap-1.5 rounded-full px-2.5 py-1 text-(length:--cue-text-ui) transition-colors duration-slow", settled ? cn(field, "text-fg-muted") : "bg-stream/15 text-stream"),
31
31
  children: [/* @__PURE__ */ jsx(BotIcon, { className: "size-3" }), to]
32
32
  })
33
33
  ]
@@ -43,7 +43,7 @@ function AgentPlan({ steps, activeIndex, className, ...props }) {
43
43
  /* @__PURE__ */ jsx("div", {
44
44
  className: "bg-surface-2 h-[3px] w-full overflow-hidden rounded-full",
45
45
  children: /* @__PURE__ */ jsx("span", {
46
- className: "bg-accent block h-full rounded-full transition-[width] duration-500",
46
+ className: "bg-accent block h-full rounded-full transition-[width] duration-slow",
47
47
  style: { width: `${progress}%` }
48
48
  })
49
49
  }),
@@ -26,7 +26,7 @@ function AgentStatus({ state, label, elapsed, className, ...props }) {
26
26
  className: cn("size-1.5 shrink-0 rounded-full motion-reduce:animate-none", state === "working" ? "animate-pulse bg-stream" : "bg-surface-3 animate-pulse")
27
27
  }),
28
28
  /* @__PURE__ */ jsx("span", {
29
- className: "cue-elements-fade-in cue-elements-blur-in-2 cue-elements-animate-in max-w-44 truncate text-(length:--cue-text-ui) duration-300 motion-reduce:animate-none",
29
+ className: "cue-elements-fade-in cue-elements-blur-in-2 cue-elements-animate-in max-w-44 truncate text-(length:--cue-text-ui) duration-base motion-reduce:animate-none",
30
30
  children: label
31
31
  }, label),
32
32
  elapsed !== void 0 && state !== "done" && /* @__PURE__ */ jsx("span", {
@@ -4,14 +4,16 @@ import { useRender } from "@base-ui/react/use-render";
4
4
  //#region src/elements/agent-surface.d.ts
5
5
  interface AgentSurfaceProps extends Omit<React.ComponentPropsWithoutRef<"div">, "children"> {
6
6
  /**
7
- * Which value-set paints the subtree: `cue` (the default) or `aui`, which
8
- * installs assistant-ui's own inks, grounds and hues.
7
+ * Which value-set paints the subtree: `cue` (the default); `aui`, which
8
+ * installs assistant-ui's own inks, grounds and hues; or `cursor`, Cursor's
9
+ * agent-pane values. Both halves of each follow `data-mode`.
9
10
  */
10
11
  skin?: AgentSkin;
11
12
  /**
12
13
  * How much a supersede island re-points: `cue-metrics` (the default, colour
13
- * only) or `upstream`, which also pins assistant-ui's radii, literal type
14
- * sizes and spacing so the density ladder stops at the island edge.
14
+ * only) or `upstream`, which also pins the skin's own geometry. For `aui`
15
+ * that is its radii, literal type sizes and spacing, so the density ladder
16
+ * stops at the island edge; for `cursor`, its corners and faces.
15
17
  */
16
18
  fidelity?: AgentFidelity;
17
19
  /**
@@ -38,8 +40,9 @@ interface AgentSurfaceProps extends Omit<React.ComponentPropsWithoutRef<"div">,
38
40
  * `data-density` are untouched, and a light app keeps a light island. And it is
39
41
  * not needed for cue mode: the elements were rewritten onto cue's tokens by the
40
42
  * vendoring codemod, so `skin="cue"` is what they already do. Reach for this to
41
- * show an element the way assistant-ui drew it, or to hand a chat panel back to
42
- * upstream's design language inside a console that is otherwise cue's.
43
+ * show an element the way assistant-ui drew it, to hand a chat panel back to
44
+ * upstream's design language inside a console that is otherwise cue's, or to
45
+ * wear Cursor's agent pane with `skin="cursor"` rather than copying its palette.
43
46
  *
44
47
  * What an `aui` island re-points is this family's own vocabulary rather than the
45
48
  * whole token contract: `--cue-busy`, `--cue-info`, `--cue-selection`,
@@ -20,8 +20,9 @@ import { useRender } from "@base-ui/react/use-render";
20
20
  * `data-density` are untouched, and a light app keeps a light island. And it is
21
21
  * not needed for cue mode: the elements were rewritten onto cue's tokens by the
22
22
  * vendoring codemod, so `skin="cue"` is what they already do. Reach for this to
23
- * show an element the way assistant-ui drew it, or to hand a chat panel back to
24
- * upstream's design language inside a console that is otherwise cue's.
23
+ * show an element the way assistant-ui drew it, to hand a chat panel back to
24
+ * upstream's design language inside a console that is otherwise cue's, or to
25
+ * wear Cursor's agent pane with `skin="cursor"` rather than copying its palette.
25
26
  *
26
27
  * What an `aui` island re-points is this family's own vocabulary rather than the
27
28
  * whole token contract: `--cue-busy`, `--cue-info`, `--cue-selection`,
@@ -1,3 +1,4 @@
1
+ import { RequestDelivery } from "./vocabulary.js";
1
2
  import { ComponentProps } from "react";
2
3
  //#region src/elements/approval-card.d.ts
3
4
  /**
@@ -5,6 +6,15 @@ import { ComponentProps } from "react";
5
6
  * `done` or `denied`.
6
7
  */
7
8
  type ApprovalState = "request" | "running" | "done" | "denied";
9
+ /** One decision the agent offered, drawn as one button. */
10
+ interface ApprovalOption {
11
+ /** What `onDecide` is called with. The agent's own id for the decision. */
12
+ id: string;
13
+ /** What the button says. */
14
+ label: string;
15
+ /** `primary` draws the filled button; `quiet`, the default, the plain one. */
16
+ tone?: "primary" | "quiet";
17
+ }
8
18
  /**
9
19
  * Human in the loop: the agent asks before it runs anything with side effects.
10
20
  *
@@ -20,7 +30,7 @@ type ApprovalState = "request" | "running" | "done" | "denied";
20
30
  * onDeny={() => deny()}
21
31
  * />
22
32
  */
23
- declare function ApprovalCard({ state, command, title, subtitle, onAllowOnce, onAlwaysAllow, onDeny, className, ...props }: Omit<ComponentProps<"div">, "children" | "state" | "command" | "title" | "subtitle" | "onAllowOnce" | "onAlwaysAllow" | "onDeny"> & {
33
+ declare function ApprovalCard({ state, command, title, subtitle, onAllowOnce, onAlwaysAllow, onDeny, options, onDecide, delivery, className, ...props }: Omit<ComponentProps<"div">, "children" | "state" | "command" | "title" | "subtitle" | "onAllowOnce" | "onAlwaysAllow" | "onDeny" | "options" | "onDecide" | "delivery"> & {
24
34
  /** Lifecycle of the card: request with actions, running spinner, done check, or denied. */
25
35
  state: ApprovalState;
26
36
  /** Command shown in the mono field the agent wants to run. */
@@ -31,10 +41,26 @@ declare function ApprovalCard({ state, command, title, subtitle, onAllowOnce, on
31
41
  subtitle: string;
32
42
  /** Called when the user clicks Allow once. */
33
43
  onAllowOnce?: () => void;
34
- /** Called when the user clicks Always allow. */
44
+ /**
45
+ * Called when the user clicks Always allow. The button is drawn only when this is
46
+ * set, so a card never offers a standing grant nothing can honour.
47
+ */
35
48
  onAlwaysAllow?: () => void;
36
49
  /** Called when the user clicks Deny. */
37
50
  onDeny?: () => void;
51
+ /**
52
+ * The decisions the agent offered, one button each and in this order. Replaces the
53
+ * default Deny, Always allow and Allow once.
54
+ */
55
+ options?: readonly ApprovalOption[];
56
+ /** Called with an option's `id` when the user picks it. */
57
+ onDecide?: (id: string) => void;
58
+ /**
59
+ * Where the answer has got to: `sending`, `sent` or `failed`. While it is on its way
60
+ * and once it has arrived, the card offers nothing to press. `failed` offers the
61
+ * decisions again. An outcome `state` wins over it.
62
+ */
63
+ delivery?: RequestDelivery;
38
64
  }): import("react").JSX.Element;
39
65
  //#endregion
40
- export { ApprovalCard, ApprovalState };
66
+ export { ApprovalCard, ApprovalOption, ApprovalState };
@@ -1,6 +1,7 @@
1
1
  "use client";
2
2
  import { cn } from "../lib/cn.js";
3
3
  import { field, inkButton, paper } from "./surfaces.js";
4
+ import { DeliveryNote } from "./request-delivery.js";
4
5
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
5
6
  import { CheckIcon, Loader2Icon, TerminalIcon, XIcon } from "lucide-react";
6
7
  //#region src/elements/approval-card.tsx
@@ -19,7 +20,7 @@ import { CheckIcon, Loader2Icon, TerminalIcon, XIcon } from "lucide-react";
19
20
  * onDeny={() => deny()}
20
21
  * />
21
22
  */
22
- function ApprovalCard({ state, command, title, subtitle, onAllowOnce, onAlwaysAllow, onDeny, className, ...props }) {
23
+ function ApprovalCard({ state, command, title, subtitle, onAllowOnce, onAlwaysAllow, onDeny, options, onDecide, delivery, className, ...props }) {
23
24
  return /* @__PURE__ */ jsxs("div", {
24
25
  "data-slot": "approval-card",
25
26
  className: cn(paper, "flex w-full max-w-sm flex-col gap-3.5 rounded-overlay p-4", className),
@@ -46,18 +47,21 @@ function ApprovalCard({ state, command, title, subtitle, onAllowOnce, onAlwaysAl
46
47
  children: command
47
48
  }),
48
49
  /* @__PURE__ */ jsx("div", {
49
- className: "flex h-8 items-center justify-end gap-2",
50
- children: state === "request" ? /* @__PURE__ */ jsxs(Fragment, { children: [
50
+ className: "flex min-h-8 flex-wrap items-center justify-end gap-2",
51
+ children: state === "request" && (delivery === "sending" || delivery === "sent") ? /* @__PURE__ */ jsx(DeliveryNote, { delivery }) : state === "request" ? /* @__PURE__ */ jsxs(Fragment, { children: [delivery === "failed" ? /* @__PURE__ */ jsx(DeliveryNote, {
52
+ delivery: "failed",
53
+ className: "me-auto"
54
+ }) : null, options === void 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
51
55
  /* @__PURE__ */ jsx("button", {
52
56
  type: "button",
53
57
  onClick: onDeny,
54
- className: "text-fg-muted hover:bg-surface-2 hover:text-fg h-8 rounded-full px-3.5 text-(length:--cue-text-ui) font-medium transition-[background-color,color,scale] duration-150 active:scale-[0.96]",
58
+ className: "text-fg-muted hover:bg-surface-2 hover:text-fg h-8 rounded-full px-3.5 text-(length:--cue-text-ui) font-medium transition-[background-color,color,scale] duration-fast active:scale-[0.96]",
55
59
  children: "Deny"
56
60
  }),
57
- /* @__PURE__ */ jsx("button", {
61
+ onAlwaysAllow === void 0 ? null : /* @__PURE__ */ jsx("button", {
58
62
  type: "button",
59
63
  onClick: onAlwaysAllow,
60
- className: "text-fg-muted hover:bg-surface-2 hover:text-fg h-8 rounded-full px-3.5 text-(length:--cue-text-ui) font-medium transition-[background-color,color,scale] duration-150 active:scale-[0.96]",
64
+ className: "text-fg-muted hover:bg-surface-2 hover:text-fg h-8 rounded-full px-3.5 text-(length:--cue-text-ui) font-medium transition-[background-color,color,scale] duration-fast active:scale-[0.96]",
61
65
  children: "Always allow"
62
66
  }),
63
67
  /* @__PURE__ */ jsx("button", {
@@ -66,8 +70,14 @@ function ApprovalCard({ state, command, title, subtitle, onAllowOnce, onAlwaysAl
66
70
  className: cn(inkButton, "flex h-8 items-center rounded-full px-3.5 text-(length:--cue-text-ui) font-medium"),
67
71
  children: "Allow once"
68
72
  })
69
- ] }) : /* @__PURE__ */ jsx("div", {
70
- className: "cue-elements-fade-in cue-elements-animate-in text-fg-muted flex items-center gap-2 text-(length:--cue-text-ui) duration-300",
73
+ ] }) : options.map((option) => /* @__PURE__ */ jsx("button", {
74
+ type: "button",
75
+ "data-tone": option.tone ?? "quiet",
76
+ onClick: () => onDecide?.(option.id),
77
+ className: option.tone === "primary" ? cn(inkButton, "flex h-8 items-center rounded-full px-3.5 text-(length:--cue-text-ui) font-medium") : "text-fg-muted hover:bg-surface-2 hover:text-fg h-8 rounded-full px-3.5 text-(length:--cue-text-ui) font-medium transition-[background-color,color,scale] duration-fast active:scale-[0.96]",
78
+ children: option.label
79
+ }, option.id))] }) : /* @__PURE__ */ jsx("div", {
80
+ className: "cue-elements-fade-in cue-elements-animate-in text-fg-muted flex items-center gap-2 text-(length:--cue-text-ui) duration-base",
71
81
  children: state === "running" ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Loader2Icon, { className: "text-fg-muted size-3.5 animate-spin" }), "Approved, running"] }) : state === "denied" ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(XIcon, { className: "text-fg-muted size-3.5" }), "Denied"] }) : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(CheckIcon, { className: "size-3.5 text-ok" }), "Finished with exit 0"] })
72
82
  }, state)
73
83
  })
@@ -20,7 +20,7 @@ import { ArrowUpRightIcon, FileTextIcon } from "lucide-react";
20
20
  function ArtifactCard({ title, meta, generating = false, words = 0, className, ...props }) {
21
21
  return /* @__PURE__ */ jsxs("div", {
22
22
  "data-slot": "artifact-card",
23
- className: cn(paper, "group flex w-full max-w-xs cursor-pointer items-center gap-3 rounded-overlay p-3.5 transition-transform duration-150 hover:-translate-y-px active:scale-[0.98]", className),
23
+ className: cn(paper, "group flex w-full max-w-xs cursor-pointer items-center gap-3 rounded-overlay p-3.5 transition-transform duration-fast hover:-translate-y-px active:scale-[0.98]", className),
24
24
  ...props,
25
25
  children: [
26
26
  /* @__PURE__ */ jsx("span", {
@@ -46,7 +46,7 @@ function ArtifactCard({ title, meta, generating = false, words = 0, className, .
46
46
  })
47
47
  ]
48
48
  }) : /* @__PURE__ */ jsx("p", {
49
- className: cn(mono, "cue-elements-fade-in cue-elements-blur-in-2 cue-elements-animate-in text-fg-subtle duration-300 motion-reduce:animate-none"),
49
+ className: cn(mono, "cue-elements-fade-in cue-elements-blur-in-2 cue-elements-animate-in text-fg-subtle duration-base motion-reduce:animate-none"),
50
50
  children: meta
51
51
  })]
52
52
  }),
@@ -54,7 +54,7 @@ function CanvasSplitMessage({ speaker, className, ...props }) {
54
54
  return /* @__PURE__ */ jsx("div", {
55
55
  "data-slot": "canvas-split-message",
56
56
  "data-speaker": speaker,
57
- className: cn("cue-elements-fade-in cue-elements-animate-in cue-elements-fill-mode-both text-(length:--cue-text-body) leading-(--cue-leading-ui) duration-300", speaker === "user" ? cn(field, "text-fg-muted ms-auto rounded-overlay px-3 py-2") : "text-fg-muted", className),
57
+ className: cn("cue-elements-fade-in cue-elements-animate-in cue-elements-fill-mode-both text-(length:--cue-text-body) leading-(--cue-leading-ui) duration-base", speaker === "user" ? cn(field, "text-fg-muted ms-auto rounded-overlay px-3 py-2") : "text-fg-muted", className),
58
58
  ...props
59
59
  });
60
60
  }
@@ -86,7 +86,7 @@ function CanvasSplitHeader({ title, version, saved, onCopy, onClose, className,
86
86
  children: ["v", version]
87
87
  }),
88
88
  /* @__PURE__ */ jsx("span", {
89
- className: cn(mono, "shrink-0 transition-colors duration-300", saved ? "text-ok" : "text-fg-subtle"),
89
+ className: cn(mono, "shrink-0 transition-colors duration-base", saved ? "text-ok" : "text-fg-subtle"),
90
90
  children: saved ? /* @__PURE__ */ jsxs("span", {
91
91
  className: "flex items-center gap-1",
92
92
  children: [/* @__PURE__ */ jsx(CheckIcon, { className: "size-3" }), "saved"]
@@ -130,7 +130,7 @@ function CanvasSplitBody({ writing, className, children, ...props }) {
130
130
  function CanvasSplitLine({ heading, className, ...props }) {
131
131
  return /* @__PURE__ */ jsx("p", {
132
132
  "data-slot": "canvas-split-line",
133
- className: cn("cue-elements-fade-in cue-elements-slide-in-from-bottom-1 cue-elements-animate-in cue-elements-fill-mode-both text-(length:--cue-text-body) leading-(--cue-leading-ui) duration-300", heading ? "text-fg font-medium" : "text-fg-muted", className),
133
+ className: cn("cue-elements-fade-in cue-elements-slide-in-from-bottom-1 cue-elements-animate-in cue-elements-fill-mode-both text-(length:--cue-text-body) leading-(--cue-leading-ui) duration-base", heading ? "text-fg font-medium" : "text-fg-muted", className),
134
134
  ...props
135
135
  });
136
136
  }
@@ -85,7 +85,7 @@ function Chart({ label, value, delta, points, visibleCount, variant = "area", cl
85
85
  width: barWidth,
86
86
  height: Math.max(1, 82 - top),
87
87
  rx: "1.5",
88
- className: cn("cue-elements-fade-in cue-elements-animate-in cue-elements-fill-mode-both duration-300", i === lastIndex ? "fill-stream" : "fill-fg-subtle"),
88
+ className: cn("cue-elements-fade-in cue-elements-animate-in cue-elements-fill-mode-both duration-base", i === lastIndex ? "fill-stream" : "fill-fg-subtle"),
89
89
  style: { animationDelay: `${i * 40}ms` }
90
90
  }, i);
91
91
  }) : /* @__PURE__ */ jsxs(Fragment, { children: [
@@ -46,7 +46,7 @@ function ChatPanelMessages({ className, ...props }) {
46
46
  function ChatPanelUserMessage({ className, ...props }) {
47
47
  return /* @__PURE__ */ jsx("div", {
48
48
  "data-slot": "chat-panel-user-message",
49
- className: cn(field, "cue-elements-fade-in cue-elements-slide-in-from-bottom-1 cue-elements-animate-in self-end rounded-overlay px-3 py-1.5 text-(length:--cue-text-ui) duration-300", className),
49
+ className: cn(field, "cue-elements-fade-in cue-elements-slide-in-from-bottom-1 cue-elements-animate-in self-end rounded-overlay px-3 py-1.5 text-(length:--cue-text-ui) duration-base", className),
50
50
  ...props
51
51
  });
52
52
  }
@@ -62,7 +62,7 @@ function ChatPanelAssistantMessage({ className, ...props }) {
62
62
  function ChatPanelTyping({ className, ...props }) {
63
63
  return /* @__PURE__ */ jsx("div", {
64
64
  "data-slot": "chat-panel-typing",
65
- className: cn("cue-elements-fade-in cue-elements-animate-in flex gap-1 self-start px-1 duration-300", className),
65
+ className: cn("cue-elements-fade-in cue-elements-animate-in flex gap-1 self-start px-1 duration-base", className),
66
66
  ...props,
67
67
  children: [
68
68
  "-0.32s",