zidane-ui 9.1.4 → 9.1.5

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 (240) hide show
  1. package/dist/adapters/server-extensions-host.js +1 -1
  2. package/dist/adapters/server-host.js +2 -1
  3. package/dist/adapters/server-machines-host.js +1 -1
  4. package/dist/adapters/server-session-host.js +87 -13
  5. package/dist/adapters/server-settings-host.js +1 -1
  6. package/dist/appearance/AppearanceProvider.js +2 -2
  7. package/dist/appearance/headless/index.js +1 -1
  8. package/dist/approvals/headless/index.js +2 -2
  9. package/dist/chat/HostTranscript.d.ts +5 -1
  10. package/dist/chat/HostTranscript.js +16 -4
  11. package/dist/chat/RunEstimateChip.d.ts +5 -3
  12. package/dist/chat/RunEstimateChip.js +33 -3
  13. package/dist/chat/Transcript.js +1 -1
  14. package/dist/chat/composer/ComposerEditor.js +1 -1
  15. package/dist/chat/composer/ConversationBadges.js +16 -10
  16. package/dist/chat/composer/composer-bus.d.ts +2 -2
  17. package/dist/chat/composer/composer-bus.js +2 -2
  18. package/dist/chat/composer/use-composer-stores.js +1 -1
  19. package/dist/chat/composer/use-run-meter.js +1 -1
  20. package/dist/chat/headless/composer/conversation-badges.d.ts +8 -3
  21. package/dist/chat/headless/composer/conversation-badges.js +26 -11
  22. package/dist/chat/headless/composer/index.js +2 -2
  23. package/dist/chat/headless/composer/labels-chrome.d.ts +3 -1
  24. package/dist/chat/headless/composer/labels-chrome.js +3 -1
  25. package/dist/chat/headless/index.d.ts +5 -3
  26. package/dist/chat/headless/index.js +16 -14
  27. package/dist/chat/headless/labels.d.ts +54 -1
  28. package/dist/chat/headless/labels.js +62 -2
  29. package/dist/chat/headless/secrets.d.ts +32 -0
  30. package/dist/chat/headless/secrets.js +43 -0
  31. package/dist/chat/headless/session-list.d.ts +45 -0
  32. package/dist/chat/headless/sidebar/estimates.js +13 -1
  33. package/dist/chat/headless/sidebar/index.d.ts +2 -2
  34. package/dist/chat/headless/sidebar/labels.d.ts +50 -0
  35. package/dist/chat/headless/sidebar/labels.js +1 -0
  36. package/dist/chat/headless/sidebar/query.d.ts +2 -2
  37. package/dist/chat/headless/sidebar/query.js +6 -3
  38. package/dist/chat/headless/sidebar/types.d.ts +7 -1
  39. package/dist/chat/headless/sidebar/view.js +2 -0
  40. package/dist/chat/headless/transcript/auth-failure.d.ts +67 -0
  41. package/dist/chat/headless/transcript/auth-failure.js +87 -0
  42. package/dist/chat/headless/transcript/index.d.ts +2 -1
  43. package/dist/chat/headless/transcript/index.js +2 -1
  44. package/dist/chat/headless/transcript/nesting.d.ts +3 -3
  45. package/dist/chat/headless/transcript/nesting.js +12 -4
  46. package/dist/chat/index.d.ts +4 -2
  47. package/dist/chat/index.js +3 -1
  48. package/dist/chat/interactions/InboxPanel.d.ts +6 -2
  49. package/dist/chat/interactions/InboxPanel.js +82 -44
  50. package/dist/chat/interactions/PlanCard.d.ts +2 -2
  51. package/dist/chat/interactions/PlanCard.js +1 -1
  52. package/dist/chat/interactions/QuestionField.d.ts +8 -3
  53. package/dist/chat/interactions/QuestionField.js +89 -8
  54. package/dist/chat/interactions/QuestionForm.d.ts +3 -1
  55. package/dist/chat/interactions/QuestionForm.js +48 -10
  56. package/dist/chat/interactions/SecretInput.d.ts +32 -0
  57. package/dist/chat/interactions/SecretInput.js +72 -0
  58. package/dist/chat/interactions/card-ui.d.ts +5 -3
  59. package/dist/chat/interactions/card-ui.js +5 -3
  60. package/dist/chat/interactions/index.d.ts +2 -1
  61. package/dist/chat/interactions/index.js +2 -1
  62. package/dist/chat/sidebar/SessionSidebar.js +2 -0
  63. package/dist/chat/sidebar/SidebarFilterMenu.d.ts +7 -2
  64. package/dist/chat/sidebar/SidebarFilterMenu.js +26 -2
  65. package/dist/chat/sidebar/SidebarSessionRow.d.ts +3 -1
  66. package/dist/chat/sidebar/SidebarSessionRow.js +8 -3
  67. package/dist/chat/sidebar/sidebar-live.js +8 -4
  68. package/dist/chat/transcript/AuthFailureCard.d.ts +34 -0
  69. package/dist/chat/transcript/AuthFailureCard.js +358 -0
  70. package/dist/chat/transcript/InteractionToolBody.js +12 -2
  71. package/dist/chat/transcript/actions.d.ts +24 -1
  72. package/dist/chat/transcript/index.d.ts +3 -2
  73. package/dist/chat/transcript/index.js +2 -1
  74. package/dist/chat/transcript/notices.d.ts +2 -1
  75. package/dist/chat/transcript/notices.js +13 -1
  76. package/dist/chat/transcript/script-input.d.ts +1 -1
  77. package/dist/chat/transcript/shell-block.js +1 -1
  78. package/dist/components/HowItWorks.d.ts +8 -1
  79. package/dist/components/HowItWorks.js +2 -2
  80. package/dist/components/RowActionsMenu.d.ts +6 -1
  81. package/dist/components/RowActionsMenu.js +4 -0
  82. package/dist/core.css +24 -0
  83. package/dist/dist/{index-WlE93Q6d.d.ts → index-BI6RZc60.d.ts} +36 -3
  84. package/dist/dist/index-f-XWIoYo.d.ts +1 -0
  85. package/dist/dist/index.d.ts +6 -0
  86. package/dist/dist/sandbox-B4OW6oNx.d.ts +2 -0
  87. package/dist/dist/timeout-B5pO5I5a.d.ts +30 -0
  88. package/dist/extensions/ExtensionSettingsPage.js +1 -1
  89. package/dist/extensions/ExtensionSettingsSidebar.js +1 -1
  90. package/dist/extensions/headless/fixture-host.js +1 -1
  91. package/dist/extensions/headless/index.js +4 -4
  92. package/dist/extensions/hooks.js +1 -1
  93. package/dist/files/DiffsTab.js +1 -1
  94. package/dist/files/EditorColumn.js +1 -1
  95. package/dist/files/MediaDiff.js +1 -1
  96. package/dist/files/SearchPanel.js +121 -53
  97. package/dist/files/headless/name-match.js +29 -14
  98. package/dist/files/useEntryActions.js +1 -1
  99. package/dist/files/useOpenFiles.js +1 -1
  100. package/dist/git/WherePicker.js +2 -2
  101. package/dist/git/headless/environment.d.ts +6 -1
  102. package/dist/git/headless/environment.js +5 -2
  103. package/dist/git/headless/labels.d.ts +2 -0
  104. package/dist/git/headless/labels.js +2 -0
  105. package/dist/git/index.d.ts +2 -1
  106. package/dist/git/index.js +2 -1
  107. package/dist/git/useStartFromPanel.d.ts +22 -0
  108. package/dist/git/useStartFromPanel.js +91 -0
  109. package/dist/keybindings/KeyCommands.js +1 -1
  110. package/dist/keybindings/KeybindingsProvider.js +1 -1
  111. package/dist/keybindings/headless/catalog.js +2 -2
  112. package/dist/keybindings/headless/index.js +2 -2
  113. package/dist/keybindings/headless/overrides.js +1 -1
  114. package/dist/machines/HostSyncPanel.js +1 -1
  115. package/dist/machines/SyncOutcomeBadge.js +1 -1
  116. package/dist/machines/headless/index.js +1 -1
  117. package/dist/onboarding/demos/features.d.ts +9 -1
  118. package/dist/onboarding/demos/features.js +107 -2
  119. package/dist/onboarding/demos/how-it-works.d.ts +5 -1
  120. package/dist/onboarding/demos/how-it-works.js +120 -2
  121. package/dist/onboarding/demos/index.js +1 -1
  122. package/dist/onboarding/headless/labels.d.ts +17 -0
  123. package/dist/onboarding/headless/labels.js +17 -0
  124. package/dist/onboarding/index.js +4 -4
  125. package/dist/onboarding/marks.d.ts +1 -2
  126. package/dist/onboarding/marks.js +4 -12
  127. package/dist/onboarding/steps/ConnectorsStep.js +1 -1
  128. package/dist/onboarding/steps/ReadyStep.js +1 -1
  129. package/dist/onboarding/steps/SkillsStep.js +1 -1
  130. package/dist/onboarding/steps/connect/ProviderPanel.js +1 -1
  131. package/dist/onboarding/tutorials.d.ts +3 -1
  132. package/dist/onboarding/tutorials.js +2 -1
  133. package/dist/palette/app-sources.js +1 -1
  134. package/dist/session/ArtifactsPanel.js +1 -1
  135. package/dist/session/PanelAttention.d.ts +14 -6
  136. package/dist/session/PanelAttention.js +35 -24
  137. package/dist/session/PlanTracker.d.ts +54 -0
  138. package/dist/session/PlanTracker.js +412 -0
  139. package/dist/session/ProjectHome.d.ts +15 -2
  140. package/dist/session/ProjectHome.js +158 -85
  141. package/dist/session/RemindersPanel.d.ts +25 -0
  142. package/dist/session/RemindersPanel.js +526 -0
  143. package/dist/session/SecretsPanel.d.ts +19 -0
  144. package/dist/session/SecretsPanel.js +241 -0
  145. package/dist/session/SessionProvider.d.ts +26 -5
  146. package/dist/session/SessionProvider.js +51 -6
  147. package/dist/session/StepStatusIcon.d.ts +24 -0
  148. package/dist/session/StepStatusIcon.js +50 -0
  149. package/dist/session/TodoList.d.ts +4 -3
  150. package/dist/session/TodoList.js +6 -5
  151. package/dist/session/headless/fixture-host.d.ts +32 -22
  152. package/dist/session/headless/fixture-host.js +86 -51
  153. package/dist/session/headless/fixture-plans.d.ts +57 -0
  154. package/dist/session/headless/fixture-plans.js +141 -0
  155. package/dist/session/headless/fixture-schedules.d.ts +49 -0
  156. package/dist/session/headless/fixture-schedules.js +133 -0
  157. package/dist/session/headless/home-layout.d.ts +10 -2
  158. package/dist/session/headless/home-layout.js +7 -2
  159. package/dist/session/headless/host.d.ts +85 -13
  160. package/dist/session/headless/index.d.ts +5 -3
  161. package/dist/session/headless/index.js +5 -3
  162. package/dist/session/headless/labels.d.ts +95 -12
  163. package/dist/session/headless/labels.js +67 -18
  164. package/dist/session/headless/plans.d.ts +53 -0
  165. package/dist/session/headless/plans.js +78 -0
  166. package/dist/session/headless/reminder-labels.d.ts +63 -0
  167. package/dist/session/headless/reminder-labels.js +84 -0
  168. package/dist/session/headless/schedules.d.ts +93 -0
  169. package/dist/session/headless/schedules.js +190 -0
  170. package/dist/session/headless/secrets.d.ts +28 -0
  171. package/dist/session/headless/secrets.js +48 -0
  172. package/dist/session/index.d.ts +7 -5
  173. package/dist/session/index.js +6 -4
  174. package/dist/session/testing/contract.d.ts +18 -5
  175. package/dist/session/testing/contract.js +351 -43
  176. package/dist/session/testing/index.d.ts +4 -2
  177. package/dist/session/testing/index.js +4 -2
  178. package/dist/settings/headless/catalog.js +17 -1
  179. package/dist/settings/headless/index.d.ts +2 -2
  180. package/dist/settings/headless/index.js +3 -3
  181. package/dist/settings/headless/labels.d.ts +0 -2
  182. package/dist/settings/headless/labels.js +0 -2
  183. package/dist/settings/headless/layout.js +7 -1
  184. package/dist/settings/headless/providers.d.ts +17 -1
  185. package/dist/settings/headless/providers.js +19 -1
  186. package/dist/settings/headless/search.d.ts +1 -1
  187. package/dist/settings/rows.d.ts +1 -1
  188. package/dist/settings/rows.js +2 -2
  189. package/dist/settings/sections/appearance/AppearanceSection.js +2 -2
  190. package/dist/settings/sections/claude-import/ClaudeImportDialog.js +1 -1
  191. package/dist/settings/sections/editor/EditorSection.js +1 -1
  192. package/dist/settings/sections/environments/EnvironmentsSection.js +1 -1
  193. package/dist/settings/sections/extensions/ChangeOriginDialog.js +1 -1
  194. package/dist/settings/sections/extensions/CreateExtensionDialog.js +1 -1
  195. package/dist/settings/sections/extensions/ExtensionsSection.js +1 -1
  196. package/dist/settings/sections/host-catalog/HostCatalogSection.js +1 -1
  197. package/dist/settings/sections/instructions/InstructionsSection.js +1 -1
  198. package/dist/settings/sections/mcp/McpSection.js +1 -1
  199. package/dist/settings/sections/mcp/McpServerRow.js +1 -1
  200. package/dist/settings/sections/models/ModelRows.js +2 -2
  201. package/dist/settings/sections/providers/ProvidersSection.js +209 -201
  202. package/dist/settings/sections/providers/ServerProvidersSection.js +1 -1
  203. package/dist/settings/sections/shared/catalog.js +1 -1
  204. package/dist/settings/sections/skills/SkillsSection.js +2 -2
  205. package/dist/styles.css +59 -1
  206. package/dist/terminal/HostTerminalSurface.js +2 -2
  207. package/dist/workspace/PanelContentView.d.ts +22 -4
  208. package/dist/workspace/PanelContentView.js +112 -16
  209. package/dist/workspace/PanelGlyph.d.ts +21 -0
  210. package/dist/workspace/PanelGlyph.js +33 -0
  211. package/dist/workspace/PanelHome.js +5 -3
  212. package/dist/workspace/PanelStrip.js +11 -4
  213. package/dist/workspace/PanelToolbar.d.ts +32 -0
  214. package/dist/workspace/PanelToolbar.js +194 -0
  215. package/dist/workspace/WorkspaceProvider.d.ts +3 -1
  216. package/dist/workspace/WorkspaceProvider.js +6 -2
  217. package/dist/workspace/extension-tabs.js +5 -2
  218. package/dist/workspace/headless/host.d.ts +4 -0
  219. package/dist/workspace/headless/index.d.ts +3 -1
  220. package/dist/workspace/headless/index.js +4 -2
  221. package/dist/workspace/headless/labels.d.ts +10 -0
  222. package/dist/workspace/headless/labels.js +11 -1
  223. package/dist/workspace/headless/pages.d.ts +15 -0
  224. package/dist/workspace/headless/pages.js +49 -0
  225. package/dist/workspace/headless/tab-mark.d.ts +14 -0
  226. package/dist/workspace/headless/tab-mark.js +27 -0
  227. package/dist/workspace/headless/view.d.ts +7 -1
  228. package/dist/workspace/index.d.ts +3 -2
  229. package/dist/workspace/index.js +3 -2
  230. package/dist/workspace/testing/index.js +6 -0
  231. package/dist/workspace/workspace-key-commands.js +3 -3
  232. package/package.json +2 -2
  233. package/src/styles/motion.css +13 -0
  234. package/src/styles/onboarding.css +0 -1
  235. package/src/styles/primitives.css +10 -0
  236. package/src/styles/workspace.css +17 -0
  237. package/dist/session/TodosPanel.d.ts +0 -19
  238. package/dist/session/TodosPanel.js +0 -251
  239. package/dist/session/headless/todos.d.ts +0 -22
  240. package/dist/session/headless/todos.js +0 -38
@@ -18,6 +18,12 @@ import { useEffect, useState } from "react";
18
18
  * card alone keeps the agent waiting. Esc hands the keyboard back
19
19
  * (`onEscape`).
20
20
  *
21
+ * Under what waits on the user, the session's plans: `inProgress` (the plan
22
+ * being applied, or the stopped one that can resume) and `recent` (closed
23
+ * plans). The app passes them (`zidane-ui/session`'s `PlanTracker` and
24
+ * `RecentPlans`); the sections show only when given. Only "Needs you" asks
25
+ * for attention: the tab's badge counts requests, never steps.
26
+ *
21
27
  * <InboxPanel sessionId={id} focus={focusRequest === INBOX_PANEL_ID} onFocusTaken={takeFocusRequest} />
22
28
  */
23
29
  /** What a dismissed question tells the agent. */
@@ -27,7 +33,7 @@ const DISMISSED = "dismissed by user";
27
33
  *
28
34
  * @param props - The {@link InboxPanelProps}.
29
35
  */
30
- function InboxPanel({ sessionId, focus = false, onFocusTaken, onEscape, Markdown, className, style }) {
36
+ function InboxPanel({ sessionId, focus = false, onFocusTaken, onEscape, Markdown, inProgress, recent, className, style }) {
31
37
  const interactions = useChatHost().interactions;
32
38
  const labels = useChatLabels();
33
39
  const pending = usePendingInteractions(sessionId);
@@ -66,57 +72,89 @@ function InboxPanel({ sessionId, focus = false, onFocusTaken, onEscape, Markdown
66
72
  head,
67
73
  onFocusTaken
68
74
  ]);
69
- if (rows.length === 0) return /* @__PURE__ */ jsx(PanelEmpty, {
75
+ const hasPlans = Boolean(inProgress) || Boolean(recent);
76
+ if (rows.length === 0 && !hasPlans) return /* @__PURE__ */ jsx(PanelEmpty, {
70
77
  icon: "i-ph-tray",
71
78
  title: labels.inboxEmpty,
72
79
  className,
73
80
  style
74
81
  });
75
- return /* @__PURE__ */ jsx("div", {
76
- className: `flex-1 min-h-0 overflow-y-auto${className ? ` ${className}` : ""}`,
82
+ return /* @__PURE__ */ jsxs("div", {
83
+ className: `flex-1 min-h-0 overflow-y-auto p-3 flex flex-col gap-4${className ? ` ${className}` : ""}`,
77
84
  style,
78
- children: /* @__PURE__ */ jsx("ul", {
79
- className: "m-0 p-3 list-none flex flex-col gap-2",
80
- "aria-label": labels.interactionsLabel,
81
- onKeyDown: (e) => {
82
- if (e.key === "Escape" && onEscape) {
83
- e.preventDefault();
84
- e.stopPropagation();
85
- onEscape();
86
- }
87
- },
88
- children: rows.map((row) => {
89
- const isHead = row === head;
90
- const open = isHead || expanded === row.item.requestId;
91
- const request = row.item.request;
92
- return /* @__PURE__ */ jsxs("li", {
93
- "data-state": row.leaving ? "leaving" : "open",
94
- "data-testid": "inbox-card",
95
- className: "zg-inbox-card zg-raised border rounded-zg-lg overflow-hidden",
96
- children: [/* @__PURE__ */ jsx(CardHeader, {
97
- request,
98
- live: row.item.live !== false,
99
- open,
100
- collapsible: !isHead,
101
- onToggle: () => setExpanded((e) => e === row.item.requestId ? null : row.item.requestId)
102
- }), open && !row.leaving && interactions && /* @__PURE__ */ jsx("div", {
103
- className: "px-3 pb-3",
104
- children: request.kind === "plan" ? /* @__PURE__ */ jsx(PlanCard, {
105
- request,
106
- autoFocus: isHead && focus,
107
- Markdown,
108
- onAnswer: (response) => interactions.answer(request.id, response)
109
- }) : /* @__PURE__ */ jsx(QuestionForm, {
85
+ children: [
86
+ hasPlans && /* @__PURE__ */ jsx(InboxSection, {
87
+ title: labels.inboxNeedsYou,
88
+ children: rows.length === 0 && /* @__PURE__ */ jsx("p", {
89
+ className: "m-0 px-1 zg-meta",
90
+ children: labels.inboxEmpty
91
+ })
92
+ }),
93
+ /* @__PURE__ */ jsx("ul", {
94
+ className: `m-0 p-0 list-none flex flex-col gap-2${rows.length === 0 ? " hidden" : ""}`,
95
+ "aria-label": labels.interactionsLabel,
96
+ onKeyDown: (e) => {
97
+ if (e.key === "Escape" && onEscape) {
98
+ e.preventDefault();
99
+ e.stopPropagation();
100
+ onEscape();
101
+ }
102
+ },
103
+ children: rows.map((row) => {
104
+ const isHead = row === head;
105
+ const open = isHead || expanded === row.item.requestId;
106
+ const request = row.item.request;
107
+ return /* @__PURE__ */ jsxs("li", {
108
+ "data-state": row.leaving ? "leaving" : "open",
109
+ "data-testid": "inbox-card",
110
+ className: "zg-inbox-card zg-raised border rounded-zg-lg overflow-hidden",
111
+ children: [/* @__PURE__ */ jsx(CardHeader, {
110
112
  request,
111
- autoFocus: isHead && focus,
112
- Markdown,
113
- onAnswer: (response) => interactions.answer(request.id, response),
114
- ...interactions.cancel ? { onDismiss: () => interactions.cancel(request.id, DISMISSED) } : {}
115
- })
116
- })]
117
- }, row.item.requestId);
113
+ live: row.item.live !== false,
114
+ open,
115
+ collapsible: !isHead,
116
+ onToggle: () => setExpanded((e) => e === row.item.requestId ? null : row.item.requestId)
117
+ }), open && !row.leaving && interactions && /* @__PURE__ */ jsx("div", {
118
+ className: "px-3 pb-3",
119
+ children: request.kind === "plan" ? /* @__PURE__ */ jsx(PlanCard, {
120
+ request,
121
+ autoFocus: isHead && focus,
122
+ Markdown,
123
+ onAnswer: (response) => interactions.answer(request.id, response)
124
+ }) : /* @__PURE__ */ jsx(QuestionForm, {
125
+ request,
126
+ autoFocus: isHead && focus,
127
+ Markdown,
128
+ onAnswer: (response) => interactions.answer(request.id, response),
129
+ ...interactions.cancel ? { onDismiss: () => interactions.cancel(request.id, DISMISSED) } : {}
130
+ })
131
+ })]
132
+ }, row.item.requestId);
133
+ })
134
+ }),
135
+ inProgress && /* @__PURE__ */ jsx(InboxSection, {
136
+ title: labels.inboxInProgress,
137
+ children: /* @__PURE__ */ jsx("div", {
138
+ className: "zg-raised border rounded-zg-lg px-1",
139
+ children: inProgress
140
+ })
141
+ }),
142
+ recent && /* @__PURE__ */ jsx(InboxSection, {
143
+ title: labels.inboxRecent,
144
+ children: recent
118
145
  })
119
- })
146
+ ]
147
+ });
148
+ }
149
+ /** A titled part of the Inbox. */
150
+ function InboxSection({ title, children }) {
151
+ return /* @__PURE__ */ jsxs("section", {
152
+ className: "flex flex-col gap-2",
153
+ "aria-label": title,
154
+ children: [/* @__PURE__ */ jsx("h3", {
155
+ className: "zg-section-label m-0 px-1",
156
+ children: title
157
+ }), children]
120
158
  });
121
159
  }
122
160
  function CardHeader({ request, live, open, collapsible, onToggle }) {
@@ -1,6 +1,6 @@
1
1
  import { MarkdownSlotProps } from "../transcript/renderers.js";
2
2
  import { CSSProperties, ComponentType } from "react";
3
- import { PlanPayload, PlanRequest, PlanResponse } from "zidane/chat";
3
+ import { PlanProposal, PlanRequest, PlanResponse } from "zidane/chat";
4
4
  //#region src/chat/interactions/PlanCard.d.ts
5
5
  /** Props for {@link PlanCard}. */
6
6
  interface PlanCardProps {
@@ -26,7 +26,7 @@ declare function PlanCard({ request, autoFocus, onAnswer, Markdown, className, s
26
26
  /** Props for {@link PlanBody}. */
27
27
  interface PlanBodyProps {
28
28
  /** The plan to show: its markdown and optional numbered steps. */
29
- payload: PlanPayload;
29
+ payload: PlanProposal;
30
30
  /** Draws the plan. Defaults to the transcript's `Markdown` slot. */
31
31
  Markdown?: ComponentType<MarkdownSlotProps>;
32
32
  }
@@ -5,7 +5,7 @@ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
5
5
  import { useRef, useState } from "react";
6
6
  //#region src/chat/interactions/PlanCard.tsx
7
7
  /**
8
- * `present_plan` as a card (the desktop app's inbox, `InteractionCard`). Renders the canonical `PlanPayload` and
8
+ * `present_plan` as a card (the desktop app's inbox, `InteractionCard`). Renders the canonical `PlanProposal` and
9
9
  * lets the user approve / revise / reject; the structured `PlanResponse`
10
10
  * goes to the host, which serialises it for the model and, on `reject`,
11
11
  * aborts the run once the rejection is recorded (the TUI's contract).
@@ -3,9 +3,10 @@ import { CSSProperties } from "react";
3
3
  import { AnswerValue, Question } from "zidane/chat";
4
4
  //#region src/chat/interactions/QuestionField.d.ts
5
5
  /**
6
- * Same defaults as the TUI: select/confirm required, free text optional.
6
+ * Same defaults as the TUI: select/confirm required, free text optional. A secret needs its value (Decline is the
7
+ * way out).
7
8
  *
8
- * @param q - The question. `required` wins; otherwise `select` and `confirm` are required and free text is optional.
9
+ * @param q - The question. `required` wins; otherwise `select`, `confirm` and `secret` are required and free text is optional.
9
10
  */
10
11
  declare function isRequired(q: Question): boolean;
11
12
  /** Props for {@link QuestionField}. */
@@ -24,6 +25,10 @@ interface QuestionFieldProps {
24
25
  labels: ChatLabels;
25
26
  /** The answered form (transcript): the same card, showing what was picked, nothing editable. */
26
27
  readOnly?: boolean;
28
+ /** Enter in a secret's password field (the form saves). */
29
+ onSubmit?: () => void;
30
+ /** Opens the Secrets panel from an answered secret. Absent: no button. */
31
+ onOpenSecrets?: () => void;
27
32
  /** Extra classes on the root element. */
28
33
  className?: string;
29
34
  /** Inline styles on the root element, merged after the component's own. */
@@ -34,6 +39,6 @@ interface QuestionFieldProps {
34
39
  *
35
40
  * @param props - Field props: `index`, `question`, `value`, `onChange`, `requestId`, `labels`, and `readOnly`.
36
41
  */
37
- declare function QuestionField({ index, question, value, onChange, requestId, labels, readOnly, className, style }: QuestionFieldProps): import("react").JSX.Element;
42
+ declare function QuestionField({ index, question, value, onChange, requestId, labels, readOnly, onSubmit, onOpenSecrets, className, style }: QuestionFieldProps): import("react").JSX.Element;
38
43
  //#endregion
39
44
  export { QuestionField, QuestionFieldProps, isRequired };
@@ -1,6 +1,9 @@
1
- import "./card-ui.js";
1
+ import { INPUT } from "./card-ui.js";
2
+ import { secretTimeLeftIso } from "../headless/secrets.js";
3
+ import { SecretInput } from "./SecretInput.js";
2
4
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
5
  import { useRef } from "react";
6
+ import { DEFAULT_SECRET_TTL, isSecretAnswer, isSecretReference } from "zidane/chat/pure";
4
7
  //#region src/chat/interactions/QuestionField.tsx
5
8
  /**
6
9
  * One question of an `ask_user` form as its card: the numbered prompt, its
@@ -9,25 +12,26 @@ import { useRef } from "react";
9
12
  * answer given (the transcript's answered form).
10
13
  */
11
14
  /**
12
- * Same defaults as the TUI: select/confirm required, free text optional.
15
+ * Same defaults as the TUI: select/confirm required, free text optional. A secret needs its value (Decline is the
16
+ * way out).
13
17
  *
14
- * @param q - The question. `required` wins; otherwise `select` and `confirm` are required and free text is optional.
18
+ * @param q - The question. `required` wins; otherwise `select`, `confirm` and `secret` are required and free text is optional.
15
19
  */
16
20
  function isRequired(q) {
17
- return q.required ?? (q.type === "select" || q.type === "confirm");
21
+ return q.required ?? (q.type === "select" || q.type === "confirm" || q.type === "secret");
18
22
  }
19
23
  /**
20
24
  * Renders one question of an `ask_user` form as a card: the numbered prompt, its help, and the control its type takes.
21
25
  *
22
26
  * @param props - Field props: `index`, `question`, `value`, `onChange`, `requestId`, `labels`, and `readOnly`.
23
27
  */
24
- function QuestionField({ index, question, value, onChange, requestId, labels, readOnly = false, className, style }) {
28
+ function QuestionField({ index, question, value, onChange, requestId, labels, readOnly = false, onSubmit, onOpenSecrets, className, style }) {
25
29
  const required = isRequired(question) && !readOnly;
26
30
  const inputId = `ask-${requestId}-${question.id}`;
27
31
  const promptId = `${inputId}-prompt`;
28
32
  const multi = question.type === "select" && question.multi;
29
33
  const describedBy = [question.description && `${inputId}-desc`, multi && `${inputId}-hint`].filter(Boolean).join(" ") || void 0;
30
- const labelsInput = !readOnly && (question.type === "text" || question.type === "textarea");
34
+ const labelsInput = !readOnly && (question.type === "text" || question.type === "textarea" || question.type === "secret");
31
35
  const promptClass = "text-zg-ui font-medium leading-5 text-[var(--zg-text)] zg-balance";
32
36
  const promptText = /* @__PURE__ */ jsxs(Fragment, { children: [question.prompt, required && /* @__PURE__ */ jsx("span", {
33
37
  "aria-hidden": true,
@@ -103,7 +107,7 @@ function QuestionField({ index, question, value, onChange, requestId, labels, re
103
107
  placeholder: question.placeholder,
104
108
  "aria-required": required || void 0,
105
109
  "aria-describedby": describedBy,
106
- className: `zg-well w-full text-zg-ui px-2.5 placeholder:text-[var(--zg-text-subtle)] h-8`
110
+ className: `${INPUT} h-8`
107
111
  }),
108
112
  !readOnly && question.type === "textarea" && /* @__PURE__ */ jsx("textarea", {
109
113
  id: inputId,
@@ -113,7 +117,26 @@ function QuestionField({ index, question, value, onChange, requestId, labels, re
113
117
  rows: 3,
114
118
  "aria-required": required || void 0,
115
119
  "aria-describedby": describedBy,
116
- className: `zg-well w-full text-zg-ui px-2.5 placeholder:text-[var(--zg-text-subtle)] min-h-20 py-2 resize-y`
120
+ className: `${INPUT} min-h-20 py-2 resize-y`
121
+ }),
122
+ !readOnly && question.type === "secret" && /* @__PURE__ */ jsx(SecretInput, {
123
+ id: inputId,
124
+ ttl: isSecretAnswer(value) && value.kind === "secret-value" ? value.ttl : question.ttl ?? DEFAULT_SECRET_TTL,
125
+ onChange: (typed, ttl) => onChange({
126
+ kind: "secret-value",
127
+ value: typed,
128
+ ttl
129
+ }),
130
+ describedBy,
131
+ required,
132
+ onSubmit
133
+ }),
134
+ readOnly && question.type === "secret" && /* @__PURE__ */ jsx(SecretOutcome, {
135
+ name: question.name,
136
+ value,
137
+ labels,
138
+ groupProps,
139
+ onOpenSecrets
117
140
  }),
118
141
  question.type === "select" && /* @__PURE__ */ jsx("div", {
119
142
  className: "flex flex-col gap-1.5",
@@ -179,6 +202,64 @@ function QuestionField({ index, question, value, onChange, requestId, labels, re
179
202
  });
180
203
  }
181
204
  /**
205
+ * An answered secret, never its value: saved (and when it expires), reused, declined. The answer is the host's
206
+ * `SecretReference`; a raw `SecretAnswer` (not sealed yet) shows only that it was sent.
207
+ */
208
+ function SecretOutcome({ name, value, labels, groupProps, onOpenSecrets }) {
209
+ const outcome = secretOutcome(name, value, labels);
210
+ return /* @__PURE__ */ jsxs("div", {
211
+ ...groupProps,
212
+ role: "group",
213
+ className: "flex flex-wrap items-center gap-x-2 gap-y-1 text-zg-ui leading-5",
214
+ children: [/* @__PURE__ */ jsxs("span", {
215
+ className: `flex items-center gap-1.5 ${outcome.saved ? "text-[var(--zg-text)]" : "text-[var(--zg-text-muted)]"}`,
216
+ "data-testid": "secret-outcome",
217
+ children: [/* @__PURE__ */ jsx("span", {
218
+ "aria-hidden": true,
219
+ className: `${outcome.icon} shrink-0`
220
+ }), outcome.text]
221
+ }), outcome.saved && onOpenSecrets && /* @__PURE__ */ jsx("button", {
222
+ type: "button",
223
+ onClick: onOpenSecrets,
224
+ className: "zg-focus-ring rounded-zg-xs bg-transparent border-0 p-0 cursor-pointer text-zg-ui text-[var(--zg-accent)] hover:underline",
225
+ children: labels.openSecrets
226
+ })]
227
+ });
228
+ }
229
+ function secretOutcome(name, value, labels) {
230
+ if (isSecretReference(value)) {
231
+ if ("declined" in value) return {
232
+ text: labels.secretDeclined(value.secret),
233
+ icon: "i-ph-minus-circle",
234
+ saved: false
235
+ };
236
+ if (value.reused) return {
237
+ text: labels.secretReused(value.secret),
238
+ icon: "i-ph-key",
239
+ saved: true
240
+ };
241
+ return {
242
+ text: labels.secretSaved(value.secret, secretTimeLeftIso(value.expiresAt, Date.now())),
243
+ icon: "i-ph-shield-check",
244
+ saved: true
245
+ };
246
+ }
247
+ if (isSecretAnswer(value)) return value.kind === "secret-declined" ? {
248
+ text: labels.secretDeclined(name),
249
+ icon: "i-ph-minus-circle",
250
+ saved: false
251
+ } : {
252
+ text: labels.secretSent(name),
253
+ icon: "i-ph-shield-check",
254
+ saved: false
255
+ };
256
+ return {
257
+ text: labels.noAnswer,
258
+ icon: "i-ph-minus-circle",
259
+ saved: false
260
+ };
261
+ }
262
+ /**
182
263
  * Radio-group keyboarding (WAI-ARIA APG): one tab stop, on the checked
183
264
  * radio or else the first; arrows move focus and pick (wrapping), Home / End
184
265
  * jump to the ends. Read-only, the arrows still move focus so every option
@@ -38,6 +38,8 @@ interface AnsweredQuestionsProps {
38
38
  id: string;
39
39
  /** Draws the intro; defaults to the transcript's `Markdown` slot. */
40
40
  Markdown?: ComponentType<MarkdownSlotProps>;
41
+ /** Opens the Secrets panel from an answered secret. Absent: no button. */
42
+ onOpenSecrets?: () => void;
41
43
  /** Extra classes on the root element. */
42
44
  className?: string;
43
45
  /** Inline styles on the root element, merged after the component's own. */
@@ -51,7 +53,7 @@ interface AnsweredQuestionsProps {
51
53
  *
52
54
  * @param props - Component props: the `payload` that was asked, the `answers`, optional `instructions`, a unique `id`, and an optional `Markdown` renderer.
53
55
  */
54
- declare function AnsweredQuestions({ payload, answers, instructions, id, Markdown: MarkdownProp, className, style }: AnsweredQuestionsProps): import("react").JSX.Element;
56
+ declare function AnsweredQuestions({ payload, answers, instructions, id, Markdown: MarkdownProp, onOpenSecrets, className, style }: AnsweredQuestionsProps): import("react").JSX.Element;
55
57
  /**
56
58
  * Seed the form from the payload: `default` for free text, `selected`
57
59
  * pre-ticks for multi-select (an opt-out list arrives already ticked).
@@ -4,6 +4,7 @@ import { BTN_GHOST_LEADING, BTN_PRIMARY, CardError, INPUT, useAutoFocus } from "
4
4
  import { QuestionField, isRequired } from "./QuestionField.js";
5
5
  import { jsx, jsxs } from "react/jsx-runtime";
6
6
  import { useMemo, useRef, useState } from "react";
7
+ import { isSecretAnswer } from "zidane/chat/pure";
7
8
  //#region src/chat/interactions/QuestionForm.tsx
8
9
  /**
9
10
  * `ask_user` as a card (the desktop app's inbox, `InteractionCard`). The canonical `QuestionPayload` as a form:
@@ -21,6 +22,13 @@ import { useMemo, useRef, useState } from "react";
21
22
  *
22
23
  * Two ways out: **Send** answers (`onAnswer`); **Dismiss** (`onDismiss`,
23
24
  * shown when given) cancels so the agent moves on without an answer.
25
+ *
26
+ * A `secret` question takes a password field and a TTL (`SecretInput`) and
27
+ * answers with a `SecretAnswer`, which the host saves and swaps for a
28
+ * reference before anything records it. A form with secrets says **Save
29
+ * securely** (only secrets) and offers **Decline** in place of Dismiss: it
30
+ * answers every secret with `secret-declined`. A form of secrets alone has
31
+ * no additional instructions. Nothing here persists the typed value.
24
32
  * Leaving the card alone keeps the question pending. Markdown (the intro)
25
33
  * draws through the transcript's `Markdown` slot unless `Markdown` is passed.
26
34
  */
@@ -40,21 +48,26 @@ function QuestionForm({ request, autoFocus = false, onAnswer, onDismiss, Markdow
40
48
  const [error, setError] = useState(null);
41
49
  const rootRef = useRef(null);
42
50
  useAutoFocus(rootRef, autoFocus);
51
+ const secretIds = useMemo(() => payload.questions.filter((q) => q.type === "secret").map((q) => q.id), [payload.questions]);
52
+ const onlySecrets = secretIds.length > 0 && secretIds.length === payload.questions.length;
43
53
  const canSubmit = useMemo(() => payload.questions.every((q) => {
44
- if (!isRequired(q)) return true;
45
54
  const value = answers[q.id];
55
+ if (q.type === "secret") {
56
+ const typed = isSecretAnswer(value) && value.kind === "secret-value" ? value.value : "";
57
+ return !isRequired(q) || typed.trim().length > 0;
58
+ }
59
+ if (!isRequired(q)) return true;
46
60
  if (q.type === "confirm") return typeof value === "boolean";
47
61
  if (q.type === "select" && q.multi) return Array.isArray(value) && value.length > 0;
48
62
  return typeof value === "string" && value.trim().length > 0;
49
63
  }), [answers, payload.questions]);
50
- const submit = async () => {
51
- if (busy || !canSubmit) return;
64
+ const send = async (sent) => {
52
65
  setBusy(true);
53
66
  setError(null);
54
67
  try {
55
68
  await onAnswer({
56
69
  kind: "question",
57
- answers,
70
+ answers: sent,
58
71
  ...instructions.trim() ? { instructions: instructions.trim() } : {}
59
72
  });
60
73
  } catch (err) {
@@ -62,6 +75,22 @@ function QuestionForm({ request, autoFocus = false, onAnswer, onDismiss, Markdow
62
75
  setBusy(false);
63
76
  }
64
77
  };
78
+ const submit = async () => {
79
+ if (busy || !canSubmit) return;
80
+ const sent = { ...answers };
81
+ for (const id of secretIds) {
82
+ const value = sent[id];
83
+ if (!isSecretAnswer(value) || value.kind === "secret-value" && value.value.trim() === "") delete sent[id];
84
+ }
85
+ await send(sent);
86
+ };
87
+ /** Every secret answered as declined; the other answers go as they are. */
88
+ const decline = async () => {
89
+ if (busy) return;
90
+ const sent = { ...answers };
91
+ for (const id of secretIds) sent[id] = { kind: "secret-declined" };
92
+ await send(sent);
93
+ };
65
94
  /** Explicit dismissal: the agent is told there is no answer and continues. */
66
95
  const dismiss = async () => {
67
96
  if (busy || !onDismiss) return;
@@ -100,9 +129,10 @@ function QuestionForm({ request, autoFocus = false, onAnswer, onDismiss, Markdow
100
129
  value: answers[q.id],
101
130
  onChange: (v) => setAnswer(q.id, v),
102
131
  requestId: request.id,
103
- labels
132
+ labels,
133
+ onSubmit: () => void submit()
104
134
  }, q.id)),
105
- /* @__PURE__ */ jsxs("div", {
135
+ !onlySecrets && /* @__PURE__ */ jsxs("div", {
106
136
  className: "flex flex-col gap-1.5",
107
137
  children: [/* @__PURE__ */ jsxs("div", {
108
138
  className: "flex items-baseline gap-1.5",
@@ -127,7 +157,14 @@ function QuestionForm({ request, autoFocus = false, onAnswer, onDismiss, Markdow
127
157
  /* @__PURE__ */ jsxs("div", {
128
158
  className: "flex items-center gap-2 pt-1",
129
159
  children: [
130
- onDismiss && /* @__PURE__ */ jsx("button", {
160
+ secretIds.length > 0 ? /* @__PURE__ */ jsx("button", {
161
+ type: "button",
162
+ onClick: () => void decline(),
163
+ disabled: busy,
164
+ title: labels.declineSecretHint,
165
+ className: BTN_GHOST_LEADING,
166
+ children: labels.declineSecret
167
+ }) : onDismiss && /* @__PURE__ */ jsx("button", {
131
168
  type: "button",
132
169
  onClick: () => void dismiss(),
133
170
  disabled: busy,
@@ -143,7 +180,7 @@ function QuestionForm({ request, autoFocus = false, onAnswer, onDismiss, Markdow
143
180
  disabled: busy || !canSubmit,
144
181
  className: BTN_PRIMARY,
145
182
  "data-testid": "inbox-send",
146
- children: labels.sendAnswers(payload.questions.length)
183
+ children: onlySecrets ? labels.saveSecurely : labels.sendAnswers(payload.questions.length)
147
184
  })
148
185
  ]
149
186
  })
@@ -158,7 +195,7 @@ function QuestionForm({ request, autoFocus = false, onAnswer, onDismiss, Markdow
158
195
  *
159
196
  * @param props - Component props: the `payload` that was asked, the `answers`, optional `instructions`, a unique `id`, and an optional `Markdown` renderer.
160
197
  */
161
- function AnsweredQuestions({ payload, answers, instructions, id, Markdown: MarkdownProp, className, style }) {
198
+ function AnsweredQuestions({ payload, answers, instructions, id, Markdown: MarkdownProp, onOpenSecrets, className, style }) {
162
199
  const labels = useChatLabels();
163
200
  const slots = useTranscriptRenderers();
164
201
  const Markdown = MarkdownProp ?? slots.Markdown;
@@ -177,7 +214,8 @@ function AnsweredQuestions({ payload, answers, instructions, id, Markdown: Markd
177
214
  onChange: () => void 0,
178
215
  requestId: id,
179
216
  labels,
180
- readOnly: true
217
+ readOnly: true,
218
+ onOpenSecrets
181
219
  }, q.id)),
182
220
  instructions && instructions.trim() !== "" && /* @__PURE__ */ jsxs("div", {
183
221
  className: "flex flex-col gap-1",
@@ -0,0 +1,32 @@
1
+ import { CSSProperties, Ref } from "react";
2
+ import { SecretTtl } from "zidane/chat/pure";
3
+ //#region src/chat/interactions/SecretInput.d.ts
4
+ /** Props for {@link SecretInput}. */
5
+ interface SecretInputProps {
6
+ /** Id of the password field: a `<label htmlFor>` names it. */
7
+ id: string;
8
+ /** The TTL picked. */
9
+ ttl: SecretTtl;
10
+ /** Called on every keystroke with the typed value and the picked TTL. Keep the value out of anything logged or saved. */
11
+ onChange: (value: string, ttl: SecretTtl) => void;
12
+ /** Ids of the elements that describe the field (the question's help). */
13
+ describedBy?: string;
14
+ /** Marks the field required for assistive tech. */
15
+ required?: boolean;
16
+ /** Called on Enter in the password field. */
17
+ onSubmit?: () => void;
18
+ /** Ref to the password field. */
19
+ inputRef?: Ref<HTMLInputElement>;
20
+ /** Extra classes on the root element. */
21
+ className?: string;
22
+ /** Inline styles on the root element, merged after the component's own. */
23
+ style?: CSSProperties;
24
+ }
25
+ /**
26
+ * Renders a password field, a TTL picker and the note on where the value is kept.
27
+ *
28
+ * @param props - The {@link SecretInputProps}.
29
+ */
30
+ declare function SecretInput({ id, ttl, onChange, describedBy, required, onSubmit, inputRef, className, style }: SecretInputProps): import("react").JSX.Element;
31
+ //#endregion
32
+ export { SecretInput, SecretInputProps };
@@ -0,0 +1,72 @@
1
+ import { useChatLabels } from "../useChatLabels.js";
2
+ import { FIELD, INPUT } from "./card-ui.js";
3
+ import { jsx, jsxs } from "react/jsx-runtime";
4
+ import { useRef } from "react";
5
+ import { SECRET_TTLS } from "zidane/chat/pure";
6
+ //#region src/chat/interactions/SecretInput.tsx
7
+ /**
8
+ * The value half of a secret: a password field that never holds the value in
9
+ * the DOM (uncontrolled, no `value` attribute, no autocomplete), a TTL
10
+ * picker, and the shield note that says where the value goes and for how
11
+ * long. The `ask_user` form's secret question and the Secrets panel's
12
+ * add form both draw it.
13
+ */
14
+ /**
15
+ * Renders a password field, a TTL picker and the note on where the value is kept.
16
+ *
17
+ * @param props - The {@link SecretInputProps}.
18
+ */
19
+ function SecretInput({ id, ttl, onChange, describedBy, required = false, onSubmit, inputRef, className, style }) {
20
+ const labels = useChatLabels();
21
+ const value = useRef("");
22
+ const noteId = `${id}-note`;
23
+ return /* @__PURE__ */ jsxs("div", {
24
+ className: ["flex flex-col gap-1.5", className].filter(Boolean).join(" "),
25
+ style,
26
+ children: [/* @__PURE__ */ jsxs("div", {
27
+ className: "flex items-center gap-1.5",
28
+ children: [/* @__PURE__ */ jsx("input", {
29
+ ref: inputRef,
30
+ id,
31
+ type: "password",
32
+ autoComplete: "off",
33
+ autoCorrect: "off",
34
+ autoCapitalize: "off",
35
+ spellCheck: false,
36
+ placeholder: labels.secretValuePlaceholder,
37
+ "aria-required": required || void 0,
38
+ "aria-describedby": [describedBy, noteId].filter(Boolean).join(" "),
39
+ "data-secret-input": "",
40
+ className: `${INPUT} h-8 min-w-0 flex-1`,
41
+ onChange: (e) => {
42
+ value.current = e.target.value;
43
+ onChange(value.current, ttl);
44
+ },
45
+ onKeyDown: (e) => {
46
+ if (e.key === "Enter" && !e.metaKey && !e.ctrlKey && onSubmit) {
47
+ e.preventDefault();
48
+ onSubmit();
49
+ }
50
+ }
51
+ }), /* @__PURE__ */ jsx("select", {
52
+ "aria-label": labels.secretTtlLabel,
53
+ value: ttl,
54
+ onChange: (e) => onChange(value.current, e.target.value),
55
+ className: `${FIELD} h-8 shrink-0`,
56
+ children: SECRET_TTLS.map((option) => /* @__PURE__ */ jsx("option", {
57
+ value: option,
58
+ children: labels.secretTtlName(option)
59
+ }, option))
60
+ })]
61
+ }), /* @__PURE__ */ jsxs("span", {
62
+ id: noteId,
63
+ className: "flex items-start gap-1.5 text-zg-meta leading-4 text-[var(--zg-text-muted)] zg-pretty",
64
+ children: [/* @__PURE__ */ jsx("span", {
65
+ "aria-hidden": true,
66
+ className: "i-ph-shield-check mt-px shrink-0"
67
+ }), labels.secretNote(ttl)]
68
+ })]
69
+ });
70
+ }
71
+ //#endregion
72
+ export { SecretInput };
@@ -10,8 +10,10 @@ declare const BTN_GHOST: string;
10
10
  declare const BTN_GHOST_LEADING: string;
11
11
  /** Class names of the danger button in an interaction card: the danger fill. */
12
12
  declare const BTN_DANGER: string;
13
- /** Class names of a text field in an interaction card. */
14
- declare const INPUT = "zg-well w-full text-zg-ui px-2.5 placeholder:text-[var(--zg-text-subtle)]";
13
+ /** The well of a field in an interaction card, with no width: a select sized to its options. */
14
+ declare const FIELD = "zg-well text-zg-ui px-2.5";
15
+ /** A full-width text field of a card. */
16
+ declare const INPUT = "zg-well text-zg-ui px-2.5 w-full placeholder:text-[var(--zg-text-subtle)]";
15
17
  /** Props for {@link CardError}. */
16
18
  interface CardErrorProps {
17
19
  /** The message to show, or `null` to render nothing. */
@@ -37,4 +39,4 @@ declare function CardError({ error, className, style }: CardErrorProps): import(
37
39
  */
38
40
  declare function useAutoFocus(root: RefObject<HTMLElement | null>, when: boolean): void;
39
41
  //#endregion
40
- export { BTN, BTN_DANGER, BTN_GHOST, BTN_GHOST_LEADING, BTN_PRIMARY, CardError, CardErrorProps, INPUT, useAutoFocus };
42
+ export { BTN, BTN_DANGER, BTN_GHOST, BTN_GHOST_LEADING, BTN_PRIMARY, CardError, CardErrorProps, FIELD, INPUT, useAutoFocus };
@@ -20,8 +20,10 @@ const BTN_GHOST_LEADING = buttonClass({
20
20
  });
21
21
  /** Class names of the danger button in an interaction card: the danger fill. */
22
22
  const BTN_DANGER = buttonClass({ variant: "danger-solid" });
23
- /** Class names of a text field in an interaction card. */
24
- const INPUT = "zg-well w-full text-zg-ui px-2.5 placeholder:text-[var(--zg-text-subtle)]";
23
+ /** The well of a field in an interaction card, with no width: a select sized to its options. */
24
+ const FIELD = "zg-well text-zg-ui px-2.5";
25
+ /** A full-width text field of a card. */
26
+ const INPUT = `${FIELD} w-full placeholder:text-[var(--zg-text-subtle)]`;
25
27
  /**
26
28
  * Renders an inline error message, or nothing when there is none.
27
29
  *
@@ -60,4 +62,4 @@ function useAutoFocus(root, when) {
60
62
  }, []);
61
63
  }
62
64
  //#endregion
63
- export { BTN, BTN_DANGER, BTN_GHOST, BTN_GHOST_LEADING, BTN_PRIMARY, CardError, INPUT, useAutoFocus };
65
+ export { BTN, BTN_DANGER, BTN_GHOST, BTN_GHOST_LEADING, BTN_PRIMARY, CardError, FIELD, INPUT, useAutoFocus };