@marimo-team/islands 0.25.3-dev250 → 0.25.3-dev252

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 (147) hide show
  1. package/dist/{ConnectedDataExplorerComponent-BBihxHrr.js → ConnectedDataExplorerComponent-DGOAQFtf.js} +11 -11
  2. package/dist/{ErrorBoundary-Ks8L_DFj.js → ErrorBoundary-By2DNeOL.js} +7 -7
  3. package/dist/{abnfDiagram-VCTEODGH-DBcJVxYP.js → abnfDiagram-VCTEODGH-DzrZZXRD.js} +2 -2
  4. package/dist/{any-language-editor-jGaR0EAo.js → any-language-editor-Be7dA2VK.js} +3 -3
  5. package/dist/{architectureDiagram-5GKGNRK7-BcHZXF4S.js → architectureDiagram-5GKGNRK7-iF_snyFL.js} +1 -1
  6. package/dist/{button-B6RjDKJx.js → button-0-RqY1DW.js} +2 -1
  7. package/dist/{capabilities-CkVzWJEX.js → capabilities-B6yy_CBN.js} +1 -1
  8. package/dist/{chat-ui-DgaVxFrc.js → chat-ui-zbfp80GW.js} +1547 -1482
  9. package/dist/{check-Z_vtI3OL.js → check-CSvxZk5I.js} +1 -1
  10. package/dist/{chunk-IMKFNOWR-CTZlwBAA.js → chunk-IMKFNOWR-B7xytD4B.js} +1 -1
  11. package/dist/{chunk-SHT3W25Y-53g_RZHm.js → chunk-SHT3W25Y-I8ZFP5L1.js} +1 -1
  12. package/dist/{chunk-SVP7TREG-P6abNGV_.js → chunk-SVP7TREG-Di-uAPdT.js} +1 -1
  13. package/dist/{chunk-TICWLB2K-BTJJqkyF.js → chunk-TICWLB2K-BFpUy0ud.js} +1 -1
  14. package/dist/{classDiagram-ZZMXUADV-BUGqOrTm.js → classDiagram-ZZMXUADV-Cqm4JGpx.js} +2 -2
  15. package/dist/{classDiagram-v2-VYDZK3BY-CXpQvO-s.js → classDiagram-v2-VYDZK3BY-DFHRJpN3.js} +2 -2
  16. package/dist/{code-visibility-CCCqFZYh.js → code-visibility-Byb3BN4h.js} +4 -4
  17. package/dist/{common-JXDK9IjO.js → common-CgWyYqpC.js} +262 -262
  18. package/dist/{config-DbjzjwxX.js → config-mwEjAmvP.js} +2 -2
  19. package/dist/{copy-icon-CG57cxwb.js → copy-icon-DD8bdw4L.js} +3 -3
  20. package/dist/{createReducer-DePYJ0F3.js → createReducer-Bx81cw8_.js} +1 -1
  21. package/dist/{cynefinDiagram-5FMLGOSQ-DOcnl8wW.js → cynefinDiagram-5FMLGOSQ-cuGOeTBC.js} +1 -1
  22. package/dist/{dagre-GXQ25YYZ-CuHR9QfG.js → dagre-GXQ25YYZ-DJ76aC4k.js} +1 -1
  23. package/dist/{data-grid-overlay-editor-QAJSeJpH.js → data-grid-overlay-editor-BP_fYnBa.js} +1 -1
  24. package/dist/{diagram-S7CK7UJ4-BPiG4kso.js → diagram-S7CK7UJ4-DLLc0sw7.js} +1 -1
  25. package/dist/{diagram-UQ7AKVKN-_piBOvOC.js → diagram-UQ7AKVKN-Tz_d6qmk.js} +1 -1
  26. package/dist/{diagram-VX7I27RA-DCzVeJ8M.js → diagram-VX7I27RA-D7SzPrDN.js} +1 -1
  27. package/dist/{diagram-Z3DM3KII-D64CF1jQ.js → diagram-Z3DM3KII-qr37XV58.js} +1 -1
  28. package/dist/{dialog-DsZ49mmU.js → dialog-BHYPX87J.js} +3 -3
  29. package/dist/{dropdown-menu-BRiE7Vnp.js → dropdown-menu-Blu7V6Mk.js} +6 -6
  30. package/dist/{ebnfDiagram-PWID7BFC-BapQPML-.js → ebnfDiagram-PWID7BFC-BKOQb4J0.js} +2 -2
  31. package/dist/{erDiagram-RLTQ6QDP-CNQvC2_Q.js → erDiagram-RLTQ6QDP-CM15Vq68.js} +1 -1
  32. package/dist/{error-banner-IsonnvA0.js → error-banner-DbRFOd2O.js} +2 -2
  33. package/dist/{error-entries-CRPfxMqm.js → error-entries-BuIE3Ius.js} +9 -9
  34. package/dist/{feedback-button-Bg7zGcid.js → feedback-button-DPWFUVWl.js} +7 -7
  35. package/dist/{flowDiagram-HODETNUW-BBt6S6oe.js → flowDiagram-HODETNUW-Dt3N5yFT.js} +2 -2
  36. package/dist/{formats-BK4VefeF.js → formats-gGospKWJ.js} +2 -2
  37. package/dist/{glide-data-editor-BuFe7QfV.js → glide-data-editor-GvhMU_kg.js} +10 -10
  38. package/dist/{glide-utils-CQddGpa-.js → glide-utils-qbkRZpEy.js} +3 -3
  39. package/dist/{globals-8EK96XwF.js → globals-RlhgvYCL.js} +2 -2
  40. package/dist/{html-to-image-Gx4q3ZoD.js → html-to-image-BXrAd5q8.js} +460 -455
  41. package/dist/{infoDiagram-27XIBGKW-Hd8Ifz82.js → infoDiagram-27XIBGKW-C7axJMCl.js} +1 -1
  42. package/dist/{ishikawaDiagram-5VMMS53U-CP7hnHWx.js → ishikawaDiagram-5VMMS53U-CdC9Eur7.js} +1 -1
  43. package/dist/{kanban-definition-UXKFOSKX-CX91sa9b.js → kanban-definition-UXKFOSKX-BHnMIgD7.js} +1 -1
  44. package/dist/{loader-BisjOLiW.js → loader-BVEtQ4F3.js} +1 -1
  45. package/dist/main.js +236 -236
  46. package/dist/{mermaid-Djb3Ni9t.js → mermaid-DFYN_PXd.js} +35 -35
  47. package/dist/{mindmap-definition-YA3MSWOX-DgCNo07O.js → mindmap-definition-YA3MSWOX-BagHoCGU.js} +1 -1
  48. package/dist/{number-overlay-editor-BDJF1c8h.js → number-overlay-editor-Dg1EfcEs.js} +1 -1
  49. package/dist/{pegDiagram-XKGWAZYB-SGJhnIKo.js → pegDiagram-XKGWAZYB-8IIZczip.js} +2 -2
  50. package/dist/{pieDiagram-E7YTZNPT-1stXIWCQ.js → pieDiagram-E7YTZNPT-CE6dKBIr.js} +1 -1
  51. package/dist/{process-output-D2ISIyIs.js → process-output-D2ZjxrHe.js} +4 -4
  52. package/dist/{railroadDiagram-O6MQD6OU-DY1mojzB.js → railroadDiagram-O6MQD6OU-CkexPq3b.js} +2 -2
  53. package/dist/{requirementDiagram-BXWQKSXE-gHzhUp0D.js → requirementDiagram-BXWQKSXE-B0-OkuAe.js} +1 -1
  54. package/dist/{reveal-component-B_hNJfj-.js → reveal-component-UPs4Rd1M.js} +8 -8
  55. package/dist/{select-BSxlITBo.js → select-CcidPqlM.js} +5 -5
  56. package/dist/{sequenceDiagram-WJ2MYXX4-rjPtD9fB.js → sequenceDiagram-WJ2MYXX4-Dxysg8DX.js} +1 -1
  57. package/dist/{spec-Dq8RNplp.js → spec-ZwfZcgsm.js} +4 -4
  58. package/dist/{stateDiagram-D77RDMKH-DMNxjUxV.js → stateDiagram-D77RDMKH-DTEN3-OJ.js} +2 -2
  59. package/dist/{stateDiagram-v2-MP3YSRHH-CumAaqGo.js → stateDiagram-v2-MP3YSRHH-vktk3Xc5.js} +2 -2
  60. package/dist/{strings-BGW-fXVJ.js → strings-BYJNyKmg.js} +3 -3
  61. package/dist/style.css +1 -1
  62. package/dist/{swimlanes-42K2YHIH-BU14_Tej.js → swimlanes-42K2YHIH-Cjveh9OM.js} +1 -1
  63. package/dist/{swimlanesDiagram-VR7AAH4N-CieAnPqJ.js → swimlanesDiagram-VR7AAH4N-Bdr7Ev_2.js} +2 -2
  64. package/dist/{swiper-component-Dir1o8ml.js → swiper-component-Dvj-goaD.js} +2 -2
  65. package/dist/{timeline-definition-24CTP7MA-CrVj4E1K.js → timeline-definition-24CTP7MA-KIuDcefn.js} +1 -1
  66. package/dist/{toast-BWIPi3aF.js → toast-CkB5Jq9K.js} +4 -4
  67. package/dist/{tooltip-C3UnvsRe.js → tooltip-Bzl4k00l.js} +2 -2
  68. package/dist/{use-restore-focus-BWO0z1lo.js → use-restore-focus-CmflVLWJ.js} +1 -1
  69. package/dist/{useDateFormatter-ChB4eyim.js → useDateFormatter-CYo5fm8s.js} +2 -2
  70. package/dist/{useIframeCapabilities-cMNQ80CK.js → useIframeCapabilities-BIolGsF1.js} +1 -1
  71. package/dist/{useLifecycle-BquH9ET-.js → useLifecycle-GNgvNwI-.js} +2 -2
  72. package/dist/{useTheme-DgOLVISn.js → useTheme-BZ2GGLgm.js} +141 -139
  73. package/dist/{vega-component-Bwg_Kuh-.js → vega-component-DOtem7Hf.js} +7 -7
  74. package/dist/{vennDiagram-4TSXK5OY-ZLxQ8yhm.js → vennDiagram-4TSXK5OY-DrIOUHfb.js} +1 -1
  75. package/dist/{wardleyDiagram-VM6X3IG4-D88pcLY1.js → wardleyDiagram-VM6X3IG4-CJNjf4OH.js} +1 -1
  76. package/dist/{xychartDiagram-S5SC5T6Z-_13d4g8g.js → xychartDiagram-S5SC5T6Z-DEshFho-.js} +1 -1
  77. package/package.json +1 -1
  78. package/src/__tests__/fixtures/pair-config.ts +1 -1
  79. package/src/components/app-config/__tests__/state.test.tsx +50 -0
  80. package/src/components/app-config/ai-config.tsx +1 -1
  81. package/src/components/chat/__tests__/capabilities-popover.test.tsx +0 -1
  82. package/src/components/chat/__tests__/chat-display.test.tsx +125 -0
  83. package/src/components/chat/__tests__/chat-markdown.test.tsx +43 -0
  84. package/src/components/chat/__tests__/chat-utils.test.ts +51 -1
  85. package/src/components/chat/acp/__tests__/agent-panel-initialization.test.tsx +1 -1
  86. package/src/components/chat/acp/__tests__/agent-panel.test.tsx +22 -17
  87. package/src/components/chat/acp/agent-panel.tsx +4 -3
  88. package/src/components/chat/acp/blocks.tsx +10 -10
  89. package/src/components/chat/acp/common.tsx +4 -5
  90. package/src/components/chat/acp/model-selector.tsx +1 -1
  91. package/src/components/chat/capabilities-popover.tsx +1 -6
  92. package/src/components/chat/chat-components.tsx +7 -4
  93. package/src/components/chat/chat-display.tsx +51 -6
  94. package/src/components/chat/chat-history-popover.tsx +6 -1
  95. package/src/components/chat/chat-markdown.tsx +15 -0
  96. package/src/components/chat/chat-panel.tsx +105 -119
  97. package/src/components/chat/chat-typography.css +108 -0
  98. package/src/components/chat/chat-utils.ts +8 -3
  99. package/src/components/chat/notebook-access-selector.tsx +1 -1
  100. package/src/components/chat/reasoning-accordion.tsx +3 -3
  101. package/src/components/chat/tool-call/tool-activity-group.tsx +49 -0
  102. package/src/components/chat/tool-call/tool-history-row.tsx +3 -30
  103. package/src/components/chat/tool-call/tool-result.tsx +1 -1
  104. package/src/components/chat/use-chat-generation.ts +19 -0
  105. package/src/components/editor/actions/__tests__/pair-with-agent-commands.test.ts +18 -37
  106. package/src/components/editor/actions/__tests__/pair-with-agent-modal.test.tsx +5 -8
  107. package/src/components/editor/actions/__tests__/pair-with-agent.test.tsx +5 -19
  108. package/src/components/editor/actions/pair-with-agent-commands.ts +0 -1
  109. package/src/components/editor/actions/pair-with-agent-modal.tsx +7 -11
  110. package/src/components/editor/actions/pair-with-agent.tsx +3 -31
  111. package/src/components/editor/actions/useCellActionButton.tsx +1 -1
  112. package/src/components/editor/ai/ai-completion-editor.tsx +1 -1
  113. package/src/components/editor/ai/prompt-input.tsx +189 -0
  114. package/src/components/editor/cell/StagedAICell.tsx +3 -3
  115. package/src/components/editor/cell/code/cell-editor.tsx +14 -1
  116. package/src/components/editor/chrome/panels/panel-header.tsx +13 -0
  117. package/src/components/editor/chrome/types.ts +1 -1
  118. package/src/components/editor/chrome/wrapper/__tests__/pending-ai-cells.test.tsx +53 -45
  119. package/src/components/editor/chrome/wrapper/__tests__/useAskAiAboutCell.test.tsx +58 -0
  120. package/src/components/editor/chrome/wrapper/app-chrome.tsx +89 -72
  121. package/src/components/editor/chrome/wrapper/pending-ai-cells.tsx +5 -8
  122. package/src/components/editor/chrome/wrapper/useAskAiAboutCell.ts +36 -0
  123. package/src/components/editor/renderers/__tests__/add-cell-buttons.test.tsx +18 -0
  124. package/src/components/editor/renderers/cell-array.tsx +10 -67
  125. package/src/components/markdown/markdown-renderer.tsx +35 -20
  126. package/src/components/mcp/mcp-status-indicator.tsx +18 -14
  127. package/src/core/ai/__tests__/staged-cells.test.ts +1 -467
  128. package/src/core/ai/context/context.ts +2 -0
  129. package/src/core/ai/context/providers/__tests__/cell.test.ts +49 -0
  130. package/src/core/ai/context/providers/cell.ts +54 -0
  131. package/src/core/ai/staged-cells.ts +4 -280
  132. package/src/core/ai/state.ts +3 -0
  133. package/src/core/codemirror/__tests__/setup.test.ts +32 -0
  134. package/src/core/codemirror/cells/state.ts +1 -0
  135. package/src/core/codemirror/cm.ts +5 -1
  136. package/src/core/codemirror/config/types.ts +1 -1
  137. package/src/core/codemirror/language/languages/python.ts +3 -3
  138. package/src/core/codemirror/placeholder/__tests__/extensions.test.ts +16 -0
  139. package/src/core/codemirror/placeholder/extensions.ts +57 -19
  140. package/src/core/hotkeys/hotkeys.ts +2 -1
  141. package/src/core/notebook/document/__tests__/codemirror.test.ts +1 -0
  142. package/src/plugins/impl/chat/__tests__/chat-prompts.test.tsx +1 -1
  143. package/src/plugins/impl/chat/chat-ui.tsx +1 -1
  144. package/src/components/editor/ai/__tests__/staged-cell-submission.test.ts +0 -106
  145. package/src/components/editor/ai/add-cell-with-ai.tsx +0 -506
  146. package/src/components/editor/ai/staged-cell-submission.ts +0 -46
  147. package/src/components/editor/cell/__tests__/StagedAICell.test.tsx +0 -66
@@ -146,7 +146,9 @@ export async function handleToolCall({
146
146
  addToolOutput({
147
147
  tool: toolCall.toolName,
148
148
  toolCallId: toolCall.toolCallId,
149
- output: response.result || response.error,
149
+ ...(response.error
150
+ ? { state: "output-error", errorText: response.error }
151
+ : { output: response.result }),
150
152
  });
151
153
  } else {
152
154
  // Invoke the backend/mcp tool
@@ -157,7 +159,9 @@ export async function handleToolCall({
157
159
  addToolOutput({
158
160
  tool: toolCall.toolName,
159
161
  toolCallId: toolCall.toolCallId,
160
- output: response.result || response.error,
162
+ ...(response.error
163
+ ? { state: "output-error", errorText: response.error }
164
+ : { output: response.result }),
161
165
  });
162
166
  }
163
167
  } catch (error) {
@@ -165,7 +169,8 @@ export async function handleToolCall({
165
169
  addToolOutput({
166
170
  tool: toolCall.toolName,
167
171
  toolCallId: toolCall.toolCallId,
168
- output: `Error: ${error instanceof Error ? error.message : String(error)}`,
172
+ state: "output-error",
173
+ errorText: error instanceof Error ? error.message : String(error),
169
174
  });
170
175
  }
171
176
  }
@@ -64,7 +64,7 @@ export const NotebookAccessSelector = ({
64
64
  <SelectTrigger
65
65
  aria-label={`Notebook access: ${selected.label}`}
66
66
  title={`Notebook access: ${selected.description}`}
67
- className="h-6 text-xs border-border shadow-none! ring-0! bg-muted hover:bg-muted/30 py-0 px-2 gap-1.5 shrink-0"
67
+ className="chat-control chat-selector h-6 text-xs border-border py-0 px-2 gap-1.5 shrink-0"
68
68
  >
69
69
  <selected.Icon className="h-3 w-3" />
70
70
  <span className="text-nowrap">{selected.label}</span>
@@ -2,7 +2,7 @@
2
2
 
3
3
  import { BotMessageSquareIcon } from "lucide-react";
4
4
  import React from "react";
5
- import { MarkdownRenderer } from "@/components/markdown/markdown-renderer";
5
+ import { ChatMarkdown } from "./chat-markdown";
6
6
  import {
7
7
  Accordion,
8
8
  AccordionContent,
@@ -49,9 +49,9 @@ export const ReasoningAccordion: React.FC<ReasoningAccordionProps> = ({
49
49
  </span>
50
50
  </AccordionTrigger>
51
51
  <AccordionContent className="pb-2 px-2">
52
- <div className="bg-muted/30 border border-muted/50 rounded-md p-3 italic text-muted-foreground/90 relative">
52
+ <div className="bg-muted/30 border border-muted/50 rounded-md p-3 text-muted-foreground relative">
53
53
  <div className="pr-6">
54
- <MarkdownRenderer content={reasoning} />
54
+ <ChatMarkdown content={reasoning} />
55
55
  </div>
56
56
  </div>
57
57
  </AccordionContent>
@@ -0,0 +1,49 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+
3
+ import type { PropsWithChildren } from "react";
4
+ import {
5
+ Accordion,
6
+ AccordionContent,
7
+ AccordionItem,
8
+ AccordionTrigger,
9
+ } from "@/components/ui/accordion";
10
+ import { formatToolName, type ToolState } from "./shared";
11
+
12
+ interface ToolActivity {
13
+ toolName: string;
14
+ state: ToolState;
15
+ }
16
+
17
+ export function ToolActivityGroup({
18
+ tools,
19
+ isActive,
20
+ children,
21
+ }: PropsWithChildren<{ tools: ToolActivity[]; isActive: boolean }>) {
22
+ const pending = tools.some((tool) => tool.state !== "output-available");
23
+ const editing = tools.every(
24
+ (tool) => formatToolName(tool.toolName) === "edit_notebook_tool",
25
+ );
26
+ const count = tools.length;
27
+ const label = pending
28
+ ? isActive
29
+ ? editing
30
+ ? "Editing notebook…"
31
+ : "Working…"
32
+ : "Stopped before completing all steps"
33
+ : editing
34
+ ? `Edited notebook · ${count} ${count === 1 ? "operation" : "operations"}`
35
+ : `Completed ${count} ${count === 1 ? "step" : "steps"}`;
36
+
37
+ return (
38
+ <Accordion type="single" collapsible={true} className="my-1 w-full">
39
+ <AccordionItem value="activity" className="border-0">
40
+ <AccordionTrigger className="h-auto pl-0 pr-2 py-1 gap-2 rounded text-xs font-normal text-muted-foreground hover:bg-(--slate-2) hover:text-foreground hover:no-underline">
41
+ {label}
42
+ </AccordionTrigger>
43
+ <AccordionContent className="pb-2 pl-3 border-l border-border">
44
+ {children}
45
+ </AccordionContent>
46
+ </AccordionItem>
47
+ </Accordion>
48
+ );
49
+ }
@@ -1,12 +1,6 @@
1
1
  /* Copyright 2026 Marimo. All rights reserved. */
2
2
 
3
- import {
4
- BanIcon,
5
- CheckCircleIcon,
6
- CircleSlashIcon,
7
- Loader2,
8
- WrenchIcon,
9
- } from "lucide-react";
3
+ import { BanIcon, CircleSlashIcon, Loader2, WrenchIcon } from "lucide-react";
10
4
  import React from "react";
11
5
  import {
12
6
  Accordion,
@@ -54,7 +48,7 @@ const StatusIcon: React.FC<{ state: HistoryState; interrupted: boolean }> = ({
54
48
  case "approval-responded":
55
49
  return <Loader2 className="h-3 w-3 animate-spin" />;
56
50
  case "output-available":
57
- return <CheckCircleIcon className="h-3 w-3 text-(--grass-11)" />;
51
+ return null;
58
52
  case "output-denied":
59
53
  return <BanIcon className="h-3 w-3 text-muted-foreground" />;
60
54
  default:
@@ -63,22 +57,6 @@ const StatusIcon: React.FC<{ state: HistoryState; interrupted: boolean }> = ({
63
57
  }
64
58
  };
65
59
 
66
- function getTriggerToneClass(state: HistoryState): string {
67
- switch (state) {
68
- case "output-available":
69
- return "text-(--grass-11)/80";
70
- case "output-denied":
71
- return "text-muted-foreground";
72
- case "input-streaming":
73
- case "input-available":
74
- case "approval-responded":
75
- return "";
76
- default:
77
- logNever(state);
78
- return "";
79
- }
80
- }
81
-
82
60
  interface ToolHistoryRowProps {
83
61
  toolName: string;
84
62
  state: HistoryState;
@@ -109,12 +87,7 @@ export const ToolHistoryRow: React.FC<ToolHistoryRowProps> = ({
109
87
  className={cn("w-full", className)}
110
88
  >
111
89
  <AccordionItem value="tool-call" className="border-0">
112
- <AccordionTrigger
113
- className={cn(
114
- "h-6 text-xs border-border shadow-none! ring-0! bg-muted/60 hover:bg-muted py-0 px-2 gap-1 rounded-sm [&[data-state=open]>svg]:rotate-180 hover:no-underline",
115
- interrupted ? "text-muted-foreground" : getTriggerToneClass(state),
116
- )}
117
- >
90
+ <AccordionTrigger className="h-auto pl-0 pr-2 py-1 gap-2 rounded text-xs font-normal text-muted-foreground hover:bg-(--slate-2) hover:text-foreground hover:no-underline">
118
91
  <span className="flex items-center gap-1">
119
92
  <StatusIcon state={state} interrupted={interrupted} />
120
93
  {interrupted ? "Interrupted" : STATUS_LABEL[state]}:
@@ -47,7 +47,7 @@ const PrettySuccessResult: React.FC<{ data: SuccessResult }> = ({ data }) => {
47
47
  Tool Result
48
48
  </h3>
49
49
  <div className="flex items-center gap-2">
50
- <span className="text-xs px-2 py-0.5 bg-(--grass-2) text-(--grass-11) rounded-full font-medium capitalize">
50
+ <span className="text-xs text-muted-foreground capitalize">
51
51
  {status}
52
52
  </span>
53
53
  {auth_required && (
@@ -0,0 +1,19 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+
3
+ import type { ChatStatus } from "ai";
4
+ import { useSetAtom } from "jotai";
5
+ import { useEffect } from "react";
6
+ import { chatGenerationInProgressAtom } from "@/core/ai/state";
7
+
8
+ export function useChatGeneration(status: ChatStatus) {
9
+ const setGenerating = useSetAtom(chatGenerationInProgressAtom);
10
+ const isGenerating = status === "submitted" || status === "streaming";
11
+
12
+ useEffect(() => {
13
+ setGenerating(isGenerating);
14
+ // useChat stops its request when the panel unmounts or becomes hidden.
15
+ return () => setGenerating(false);
16
+ }, [isGenerating, setGenerating]);
17
+
18
+ return isGenerating;
19
+ }
@@ -1,6 +1,6 @@
1
1
  /* Copyright 2026 Marimo. All rights reserved. */
2
2
 
3
- import { afterEach, describe, expect, it, vi } from "vitest";
3
+ import { describe, expect, it } from "vitest";
4
4
  import { PAIR_CONFIG } from "@/__tests__/fixtures/pair-config";
5
5
  import { shellQuote } from "@/utils/shell";
6
6
  import {
@@ -44,18 +44,6 @@ describe("shellQuote", () => {
44
44
  });
45
45
  });
46
46
 
47
- describe("getMarimoCommand", () => {
48
- afterEach(() => vi.unstubAllEnvs());
49
- it("uses the checkout command in development", () => {
50
- vi.stubEnv("DEV", true);
51
- expect(getMarimoCommand()).toBe("uv run marimo");
52
- });
53
- it("uses uvx outside development", () => {
54
- vi.stubEnv("DEV", false);
55
- expect(getMarimoCommand()).toBe("uvx marimo@latest");
56
- });
57
- });
58
-
59
47
  describe("getTerminalCommand", () => {
60
48
  it.each([
61
49
  ["claude", "claude"],
@@ -63,7 +51,7 @@ describe("getTerminalCommand", () => {
63
51
  ["opencode", "opencode --prompt"],
64
52
  ] as const)("launches %s through the public CLI", (agent, cli) => {
65
53
  expect(getTerminalCommand(agent, CONNECTION, true, PAIR_CONFIG)).toBe(
66
- `${cli} "$(uvx marimo@latest pair prompt \\\n --url http://localhost:8000 \\\n --notebook nb-one \\\n --with-token)"`,
54
+ `${cli} "$(marimo pair prompt \\\n --url http://localhost:8000 \\\n --notebook nb-one \\\n --with-token)"`,
67
55
  );
68
56
  });
69
57
  it("uses a stable Session ID for editor execution", () => {
@@ -89,20 +77,15 @@ describe("getTerminalCommand", () => {
89
77
  expect(command).not.toContain("--with-token");
90
78
  });
91
79
  it("uses the CLI without injected configuration", () => {
92
- vi.stubEnv("DEV", true);
93
- try {
94
- const command = getTerminalCommand(
95
- "claude",
96
- CONNECTION_WITHOUT_TARGET,
97
- false,
98
- );
99
- expect(command).toContain("uv run marimo pair prompt");
100
- expect(command).not.toMatch(
101
- /--claude|MARIMO_PAIR_NEXT|--file|--session|--notebook/,
102
- );
103
- } finally {
104
- vi.unstubAllEnvs();
105
- }
80
+ const command = getTerminalCommand(
81
+ "claude",
82
+ CONNECTION_WITHOUT_TARGET,
83
+ false,
84
+ );
85
+ expect(command).toContain(`${getMarimoCommand()} pair prompt`);
86
+ expect(command).not.toMatch(
87
+ /--claude|MARIMO_PAIR_NEXT|--file|--session|--notebook/,
88
+ );
106
89
  });
107
90
  });
108
91
 
@@ -133,21 +116,19 @@ describe("getRawPrompt", () => {
133
116
  "Session: stable-one\n",
134
117
  ],
135
118
  ])("renders the shared CLI prompt for %j", (connection, target) => {
136
- const expected = `Pair with me on this running marimo notebook.\n\nURL: http://localhost:8000\n${target}\nRun \`uvx marimo@latest pair --help\` first.\nUse \`uvx marimo@latest\` for all marimo commands.`;
119
+ const expected = `Pair with me on this running marimo notebook.\n\nURL: http://localhost:8000\n${target}\nRun \`marimo pair --help\` first.\nUse \`marimo\` for all marimo commands.`;
137
120
  expect(getRawPrompt(connection, null, PAIR_CONFIG)).toBe(expected);
138
121
  expect(
139
122
  getRawPrompt(connection, null, { ...PAIR_CONFIG, hostEnabled: false }),
140
123
  ).toBe(expected);
141
124
  });
142
125
  it("matches the shared prompt without injected templates", () => {
143
- vi.stubEnv("DEV", false);
144
- try {
145
- expect(getRawPrompt(CONNECTION, "secret-token")).toBe(
146
- getRawPrompt(CONNECTION, "secret-token", PAIR_CONFIG),
147
- );
148
- } finally {
149
- vi.unstubAllEnvs();
150
- }
126
+ expect(getRawPrompt(CONNECTION, "secret-token")).toBe(
127
+ getRawPrompt(CONNECTION, "secret-token", {
128
+ ...PAIR_CONFIG,
129
+ command: getMarimoCommand(),
130
+ }),
131
+ );
151
132
  });
152
133
  });
153
134
 
@@ -99,14 +99,11 @@ describe("PairWithAgentModal", () => {
99
99
  expect(panel).not.toHaveTextContent("--file");
100
100
  expect(panel).not.toHaveTextContent("File:");
101
101
  expect(panel).not.toHaveTextContent("notebook.py");
102
- expect(
103
- panel.textContent?.includes("Agent connections require OpenCode 2.x"),
104
- ).toBe(tab === "OpenCode");
105
102
  expect(
106
103
  within(panel).getByText(
107
104
  tab === "Other Agents"
108
- ? "Copy this prompt into your agent"
109
- : "Run in your terminal",
105
+ ? "Paste this prompt into your coding agent:"
106
+ : "Run this command in your terminal:",
110
107
  { exact: true },
111
108
  ),
112
109
  ).toBeInTheDocument();
@@ -367,15 +364,15 @@ describe("PairWithAgentModal", () => {
367
364
  });
368
365
 
369
366
  const panel = screen.getByRole("tabpanel");
370
- expect(panel).toHaveTextContent("1. Run in your terminal");
371
- expect(panel).toHaveTextContent("2. Paste when prompted for a token");
367
+ expect(panel).toHaveTextContent("1. Run this command in your terminal:");
368
+ expect(panel).toHaveTextContent("2. When prompted, paste this token:");
372
369
  expect(panel).not.toHaveTextContent("secret-token");
373
370
  fireEvent.click(
374
371
  within(panel).getByRole("button", { name: "Copy command" }),
375
372
  );
376
373
  await waitFor(() =>
377
374
  expect(copyToClipboard).toHaveBeenCalledWith(
378
- String.raw`claude "$(uvx marimo@latest pair prompt \
375
+ String.raw`claude "$(marimo pair prompt \
379
376
  --url http://localhost:8000/ \
380
377
  --notebook ${NOTEBOOK_ID} \
381
378
  --with-token)"`,
@@ -7,7 +7,7 @@ import { afterEach, describe, expect, it, vi } from "vitest";
7
7
  import { ModalProvider } from "@/components/modal/ImperativeModal";
8
8
  import { TooltipProvider } from "@/components/ui/tooltip";
9
9
  import { store } from "@/core/state/jotai";
10
- import { PairWithAgentBanner, PairWithAgentButton } from "../pair-with-agent";
10
+ import { PairWithAgentBanner } from "../pair-with-agent";
11
11
 
12
12
  const mocks = vi.hoisted(() => ({
13
13
  isWasm: vi.fn(() => false),
@@ -75,29 +75,15 @@ describe("PairWithAgent", () => {
75
75
  }),
76
76
  );
77
77
 
78
- expect(screen.getByRole("dialog", { name: "Pair an agent" })).toBeVisible();
79
- });
80
-
81
- it("opens the pairing dialog and preserves a passed click handler", () => {
82
- const onClick = vi.fn();
83
- stubTokenFetch();
84
- render(<PairWithAgentButton onClick={onClick} />, { wrapper });
85
-
86
- fireEvent.click(screen.getByRole("button", { name: "Pair your agent" }));
87
-
88
- expect(onClick).toHaveBeenCalledOnce();
89
- expect(screen.getByRole("dialog", { name: "Pair an agent" })).toBeVisible();
78
+ expect(
79
+ screen.getByRole("dialog", { name: "Pair an agent to this notebook" }),
80
+ ).toBeVisible();
90
81
  });
91
82
 
92
83
  it("hides pairing controls in WASM", () => {
93
84
  mocks.isWasm.mockReturnValue(true);
94
85
 
95
- const { container, rerender } = render(<PairWithAgentButton />, {
96
- wrapper,
97
- });
98
- expect(container).toBeEmptyDOMElement();
99
-
100
- rerender(<PairWithAgentBanner />);
86
+ const { container } = render(<PairWithAgentBanner />, { wrapper });
101
87
  expect(container).toBeEmptyDOMElement();
102
88
  });
103
89
  });
@@ -17,7 +17,6 @@ export const AGENT_LABELS: Record<AgentTab, string> = {
17
17
  prompt: "Other Agents",
18
18
  };
19
19
 
20
- /** How to invoke marimo: from the local checkout in dev, else via uvx. */
21
20
  export function getMarimoCommand(): string {
22
21
  return import.meta.env.DEV ? "uv run marimo" : "uvx marimo@latest";
23
22
  }
@@ -116,11 +116,12 @@ export const PairWithAgentModal: React.FC<{
116
116
  return (
117
117
  <DialogContent className="sm:max-w-2xl">
118
118
  <DialogHeader>
119
- <DialogTitle>Pair an agent</DialogTitle>
119
+ <DialogTitle>Pair an agent to this notebook</DialogTitle>
120
120
  <DialogDescription>
121
- Use an AI coding agent to pair program on this notebook.{" "}
121
+ Choose your coding agent, then follow the instructions below to
122
+ connect it to this notebook using marimo pair.{" "}
122
123
  <a
123
- href="https://links.marimo.app/marimo-pair"
124
+ href="https://links.marimo.app/docs-marimo-pair"
124
125
  target="_blank"
125
126
  rel="noopener noreferrer"
126
127
  className="underline"
@@ -160,12 +161,7 @@ export const PairWithAgentModal: React.FC<{
160
161
  >
161
162
  <Step
162
163
  index={hasToken ? 1 : undefined}
163
- title="Run in your terminal"
164
- hint={
165
- hostEnabled && tab === "opencode"
166
- ? "Agent connections require OpenCode 2.x."
167
- : undefined
168
- }
164
+ title="Run this command in your terminal:"
169
165
  >
170
166
  <CommandBlock
171
167
  command={getTerminalCommand(
@@ -178,7 +174,7 @@ export const PairWithAgentModal: React.FC<{
178
174
  />
179
175
  </Step>
180
176
  {hasToken && authToken && (
181
- <Step index={2} title="Paste when prompted for a token">
177
+ <Step index={2} title="When prompted, paste this token:">
182
178
  <CommandBlock
183
179
  command={authToken}
184
180
  display={maskToken(authToken)}
@@ -191,7 +187,7 @@ export const PairWithAgentModal: React.FC<{
191
187
 
192
188
  <TabsContent value="prompt" className="mt-4 flex flex-col gap-4">
193
189
  <Step
194
- title="Copy this prompt into your agent"
190
+ title="Paste this prompt into your coding agent:"
195
191
  hint={
196
192
  hasToken
197
193
  ? "Includes your auth token — keep it private."
@@ -4,7 +4,7 @@ import { ArrowUpRightIcon, BotIcon } from "lucide-react";
4
4
  import React from "react";
5
5
  import { AiProviderIcon } from "@/components/ai/ai-provider-icon";
6
6
  import { useImperativeModal } from "@/components/modal/ImperativeModal";
7
- import { Button, type ButtonProps } from "@/components/ui/button";
7
+ import { Button } from "@/components/ui/button";
8
8
  import { isWasm } from "@/core/wasm/utils";
9
9
  import { cn } from "@/utils/cn";
10
10
  import { PairWithAgentModal } from "./pair-with-agent-modal";
@@ -31,38 +31,10 @@ export const usePairWithAgentModal = () => {
31
31
  );
32
32
  };
33
33
 
34
- export const PairWithAgentButton: React.FC<
35
- ButtonProps & { label?: string }
36
- > = ({ className, label = "Pair your agent", onClick, ...props }) => {
37
- const openPairWithAgentModal = usePairWithAgentModal();
38
-
39
- if (isWasm()) {
40
- return null;
41
- }
42
-
43
- return (
44
- <Button
45
- type="button"
46
- variant="link"
47
- size="xs"
48
- className={cn("h-auto gap-1 px-0", className)}
49
- onClick={(event) => {
50
- onClick?.(event);
51
- if (!event.defaultPrevented) {
52
- openPairWithAgentModal();
53
- }
54
- }}
55
- {...props}
56
- >
57
- {label}
58
- </Button>
59
- );
60
- };
61
-
62
34
  export const PairWithAgentBanner: React.FC<{
63
35
  label?: string;
64
36
  className?: string;
65
- }> = ({ label = "Pair your agent", className }) => {
37
+ }> = ({ label = "Pair your coding agent", className }) => {
66
38
  const openPairWithAgentModal = usePairWithAgentModal();
67
39
 
68
40
  if (isWasm()) {
@@ -76,7 +48,7 @@ export const PairWithAgentBanner: React.FC<{
76
48
  className,
77
49
  )}
78
50
  >
79
- <div className="flex items-center gap-2 text-sm font-semibold">
51
+ <div className="flex flex-wrap items-center gap-2 text-sm font-semibold">
80
52
  {label}
81
53
  <fieldset
82
54
  className="m-0 flex min-w-0 -space-x-1.5 border-0 p-0 mb-0.5"
@@ -159,7 +159,7 @@ export function useCellActionButtons({ cell, closePopover }: Props) {
159
159
  },
160
160
  {
161
161
  icon: <SparklesIcon size={13} strokeWidth={1.5} />,
162
- label: "Refactor with AI",
162
+ label: "Refactor cell",
163
163
  hidden: !aiFeaturesEnabled,
164
164
  handle: () => {
165
165
  setAiCompletionCell((current) =>
@@ -45,7 +45,7 @@ import { useTheme } from "@/theme/useTheme";
45
45
  import { cn } from "@/utils/cn";
46
46
  import { prettyError } from "@/utils/errors";
47
47
  import { retryWithTimeout } from "@/utils/timeout";
48
- import { PromptInput } from "./add-cell-with-ai";
48
+ import { PromptInput } from "./prompt-input";
49
49
  import {
50
50
  AcceptCompletionButton,
51
51
  AI_EDIT_ACTION_LABELS,