zidane-ui 9.1.6 → 9.1.9

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 (224) hide show
  1. package/dist/adapters/run-host.js +17 -20
  2. package/dist/adapters/server-files-host.js +3 -1
  3. package/dist/adapters/server-host.js +18 -11
  4. package/dist/adapters/server-machines-host.js +3 -1
  5. package/dist/adapters/server-session-host.js +2 -1
  6. package/dist/adapters/server-settings-accounts.js +2 -1
  7. package/dist/adapters/server-settings-host.js +3 -3
  8. package/dist/adapters/sse-host.js +9 -7
  9. package/dist/chat/SessionGroupHeader.js +2 -2
  10. package/dist/chat/SessionHostHeader.js +1 -1
  11. package/dist/chat/StatusDot.js +24 -20
  12. package/dist/chat/composer/Composer.d.ts +13 -8
  13. package/dist/chat/composer/Composer.js +14 -10
  14. package/dist/chat/composer/ComposerEditor.js +1 -1
  15. package/dist/chat/composer/HostComposer.d.ts +1 -1
  16. package/dist/chat/composer/HostComposer.js +7 -10
  17. package/dist/chat/composer/RunStatusChip.js +13 -1
  18. package/dist/chat/composer/attachment-ingest.d.ts +4 -1
  19. package/dist/chat/composer/attachment-ingest.js +27 -10
  20. package/dist/chat/composer/composer-bus.d.ts +9 -1
  21. package/dist/chat/composer/composer-bus.js +49 -6
  22. package/dist/chat/composer/index.d.ts +2 -2
  23. package/dist/chat/composer/index.js +2 -2
  24. package/dist/chat/composer/use-draft-target.d.ts +4 -0
  25. package/dist/chat/composer/use-draft-target.js +15 -4
  26. package/dist/chat/composer/use-send-prompt.d.ts +5 -1
  27. package/dist/chat/composer/use-send-prompt.js +2 -12
  28. package/dist/chat/headless/chip-labels.d.ts +2 -0
  29. package/dist/chat/headless/chip-labels.js +3 -1
  30. package/dist/chat/headless/composer/labels-editor.d.ts +2 -0
  31. package/dist/chat/headless/composer/labels-editor.js +2 -0
  32. package/dist/chat/headless/file-chip.d.ts +12 -13
  33. package/dist/chat/headless/file-chip.js +40 -2
  34. package/dist/chat/headless/fixture-host.js +14 -17
  35. package/dist/chat/headless/host.d.ts +24 -6
  36. package/dist/chat/headless/index.d.ts +4 -4
  37. package/dist/chat/headless/index.js +4 -4
  38. package/dist/chat/headless/labels.d.ts +16 -0
  39. package/dist/chat/headless/labels.js +15 -2
  40. package/dist/chat/headless/send-flow.d.ts +21 -4
  41. package/dist/chat/headless/send-flow.js +25 -6
  42. package/dist/chat/headless/session-list.d.ts +11 -0
  43. package/dist/chat/headless/sidebar/index.js +1 -1
  44. package/dist/chat/headless/sidebar/labels.d.ts +12 -1
  45. package/dist/chat/headless/transcript-host.d.ts +8 -1
  46. package/dist/chat/headless/transcript-labels.d.ts +2 -0
  47. package/dist/chat/index.d.ts +3 -3
  48. package/dist/chat/index.js +2 -2
  49. package/dist/chat/interactions/InboxPanel.js +2 -2
  50. package/dist/chat/interactions/QuestionField.js +2 -2
  51. package/dist/chat/rich-composer/RichComposer.d.ts +9 -2
  52. package/dist/chat/rich-composer/RichComposer.js +28 -7
  53. package/dist/chat/rich-composer/composer-extensions.d.ts +2 -0
  54. package/dist/chat/rich-composer/composer-extensions.js +2 -2
  55. package/dist/chat/rich-composer/composer-keys.js +1 -1
  56. package/dist/chat/rich-composer/use-composer-bus.js +2 -1
  57. package/dist/chat/rich-composer/use-composer-editor.d.ts +4 -1
  58. package/dist/chat/rich-composer/use-composer-editor.js +2 -1
  59. package/dist/chat/sidebar/SessionSidebar.js +1 -2
  60. package/dist/chat/sidebar/SidebarSearchRow.js +1 -1
  61. package/dist/chat/sidebar/useSidebarKeyboardNav.d.ts +0 -2
  62. package/dist/chat/sidebar/useSidebarKeyboardNav.js +7 -13
  63. package/dist/chat/testing/contract.d.ts +1 -1
  64. package/dist/chat/testing/contract.js +8 -7
  65. package/dist/chat/transcript/BadgeMenu.d.ts +12 -3
  66. package/dist/chat/transcript/BadgeMenu.js +20 -3
  67. package/dist/chat/transcript/CodemodeScript.js +147 -44
  68. package/dist/chat/transcript/EditDiffBlock.js +1 -4
  69. package/dist/chat/transcript/FileChip.js +60 -14
  70. package/dist/chat/transcript/LiveOutput.js +7 -3
  71. package/dist/chat/transcript/badges.js +2 -2
  72. package/dist/chat/transcript/index.d.ts +2 -2
  73. package/dist/chat/transcript/markdown-context.d.ts +4 -1
  74. package/dist/chat/transcript/rolling.d.ts +12 -0
  75. package/dist/chat/transcript/rolling.js +22 -0
  76. package/dist/chat/transcript/script-input.d.ts +1 -1
  77. package/dist/chat/transcript/shell-block.js +70 -60
  78. package/dist/chat/transcript/tool-row.js +21 -5
  79. package/dist/code/editor/FileTree.d.ts +3 -1
  80. package/dist/code/editor/FileTree.js +29 -1
  81. package/dist/components/Restricted.d.ts +25 -0
  82. package/dist/components/Restricted.js +32 -0
  83. package/dist/components/StepFrame.d.ts +1 -1
  84. package/dist/components/ZidaneMark.d.ts +28 -0
  85. package/dist/components/ZidaneMark.js +114 -0
  86. package/dist/components/palette/PalettePanel.d.ts +7 -1
  87. package/dist/components/palette/PalettePanel.js +37 -11
  88. package/dist/components/zidane-mark.d.ts +52 -0
  89. package/dist/components/zidane-mark.js +94 -0
  90. package/dist/core.css +59 -6
  91. package/dist/dist/{index-DqCio7Nd.d.ts → index-Bq87OcGy.d.ts} +19 -12
  92. package/dist/dist/index.d.ts +1 -1
  93. package/dist/extensions/ExtensionSettingsSection.d.ts +18 -0
  94. package/dist/extensions/ExtensionSettingsSection.js +227 -0
  95. package/dist/extensions/headless/host.d.ts +1 -1
  96. package/dist/extensions/headless/labels.d.ts +41 -1
  97. package/dist/extensions/headless/labels.js +57 -0
  98. package/dist/extensions/headless/select.js +1 -2
  99. package/dist/extensions/index.d.ts +2 -1
  100. package/dist/extensions/index.js +2 -1
  101. package/dist/files/FilesTab.js +2 -0
  102. package/dist/files/TreeColumn.d.ts +5 -1
  103. package/dist/files/TreeColumn.js +6 -0
  104. package/dist/files/headless/fixture-host.js +5 -3
  105. package/dist/files/headless/host.d.ts +24 -6
  106. package/dist/files/headless/index.d.ts +2 -2
  107. package/dist/files/headless/labels.d.ts +1 -0
  108. package/dist/files/headless/labels.js +1 -0
  109. package/dist/files/testing/contract.js +9 -2
  110. package/dist/files/useWorkspace.d.ts +14 -2
  111. package/dist/files/useWorkspace.js +87 -9
  112. package/dist/git/BranchMenu.js +1 -1
  113. package/dist/git/MoveSessionDialog.js +1 -1
  114. package/dist/git/RemoteFolderPicker.js +1 -1
  115. package/dist/git/WherePicker.js +1 -1
  116. package/dist/git/headless/index.js +1 -1
  117. package/dist/git/useEnvironmentChoices.js +1 -1
  118. package/dist/index.d.ts +4 -2
  119. package/dist/index.js +4 -2
  120. package/dist/keybindings/headless/catalog.js +1 -1
  121. package/dist/labels.d.ts +1 -0
  122. package/dist/labels.js +1 -0
  123. package/dist/machines/DaemonMenu.js +1 -1
  124. package/dist/machines/HostCapabilities.d.ts +23 -1
  125. package/dist/machines/HostCapabilities.js +149 -65
  126. package/dist/machines/HostSyncPanel.js +129 -123
  127. package/dist/machines/HostsSection.js +5 -1
  128. package/dist/machines/ServerCard.d.ts +4 -2
  129. package/dist/machines/ServerCard.js +222 -99
  130. package/dist/machines/ServerMachinesPanel.js +16 -13
  131. package/dist/machines/ServerUpdatePanel.js +31 -21
  132. package/dist/machines/TokensPanel.js +75 -53
  133. package/dist/machines/headless/fixture-host.d.ts +5 -0
  134. package/dist/machines/headless/fixture-host.js +13 -4
  135. package/dist/machines/headless/fixtures.js +1 -1
  136. package/dist/machines/headless/host.d.ts +22 -2
  137. package/dist/machines/headless/index.d.ts +2 -2
  138. package/dist/machines/headless/labels.d.ts +22 -1
  139. package/dist/machines/headless/labels.js +22 -2
  140. package/dist/machines/testing/contract.d.ts +3 -1
  141. package/dist/machines/testing/contract.js +9 -0
  142. package/dist/motion/size.d.ts +22 -1
  143. package/dist/motion/size.js +82 -4
  144. package/dist/onboarding/demos/delivery.d.ts +16 -0
  145. package/dist/onboarding/demos/delivery.js +132 -0
  146. package/dist/onboarding/demos/index.d.ts +2 -1
  147. package/dist/onboarding/demos/index.js +2 -1
  148. package/dist/onboarding/headless/labels.d.ts +8 -0
  149. package/dist/onboarding/headless/labels.js +7 -0
  150. package/dist/onboarding/index.d.ts +2 -1
  151. package/dist/onboarding/index.js +2 -1
  152. package/dist/onboarding/steps/ConversationStep.js +46 -4
  153. package/dist/palette/CommandPalette.js +3 -0
  154. package/dist/palette/PaletteDialog.js +7 -3
  155. package/dist/palette/usePaletteController.d.ts +10 -0
  156. package/dist/palette/usePaletteController.js +25 -3
  157. package/dist/server/sse-handler.js +6 -1
  158. package/dist/session/ArtifactsPanel.js +1 -1
  159. package/dist/session/SideChat.js +1 -2
  160. package/dist/session/UsagePanel.js +1 -1
  161. package/dist/session/headless/host.d.ts +3 -1
  162. package/dist/session/headless/index.js +2 -2
  163. package/dist/settings/SettingsRestricted.d.ts +25 -0
  164. package/dist/settings/SettingsRestricted.js +44 -0
  165. package/dist/settings/SettingsRowsPage.js +31 -37
  166. package/dist/settings/headless/catalog.js +7 -6
  167. package/dist/settings/headless/labels.d.ts +4 -2
  168. package/dist/settings/headless/labels.js +4 -2
  169. package/dist/settings/headless/layout.js +1 -1
  170. package/dist/settings/headless/providers.d.ts +2 -0
  171. package/dist/settings/headless/search.d.ts +1 -1
  172. package/dist/settings/index.d.ts +2 -2
  173. package/dist/settings/index.js +2 -2
  174. package/dist/settings/rows.d.ts +51 -8
  175. package/dist/settings/rows.js +81 -26
  176. package/dist/settings/sections/enabled-list/EnabledListSection.js +3 -2
  177. package/dist/settings/sections/environments/EnvironmentsSection.js +4 -3
  178. package/dist/settings/sections/extensions/ExtensionConfigRows.d.ts +2 -2
  179. package/dist/settings/sections/extensions/ExtensionConfigRows.js +22 -25
  180. package/dist/settings/sections/extensions/ExtensionOptionsSection.js +3 -2
  181. package/dist/settings/sections/extensions/options-labels.d.ts +2 -2
  182. package/dist/settings/sections/extensions/options-labels.js +2 -2
  183. package/dist/settings/sections/host-catalog/HostCatalogSection.d.ts +5 -3
  184. package/dist/settings/sections/host-catalog/HostCatalogSection.js +7 -4
  185. package/dist/settings/sections/instructions/InstructionsSection.js +24 -17
  186. package/dist/settings/sections/logs/LogsSection.d.ts +2 -1
  187. package/dist/settings/sections/logs/LogsSection.js +64 -56
  188. package/dist/settings/sections/mcp/McpSection.d.ts +1 -1
  189. package/dist/settings/sections/mcp/McpSection.js +18 -17
  190. package/dist/settings/sections/models/ModelRows.d.ts +3 -3
  191. package/dist/settings/sections/models/ModelRows.js +8 -7
  192. package/dist/settings/sections/performance/PerformanceSection.js +13 -4
  193. package/dist/settings/sections/performance/labels.d.ts +0 -1
  194. package/dist/settings/sections/performance/labels.js +0 -1
  195. package/dist/settings/sections/providers/CredentialSources.js +18 -8
  196. package/dist/settings/sections/providers/ProvidersSection.d.ts +2 -2
  197. package/dist/settings/sections/providers/ProvidersSection.js +20 -17
  198. package/dist/settings/sections/providers/ServerProvidersSection.d.ts +1 -1
  199. package/dist/settings/sections/providers/ServerProvidersSection.js +10 -14
  200. package/dist/settings/sections/shared/catalog.js +3 -2
  201. package/dist/settings/sections/skills/SkillList.js +3 -2
  202. package/dist/settings/testing/accounts-demo.js +1 -0
  203. package/dist/settings/testing/accounts.contract.js +2 -0
  204. package/dist/styles.css +160 -24
  205. package/dist/terminal/HostTerminalSurface.js +1 -1
  206. package/dist/terminal/headless/links.d.ts +19 -1
  207. package/dist/terminal/headless/links.js +26 -2
  208. package/dist/theme/css-vars.js +1 -0
  209. package/dist/theme/derive.d.ts +4 -0
  210. package/dist/theme/derive.js +2 -0
  211. package/dist/workspace/PanelContentView.js +1 -1
  212. package/dist/workspace/PanelHome.d.ts +3 -1
  213. package/dist/workspace/PanelHome.js +4 -3
  214. package/dist/workspace/SidePanel.d.ts +3 -1
  215. package/dist/workspace/SidePanel.js +3 -2
  216. package/dist/workspace/headless/open-target.d.ts +1 -1
  217. package/dist/workspace/headless/open-target.js +2 -2
  218. package/package.json +2 -2
  219. package/src/styles/chat.css +10 -10
  220. package/src/styles/motion.css +37 -0
  221. package/src/styles/primitives.css +11 -3
  222. package/src/styles/sidebar.css +12 -0
  223. package/src/styles/tokens.css +6 -0
  224. package/src/styles/transcript.css +69 -5
@@ -24,7 +24,7 @@ import { usePromptGhost } from "./use-prompt-ghost.js";
24
24
  import { useStagedAttachments } from "./use-staged-attachments.js";
25
25
  import { jsx, jsxs } from "react/jsx-runtime";
26
26
  import { useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
27
- import { createSkillsCompletionProvider, uniqueSkillNamesFromReferences } from "zidane/chat/pure";
27
+ import { createSkillsCompletionProvider, otherDelivery, uniqueSkillNamesFromReferences } from "zidane/chat/pure";
28
28
  import { EditorContent, useEditorState } from "@tiptap/react";
29
29
  //#region src/chat/rich-composer/RichComposer.tsx
30
30
  /**
@@ -62,12 +62,17 @@ const DRAFT_HAS_TEXT = "\0draft";
62
62
  *
63
63
  * @param props - What the composer edits and sends, and its slots.
64
64
  */
65
- function RichComposer({ disabled, folderPath, fileRoot, activeSessionId = null, hostId = null, running = false, onStop, onSend, leadingActions, trailingActions, badges, searchStatus = null, onSearchQueryChange, onSearchNavigate, searchOverlayOpen = false, remoteSearchApply = null, attachmentsEnabled = true, inlineAutocomplete = false, promptSuggestions = false, scopeKey = null, commandArgs, siteIcons, attachmentLabel, textPreview, footer, className, style }) {
65
+ function RichComposer({ disabled, folderPath, fileRoot, activeSessionId = null, hostId = null, running = false, messageDelivery = "queue", onStop, onSend, leadingActions, trailingActions, badges, searchStatus = null, onSearchQueryChange, onSearchNavigate, searchOverlayOpen = false, remoteSearchApply = null, attachmentsEnabled = true, inlineAutocomplete = false, promptSuggestions = false, scopeKey = null, commandArgs, siteIcons, attachmentLabel, textPreview, footer, className, style }) {
66
66
  const { completions, autocomplete } = useComposerHost();
67
67
  const labels = useComposerLabels();
68
68
  const history = usePromptHistory();
69
69
  const [historyStatus, setHistoryStatus] = useState(null);
70
70
  const editorRef = useRef(null);
71
+ const runningPlaceholder = running ? messageDelivery === "steer" ? labels.placeholderSteer : labels.placeholderQueue : null;
72
+ const runningPlaceholderRef = useRef(runningPlaceholder);
73
+ useLayoutEffect(() => {
74
+ runningPlaceholderRef.current = runningPlaceholder;
75
+ });
71
76
  const liveEditor = () => {
72
77
  const ed = editorRef.current;
73
78
  return ed && !ed.isDestroyed ? ed : null;
@@ -152,7 +157,8 @@ function RichComposer({ disabled, folderPath, fileRoot, activeSessionId = null,
152
157
  history,
153
158
  activeSessionId,
154
159
  hostId,
155
- send: () => send(),
160
+ runningPlaceholderRef,
161
+ send: (alternate) => send(alternate),
156
162
  onSearchNavigate,
157
163
  applyMode,
158
164
  applyGhost,
@@ -163,6 +169,19 @@ function RichComposer({ disabled, folderPath, fileRoot, activeSessionId = null,
163
169
  useEffect(() => {
164
170
  editorRef.current = editor;
165
171
  }, [editor]);
172
+ const focusOnCreate = useRef(false);
173
+ useEffect(() => {
174
+ if (!editor) return;
175
+ const onCreate = () => {
176
+ if (!focusOnCreate.current) return;
177
+ focusOnCreate.current = false;
178
+ editor.commands.focus("end");
179
+ };
180
+ editor.on("create", onCreate);
181
+ return () => {
182
+ editor.off("create", onCreate);
183
+ };
184
+ }, [editor]);
166
185
  useEffect(() => {
167
186
  if (!editor || editor.isDestroyed) return;
168
187
  const scope = () => ({
@@ -199,7 +218,7 @@ function RichComposer({ disabled, folderPath, fileRoot, activeSessionId = null,
199
218
  setHistoryStatus(null);
200
219
  if (activeSessionId) history.reset(activeSessionId);
201
220
  }, [activeSessionId, history]);
202
- const send = () => {
221
+ const send = (alternate = false) => {
203
222
  if (!editor) return;
204
223
  const raw = editorJsonToMarkdown(editor.getJSON(), { blockGap: "\n" }).trim();
205
224
  const text = composeSubmitText(modeRef.current, raw);
@@ -210,7 +229,7 @@ function RichComposer({ disabled, folderPath, fileRoot, activeSessionId = null,
210
229
  replaceAttachments([]);
211
230
  mirror.dropStash();
212
231
  setHistoryStatus(null);
213
- dispatch(text, submitted);
232
+ dispatch(text, submitted, running ? { delivery: alternate ? otherDelivery(messageDelivery) : messageDelivery } : void 0);
214
233
  };
215
234
  const sendText = (raw) => {
216
235
  const text = raw.trim();
@@ -276,7 +295,7 @@ function RichComposer({ disabled, folderPath, fileRoot, activeSessionId = null,
276
295
  useEffect(() => {
277
296
  const ed = editorRef.current;
278
297
  if (ed && !ed.isDestroyed) ed.view.dispatch(ed.state.tr);
279
- }, [composerMode]);
298
+ }, [composerMode, runningPlaceholder]);
280
299
  useEffect(() => {
281
300
  if (modeRef.current === "search") liveEditor()?.commands.clearContent(true);
282
301
  live.current.applyMode("normal");
@@ -315,7 +334,9 @@ function RichComposer({ disabled, folderPath, fileRoot, activeSessionId = null,
315
334
  liveEditor()?.chain().focus("end").insertContent(text).run();
316
335
  },
317
336
  focus: () => {
318
- liveEditor()?.commands.focus("end");
337
+ const ed = liveEditor();
338
+ if (ed?.isInitialized) ed.commands.focus("end");
339
+ else focusOnCreate.current = true;
319
340
  },
320
341
  prompt: sendText,
321
342
  draft: ({ text, attachments: next }) => {
@@ -32,6 +32,8 @@ interface ComposerExtensionsContext {
32
32
  modeRef: RefObject<ComposerMode>;
33
33
  /** Re-evaluate the ghost after a gate input flipped without an edit. */
34
34
  ghostGateChangedRef: RefObject<() => void>;
35
+ /** The placeholder while a run works (what Enter and Alt+Enter do), `null` otherwise. */
36
+ runningPlaceholderRef: RefObject<string | null>;
35
37
  labels: ComposerLabels;
36
38
  }
37
39
  /** The editor's extensions. */
@@ -28,7 +28,7 @@ const MentionChat = Mention.extend({ name: "mentionChat" });
28
28
  const MAX_RESULTS = 8;
29
29
  /** The editor's extensions. */
30
30
  function composerExtensions(ctx) {
31
- const { disabled, folderPath, extensionCompletionProviders, extensionItems, sources, filesProvider, slashSource, chatsProvider, chatScopeRef, mentionPopovers, promptGhost, modeRef, ghostGateChangedRef, labels } = ctx;
31
+ const { disabled, folderPath, extensionCompletionProviders, extensionItems, sources, filesProvider, slashSource, chatsProvider, chatScopeRef, mentionPopovers, promptGhost, modeRef, ghostGateChangedRef, runningPlaceholderRef, labels } = ctx;
32
32
  const chatScopeLabel = (scope) => scope === "project" ? labels.chatScopeProject : labels.chatScopeAll;
33
33
  return [
34
34
  StarterKit.configure({
@@ -54,7 +54,7 @@ function composerExtensions(ctx) {
54
54
  if (promptGhost.painted()) return "";
55
55
  if (modeRef.current === "shell") return labels.placeholderShell;
56
56
  if (modeRef.current === "search") return labels.placeholderSearch;
57
- return labels.placeholderChat;
57
+ return runningPlaceholderRef.current ?? labels.placeholderChat;
58
58
  }
59
59
  }),
60
60
  Mention.configure({
@@ -87,7 +87,7 @@ function composerEditorProps(latest, name) {
87
87
  if (event.key === "Enter" && !mentionPopovers.open) {
88
88
  event.preventDefault();
89
89
  if (event.shiftKey) editorRef.current?.chain().focus().splitBlock().run();
90
- else latest.current.send();
90
+ else latest.current.send(event.altKey);
91
91
  return true;
92
92
  }
93
93
  if ((event.key === "ArrowUp" || event.key === "ArrowDown") && !event.shiftKey && !event.metaKey && !event.ctrlKey && !event.altKey && !mentionPopovers.open) {
@@ -1,4 +1,4 @@
1
- import { claimComposerFocus, onComposerAttachment, onComposerDraft, onComposerFocus, onComposerInsert, onComposerMention, onComposerPrompt, registerComposerScope } from "../composer/composer-bus.js";
1
+ import { claimComposerFocus, onComposerAttachment, onComposerDraft, onComposerFocus, onComposerInsert, onComposerMention, onComposerPrompt, registerComposerScope, takePendingComposerFocus } from "../composer/composer-bus.js";
2
2
  import { useEffect, useLayoutEffect, useRef } from "react";
3
3
  //#region src/chat/rich-composer/use-composer-bus.ts
4
4
  /**
@@ -23,6 +23,7 @@ function useComposerBus(key, handlers) {
23
23
  onComposerPrompt(key, (text) => latest.current.prompt(text)),
24
24
  onComposerDraft(key, (draft) => latest.current.draft(draft))
25
25
  ];
26
+ if (takePendingComposerFocus()) latest.current.focus();
26
27
  return () => {
27
28
  for (const off of offs) off();
28
29
  };
@@ -23,6 +23,8 @@ interface ComposerEditorOptions {
23
23
  /** The live editor (the one this hook returns, once pinned). */
24
24
  editorRef: RefObject<Editor | null>;
25
25
  modeRef: RefObject<ComposerMode>;
26
+ /** The placeholder while a run works, `null` otherwise. */
27
+ runningPlaceholderRef: RefObject<string | null>;
26
28
  /** The inline ghost on display. */
27
29
  ghostRef: RefObject<InlineGhost | null>;
28
30
  /** True while a history recall's own transactions run (no ghost for them). */
@@ -33,7 +35,8 @@ interface ComposerEditorOptions {
33
35
  history: ReturnType<typeof usePromptHistory>;
34
36
  activeSessionId: string | null;
35
37
  hostId: string | null;
36
- send(): void;
38
+ /** Send the draft. `alternate`: Alt+Enter, the other delivery while a run works. */
39
+ send(alternate?: boolean): void;
37
40
  onSearchNavigate: ((direction: 1 | -1) => void) | undefined;
38
41
  applyMode(next: ComposerMode): void;
39
42
  applyGhost(next: InlineGhost | null): void;
@@ -18,7 +18,7 @@ import { useEditor } from "@tiptap/react";
18
18
  * state through the refs it passes.
19
19
  */
20
20
  function useComposerEditor(options) {
21
- const { disabled, folderPath, extensionCompletionProviders, sources, skillsProvider, mentionPopovers, promptGhost, modeRef, ghostGateChangedRef } = options;
21
+ const { disabled, folderPath, extensionCompletionProviders, sources, skillsProvider, mentionPopovers, promptGhost, modeRef, runningPlaceholderRef, ghostGateChangedRef } = options;
22
22
  const completions = useComposerHost().completions;
23
23
  const labels = useComposerLabels();
24
24
  const latest = useRef(options);
@@ -85,6 +85,7 @@ function useComposerEditor(options) {
85
85
  promptGhost,
86
86
  modeRef,
87
87
  ghostGateChangedRef,
88
+ runningPlaceholderRef,
88
89
  labels
89
90
  }),
90
91
  editable: !disabled,
@@ -121,7 +121,7 @@ function NewSessionButton({ onNewSession, isActive, className = "", style }) {
121
121
  children: /* @__PURE__ */ jsx("span", { className: "i-ph-plus text-sm text-[var(--zg-text-muted)] group-data-[active=true]:text-[var(--zg-text)]" })
122
122
  }),
123
123
  /* @__PURE__ */ jsx("span", {
124
- className: "text-sm text-[var(--zg-text-muted)] group-data-[active=true]:text-[var(--zg-text)]",
124
+ className: "text-sm text-[var(--zg-sidebar-label-fg,var(--zg-text-muted))] group-data-[active=true]:text-[var(--zg-text)]",
125
125
  children: labels.newSession
126
126
  }),
127
127
  /* @__PURE__ */ jsx("span", { className: "flex-1" }),
@@ -314,7 +314,6 @@ function SessionSidebar(props) {
314
314
  sessions: displaySessions,
315
315
  activeSessionId,
316
316
  onActivate,
317
- onNewSession,
318
317
  ...keyboardBlocked ? { blocked: keyboardBlocked } : {}
319
318
  });
320
319
  const onRowSelect = useCallback((sessionId) => {
@@ -34,7 +34,7 @@ function SidebarSearchRow({ value, onChange, filter, className, style }) {
34
34
  placeholder: labels.searchSessions,
35
35
  "aria-label": labels.searchSessionsLabel,
36
36
  spellCheck: false,
37
- className: "flex-1 min-w-0 bg-transparent border-0 outline-none p-0 text-sm text-[var(--zg-text)] placeholder:text-[var(--zg-text-muted)]"
37
+ className: "flex-1 min-w-0 bg-transparent border-0 outline-none p-0 text-sm text-[var(--zg-text)] placeholder:text-[var(--zg-sidebar-label-fg,var(--zg-text-muted))]"
38
38
  }),
39
39
  value.length > 0 && /* @__PURE__ */ jsx(IconButton, {
40
40
  icon: "i-ph-x",
@@ -12,8 +12,6 @@ interface SidebarKeyboardNavOptions<K extends string> {
12
12
  activeSessionId: K | null;
13
13
  /** Opens a session (`null`: none, the new-session surface). */
14
14
  onActivate: (sessionId: K | null) => void;
15
- /** Starts a new session (⌘N / Ctrl+N). */
16
- onNewSession: () => void;
17
15
  /** The keys belong elsewhere now (a modal is open). Read at each key. */
18
16
  blocked?: () => boolean;
19
17
  }
@@ -3,7 +3,9 @@ import { useCallback, useEffect, useRef } from "react";
3
3
  //#region src/chat/sidebar/useSidebarKeyboardNav.ts
4
4
  /**
5
5
  * The sidebar's keyboard: ↑ / ↓ walk the chats in the order the list shows
6
- * them, ⌘N / Ctrl+N starts a new one. Only sessions: an extension's item row
6
+ * them. ⌘N / Ctrl+N is the `newSession` key command, not this hook's: the
7
+ * dispatcher fires it from the composer too, and a new session's
8
+ * `activeSessionId` change drops the cursor here. Only sessions: an extension's item row
7
9
  * is skipped, since the walk opens what it lands on and an item's target may
8
10
  * run an extension command (Tab reaches the item instead).
9
11
  *
@@ -12,7 +14,7 @@ import { useCallback, useEffect, useRef } from "react";
12
14
  * DOM, no React render in the burst, and scrolls it into view
13
15
  * (`block: 'nearest'`). Only once the presses stop for `NAV_COMMIT_MS` does
14
16
  * the session open (`onActivate`), so the heavy switch runs once per burst.
15
- * Clicks, ⌘N and outside changes commit at once (`commitImmediate`).
17
+ * Clicks and outside changes commit at once (`commitImmediate`).
16
18
  *
17
19
  * The keydown listener binds ONCE: every value it reads goes through refs, so
18
20
  * a list refresh never re-attaches it.
@@ -29,7 +31,7 @@ const NAV_COMMIT_MS = 20;
29
31
  * @returns `commitImmediate`: open a session now, cancelling a pending arrow commit.
30
32
  */
31
33
  function useSidebarKeyboardNav(options) {
32
- const { listRef, sessions, activeSessionId, onActivate, onNewSession, blocked } = options;
34
+ const { listRef, sessions, activeSessionId, onActivate, blocked } = options;
33
35
  const navRef = useRef({
34
36
  tentativeId: null,
35
37
  commitTimer: null
@@ -37,13 +39,11 @@ function useSidebarKeyboardNav(options) {
37
39
  const latest = useRef({
38
40
  sessions,
39
41
  onActivate,
40
- onNewSession,
41
42
  blocked
42
43
  });
43
44
  latest.current = {
44
45
  sessions,
45
46
  onActivate,
46
- onNewSession,
47
47
  blocked
48
48
  };
49
49
  const activeSessionIdRef = useRef(activeSessionId);
@@ -85,7 +85,7 @@ function useSidebarKeyboardNav(options) {
85
85
  }, NAV_COMMIT_MS);
86
86
  }, [applyTentative, cancelCommitTimer]);
87
87
  /**
88
- * Synchronous commit (clicks, ⌘N). Updates the ref BEFORE the state setter so a keydown in the same tick
88
+ * Synchronous commit (clicks, the New session button). Updates the ref BEFORE the state setter so a keydown in the same tick
89
89
  * reads the new value.
90
90
  */
91
91
  const commitImmediate = useCallback((id) => {
@@ -107,12 +107,6 @@ function useSidebarKeyboardNav(options) {
107
107
  function onKey(e) {
108
108
  if (latest.current.blocked?.()) return;
109
109
  if (keyScopeOf(e) !== "app") return;
110
- if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "n") {
111
- e.preventDefault();
112
- commitImmediate(null);
113
- latest.current.onNewSession();
114
- return;
115
- }
116
110
  if (e.key !== "ArrowDown" && e.key !== "ArrowUp") return;
117
111
  const list = latest.current.sessions;
118
112
  if (list.length === 0) return;
@@ -126,7 +120,7 @@ function useSidebarKeyboardNav(options) {
126
120
  }
127
121
  window.addEventListener("keydown", onKey);
128
122
  return () => window.removeEventListener("keydown", onKey);
129
- }, [commitDebounced, commitImmediate]);
123
+ }, [commitDebounced]);
130
124
  return { commitImmediate };
131
125
  }
132
126
  //#endregion
@@ -6,7 +6,7 @@ import { BadgeActionEffect, BadgeMenuItem, ExtensionReferenceView, ReferenceProv
6
6
  import { InteractionRequest } from "zidane/chat";
7
7
  //#region src/chat/testing/contract.d.ts
8
8
  /** An optional `ChatHost` member, by its path. */
9
- type ChatHostCapability = 'sessions' | 'sessions.create' | 'sessions.rename' | 'sessions.setArchived' | 'sessions.setFavorite' | 'sessions.remove' | 'sessions.query' | 'sessions.setSpace' | 'sessions.search' | 'sessions.onChanged' | 'sessions.views' | 'sessions.main' | 'send' | 'interrupt' | 'shell' | 'providerReady' | 'steer' | 'followUp' | 'interactions' | 'interactions.cancel' | 'interactions.all' | 'models' | 'toolOutput' | 'cancelTool' | 'searchSync' | 'references' | 'paths' | 'chips' | 'chips.commit' | 'chips.sessions' | 'chips.context' | 'chips.skills' | 'chips.commands' | 'badges' | 'siteIcons' | 'blockRuns';
9
+ type ChatHostCapability = 'sessions' | 'sessions.create' | 'sessions.rename' | 'sessions.setArchived' | 'sessions.setFavorite' | 'sessions.remove' | 'sessions.query' | 'sessions.setSpace' | 'sessions.search' | 'sessions.onChanged' | 'sessions.views' | 'sessions.main' | 'send' | 'interrupt' | 'shell' | 'providerReady' | 'interactions' | 'interactions.cancel' | 'interactions.all' | 'models' | 'toolOutput' | 'cancelTool' | 'searchSync' | 'references' | 'paths' | 'chips' | 'chips.commit' | 'chips.sessions' | 'chips.context' | 'chips.skills' | 'chips.commands' | 'badges' | 'siteIcons' | 'blockRuns';
10
10
  /** One thing the backend does while it runs a prompt, in order. */
11
11
  type ChatHostScriptStep =
12
12
  /** Stream a markdown reply with this text. */
@@ -19,8 +19,6 @@ const CAPABILITIES = [
19
19
  "interrupt",
20
20
  "shell",
21
21
  "providerReady",
22
- "steer",
23
- "followUp",
24
22
  "interactions",
25
23
  "interactions.cancel",
26
24
  "interactions.all",
@@ -339,18 +337,21 @@ function describeChatHostContract(name, makeHarness, capabilities) {
339
337
  await waitFor(() => expect(t.get().running).toBe(false));
340
338
  if (has("sessions")) await waitFor(() => expect(list().find((s) => s.id === "c-2")?.status).not.toBe("running"));
341
339
  });
342
- it.skipIf(!has("send", "interrupt", "steer", "followUp"))("steer and followUp resolve false when idle, true while a run is in flight", async () => {
340
+ it.skipIf(!has("send", "interrupt"))("a steer sent while a run works joins that run", async () => {
343
341
  const { host, watch } = await setup();
344
342
  const t = await watch("c-2");
345
- expect(await host.steer("c-2", "nudge")).toBe(false);
346
- expect(await host.followUp("c-2", "later")).toBe(false);
347
343
  host.send({
348
344
  sessionId: "c-2",
349
345
  prompt: "hold"
350
346
  }).catch(() => {});
351
347
  await waitFor(() => expect(t.get().running).toBe(true));
352
- expect(await host.steer("c-2", "nudge")).toBe(true);
353
- expect(await host.followUp("c-2", "later")).toBe(true);
348
+ await host.send({
349
+ sessionId: "c-2",
350
+ prompt: "nudge",
351
+ delivery: "steer"
352
+ });
353
+ await waitFor(() => expect(t.get().events.some((e) => e.kind === "user-prompt" && e.text === "nudge")).toBe(true));
354
+ expect(t.get().running).toBe(true);
354
355
  host.interrupt("c-2");
355
356
  await waitFor(() => expect(t.get().running).toBe(false));
356
357
  });
@@ -22,6 +22,13 @@ interface BadgeMenuSystem {
22
22
  /** Shows a path in the file manager. */
23
23
  revealPath(path: string): Promise<boolean>;
24
24
  }
25
+ /** Where web links open: the default browser, and the app's own browser pane. */
26
+ interface BadgeMenuBrowser {
27
+ /** The default browser's name ("Arc"): the row reads "Open in Arc". `null` when unknown: "Open in browser". */
28
+ name: string | null;
29
+ /** Opens a web link in the app's browser pane. Absent: no "Open in browser pane" row. */
30
+ openInPane?: ((url: string) => void) | undefined;
31
+ }
25
32
  /** What {@link applyBadgeEffect} applies an effect with. */
26
33
  interface BadgeEffectContext {
27
34
  /** The badge acted on. */
@@ -45,19 +52,21 @@ interface BadgeMenuProviderProps {
45
52
  composer?: BadgeMenuComposer;
46
53
  /** Opens and reveals paths. Absent: path actions answer "Path unavailable." */
47
54
  system?: BadgeMenuSystem;
55
+ /** The default browser's name and the browser pane. Absent: "Open in browser" only. */
56
+ browser?: BadgeMenuBrowser;
48
57
  /** The surfaces whose badges open the menu. */
49
58
  children: ReactNode;
50
59
  }
51
60
  /**
52
61
  * Provides the badge menu to the badges below it and renders it (and its notice) when one opens.
53
62
  *
54
- * @param props - The composer and the path opener the effects use, and the children.
63
+ * @param props - The composer, the path opener and the browser the effects use, and the children.
55
64
  */
56
- declare function BadgeMenuProvider({ composer, system, children }: BadgeMenuProviderProps): import("react").JSX.Element;
65
+ declare function BadgeMenuProvider({ composer, system, browser, children }: BadgeMenuProviderProps): import("react").JSX.Element;
57
66
  /**
58
67
  * Opens the badge menu for a badge. Outside the provider (a test, a detached
59
68
  * surface) the event is left alone, so the surrounding menu still opens.
60
69
  */
61
70
  declare function useBadgeMenu(): OpenBadgeMenu | null;
62
71
  //#endregion
63
- export { BadgeEffectContext, BadgeMenuComposer, BadgeMenuProvider, BadgeMenuProviderProps, BadgeMenuSystem, OpenBadgeMenu, useBadgeMenu };
72
+ export { BadgeEffectContext, BadgeMenuBrowser, BadgeMenuComposer, BadgeMenuProvider, BadgeMenuProviderProps, BadgeMenuSystem, OpenBadgeMenu, useBadgeMenu };
@@ -23,6 +23,8 @@ import { builtinBadgeActions, builtinBadgeEffect, mergeBadgeMenu } from "zidane/
23
23
  * the turn's own menu never opens over a badge.
24
24
  */
25
25
  const BadgeMenuContext = createContext(null);
26
+ /** The kit's own row next to "Open in browser", shown when the host has a browser pane. */
27
+ const OPEN_IN_PANE = "open-browser-pane";
26
28
  const NOTICE_MS = 2400;
27
29
  /** `abs` relative to `cwd` when inside it: the form the composer's `@` mentions use. */
28
30
  function relativeTo(abs, cwd) {
@@ -58,9 +60,9 @@ async function applyBadgeEffect(effect, ctx) {
58
60
  /**
59
61
  * Provides the badge menu to the badges below it and renders it (and its notice) when one opens.
60
62
  *
61
- * @param props - The composer and the path opener the effects use, and the children.
63
+ * @param props - The composer, the path opener and the browser the effects use, and the children.
62
64
  */
63
- function BadgeMenuProvider({ composer, system, children }) {
65
+ function BadgeMenuProvider({ composer, system, browser, children }) {
64
66
  const extensions = useExtensionsHost();
65
67
  const badges = useOptionalChatHost()?.badges ?? extensionsBadgeActions(extensions);
66
68
  const labels = useChatLabels();
@@ -98,7 +100,17 @@ function BadgeMenuProvider({ composer, system, children }) {
98
100
  const items = useMemo(() => {
99
101
  if (!open) return [];
100
102
  const { target, cwd, at } = open;
101
- return mergeBadgeMenu([...builtinBadgeActions(target), ...open.extra]).map((item) => ({
103
+ const builtins = builtinBadgeActions(target).flatMap((item) => item.id !== "open-browser" ? [item] : [browser?.name ? {
104
+ ...item,
105
+ label: labels.badgeOpenIn(browser.name)
106
+ } : item, ...browser?.openInPane ? [{
107
+ id: OPEN_IN_PANE,
108
+ label: labels.badgeOpenInBrowserPane,
109
+ group: "open",
110
+ shortcut: "B",
111
+ source: "builtin"
112
+ }] : []]);
113
+ return mergeBadgeMenu([...builtins, ...open.extra]).map((item) => ({
102
114
  id: `${item.source}:${item.id}`,
103
115
  label: item.label,
104
116
  ...item.shortcut ? { shortcut: item.shortcut } : {},
@@ -107,6 +119,10 @@ function BadgeMenuProvider({ composer, system, children }) {
107
119
  title: labels.badgeNoComposer
108
120
  } : {},
109
121
  onSelect: () => {
122
+ if (item.source === "builtin" && item.id === OPEN_IN_PANE) {
123
+ if (target.href) browser?.openInPane?.(target.href);
124
+ return;
125
+ }
110
126
  (async () => {
111
127
  const text = await applyBadgeEffect(item.source === "builtin" ? builtinBadgeEffect(item.id, target) ?? {} : await (badges?.run(cwd, item.source, item.id, target) ?? Promise.resolve({})).catch((err) => ({ notice: err instanceof Error ? err.message : String(err) })), {
112
128
  target,
@@ -130,6 +146,7 @@ function BadgeMenuProvider({ composer, system, children }) {
130
146
  badges,
131
147
  composer,
132
148
  system,
149
+ browser,
133
150
  labels,
134
151
  links
135
152
  ]);