@marimo-team/islands 0.25.3-dev5 → 0.25.3-dev51

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 (220) hide show
  1. package/dist/{ConnectedDataExplorerComponent-CNMliVzw.js → ConnectedDataExplorerComponent-Da5OoEUL.js} +14 -14
  2. package/dist/{ErrorBoundary-BiZWOrv4.js → ErrorBoundary-CbxCC2kk.js} +8 -8
  3. package/dist/{abnfDiagram-VCTEODGH-D-Ez91gV.js → abnfDiagram-VCTEODGH-Bz3mrprX.js} +2 -2
  4. package/dist/any-language-editor-k2B8YI3Y.js +75 -0
  5. package/dist/{architectureDiagram-5GKGNRK7-CTpcNtO8.js → architectureDiagram-5GKGNRK7-Cs5ujb9K.js} +1 -1
  6. package/dist/{arrays-CN1djC4k.js → arrays-R6JsImiT.js} +8 -0
  7. package/dist/{button-CvjkwdSk.js → button-BxB3RDjN.js} +1 -1
  8. package/dist/{capabilities-CQ5IjhzL.js → capabilities-BgR0osfN.js} +2 -2
  9. package/dist/{chat-ui-0lKdOoQ3.js → chat-ui-C1Gjgokv.js} +1103 -1103
  10. package/dist/{check-EDwSwP89.js → check-P35dc2dH.js} +1 -1
  11. package/dist/{chunk-IMKFNOWR-DGxi-zeo.js → chunk-IMKFNOWR-CcTSaxZt.js} +1 -1
  12. package/dist/{chunk-SHT3W25Y-CV9J3rAB.js → chunk-SHT3W25Y-DcJcjOaO.js} +1 -1
  13. package/dist/{chunk-SVP7TREG-BL-9Fqgg.js → chunk-SVP7TREG-BVtDTjaF.js} +1 -1
  14. package/dist/{chunk-TICWLB2K-Cu_XMjDM.js → chunk-TICWLB2K-BkDFkh8q.js} +1 -1
  15. package/dist/{chunk-YOKDWASO-DPZk8vhS.js → chunk-YOKDWASO-DKplTrBY.js} +20 -20
  16. package/dist/{classDiagram-ZZMXUADV-BXXV13Hq.js → classDiagram-ZZMXUADV-CfAt7HT5.js} +2 -2
  17. package/dist/{classDiagram-v2-VYDZK3BY-B7FXJ1Cp.js → classDiagram-v2-VYDZK3BY-CGEjNYa2.js} +2 -2
  18. package/dist/{code-visibility-CW5_8ZOI.js → code-visibility-DoZ9lxzx.js} +6 -6
  19. package/dist/{common-mzWw4FdI.js → common-D1PacyXO.js} +3112 -3015
  20. package/dist/{copy-icon-Ft7t9bqz.js → copy-icon-CPoxUG7G.js} +3 -3
  21. package/dist/{cynefinDiagram-5FMLGOSQ-CH96rwba.js → cynefinDiagram-5FMLGOSQ-BW6NmfJi.js} +1 -1
  22. package/dist/{dagre-GXQ25YYZ-BD3PmpFY.js → dagre-GXQ25YYZ-CWg3CTuF.js} +1 -1
  23. package/dist/{data-grid-overlay-editor-CJmehSdc.js → data-grid-overlay-editor-B50UpkXd.js} +5 -5
  24. package/dist/{data-grid-types-BH_r7g7l.js → data-grid-types-0PJ-9N5n.js} +1 -1
  25. package/dist/{diagram-S7CK7UJ4-BsSJJldB.js → diagram-S7CK7UJ4-BPxtoS-z.js} +1 -1
  26. package/dist/{diagram-UQ7AKVKN-muQtNZc4.js → diagram-UQ7AKVKN-DA-vJdFL.js} +1 -1
  27. package/dist/{diagram-VX7I27RA-CdhuZAqR.js → diagram-VX7I27RA-D6bkR84S.js} +1 -1
  28. package/dist/{diagram-Z3DM3KII-BD8mXjOj.js → diagram-Z3DM3KII-m60g4cAT.js} +1 -1
  29. package/dist/{dialog-CEBFZNyc.js → dialog-C1PcpCqX.js} +3 -3
  30. package/dist/{dist-DpBpA_n1.js → dist-Lm-D-Vr6.js} +3 -3
  31. package/dist/{dropdown-menu-CP_qCYHC.js → dropdown-menu-MG5hiCIa.js} +76 -76
  32. package/dist/{ebnfDiagram-PWID7BFC-BIN1sweE.js → ebnfDiagram-PWID7BFC-CK99sho2.js} +2 -2
  33. package/dist/{erDiagram-RLTQ6QDP-Ctp5cfoN.js → erDiagram-RLTQ6QDP-Bryq0tdE.js} +1 -1
  34. package/dist/{error-banner-D-WCIiMt.js → error-banner-Sl2FlSJ4.js} +2 -2
  35. package/dist/{error-entries-DSvFWT_M.js → error-entries-CMNtA8NK.js} +959 -959
  36. package/dist/{feedback-button-BcT1FBud.js → feedback-button-DqPqwryB.js} +29 -29
  37. package/dist/{flowDiagram-HODETNUW-3EfLrlFb.js → flowDiagram-HODETNUW-D-3aRpfS.js} +2 -2
  38. package/dist/{formats-CeOmU1MU.js → formats-6pLWWrY2.js} +3 -3
  39. package/dist/{ganttDiagram-EL5Y4UJY-BQT16JSc.js → ganttDiagram-EL5Y4UJY-Dh_4cBJZ.js} +2 -2
  40. package/dist/{glide-data-editor-XckatD0j.js → glide-data-editor-BxMhUd3K.js} +370 -368
  41. package/dist/{glide-utils-DJyqh0EM.js → glide-utils-Bt8_pBa2.js} +16 -16
  42. package/dist/{globals-jRrLCkrP.js → globals-DTJa_doJ.js} +3 -3
  43. package/dist/{highlighted-body-KPVGNVTW-zwjq21tf.js → highlighted-body-KPVGNVTW-3Ziwfz-V.js} +2 -2
  44. package/dist/{html-to-image-B8s5SgGm.js → html-to-image-By72ALyI.js} +29 -29
  45. package/dist/{infoDiagram-27XIBGKW-DipQoHxl.js → infoDiagram-27XIBGKW-s1C1kgfU.js} +1 -1
  46. package/dist/{ishikawaDiagram-5VMMS53U-DgFMjSGz.js → ishikawaDiagram-5VMMS53U-CCsRgolY.js} +1 -1
  47. package/dist/{kanban-definition-UXKFOSKX-w1ACVjgl.js → kanban-definition-UXKFOSKX-BrmxDlcc.js} +1 -1
  48. package/dist/{label-BDKF3Afc.js → label-BRnUC0NX.js} +2 -2
  49. package/dist/{linear-Cnaq3Tfb.js → linear-DWERh7eI.js} +121 -73
  50. package/dist/{loader-BQOfjTIJ.js → loader-Bm8cy3e8.js} +12 -12
  51. package/dist/main.js +2358 -2221
  52. package/dist/{mermaid-CHo49Yv-.js → mermaid-DKI0869I.js} +38 -38
  53. package/dist/mermaid-HWGCJPDP-Du3-HPQA.js +2 -0
  54. package/dist/{mindmap-definition-YA3MSWOX-CrT0KSfX.js → mindmap-definition-YA3MSWOX-641xJysF.js} +1 -1
  55. package/dist/{number-overlay-editor-pZx8dUDV.js → number-overlay-editor-Z_H6x5b-.js} +3 -3
  56. package/dist/{objects-CzUgoMIF.js → objects-DCmMvZtn.js} +1 -3
  57. package/dist/{once-BtE-wKRL.js → once-eG8Vmvjd.js} +1 -1
  58. package/dist/{pegDiagram-XKGWAZYB-BqvywNJA.js → pegDiagram-XKGWAZYB-keVUAUeS.js} +2 -2
  59. package/dist/{pieDiagram-E7YTZNPT-BfxJ_XCc.js → pieDiagram-E7YTZNPT-DHktXPO_.js} +1 -1
  60. package/dist/precisionRound-DhAzeRcm.js +49 -0
  61. package/dist/{process-output-CKRXyszb.js → process-output-D-OMW5sV.js} +4 -4
  62. package/dist/{quadrantDiagram-AXDQQJYC-Ccw0xMjN.js → quadrantDiagram-AXDQQJYC-Blrm7V7_.js} +1 -1
  63. package/dist/{railroadDiagram-O6MQD6OU-8vFLVTPu.js → railroadDiagram-O6MQD6OU-BVEF4OV_.js} +2 -2
  64. package/dist/{react-vega-BED0DjiB.js → react-vega-C4T9Rb_w.js} +12284 -12270
  65. package/dist/{requirementDiagram-BXWQKSXE-a_MxGvxe.js → requirementDiagram-BXWQKSXE-CWo-UAKV.js} +1 -1
  66. package/dist/{reveal-component-79ez8-D5.js → reveal-component-tAB5K3QE.js} +10 -10
  67. package/dist/{select-BButFi-d.js → select-B0LRDH0g.js} +4 -4
  68. package/dist/{sequenceDiagram-WJ2MYXX4-CXFMnxGh.js → sequenceDiagram-WJ2MYXX4-CHQvLnq4.js} +1 -1
  69. package/dist/{spec-BwarBpUJ.js → spec-C-LRKGtT.js} +4 -4
  70. package/dist/{stateDiagram-D77RDMKH-DQeA_CwI.js → stateDiagram-D77RDMKH-BOJZgbUJ.js} +2 -2
  71. package/dist/{stateDiagram-v2-MP3YSRHH-BxxQucm1.js → stateDiagram-v2-MP3YSRHH-C53XXdYH.js} +2 -2
  72. package/dist/style.css +1 -1
  73. package/dist/{swimlanes-42K2YHIH-Do6L-cYg.js → swimlanes-42K2YHIH-D2o1gOtw.js} +1 -1
  74. package/dist/{swimlanesDiagram-VR7AAH4N-DUzPvSVU.js → swimlanesDiagram-VR7AAH4N-BHFzN1Wk.js} +2 -2
  75. package/dist/{swiper-component-DKntyn8p.js → swiper-component-DfhRvAFi.js} +2 -2
  76. package/dist/{time-BieKwdYS.js → time-BaMbJs4z.js} +18 -18
  77. package/dist/{timeline-definition-24CTP7MA-D3c46WM5.js → timeline-definition-24CTP7MA-rv7QUa46.js} +1 -1
  78. package/dist/{toDate-vIXljnsr.js → toDate-CjPuEPou.js} +21 -20
  79. package/dist/{toast-B9UMEi8u.js → toast-CFWpihJU.js} +4 -4
  80. package/dist/{tooltip-BO1REbqs.js → tooltip-CKO1pHJo.js} +2 -2
  81. package/dist/{use-restore-focus-BEM7sFWl.js → use-restore-focus-DBCITLeF.js} +1 -1
  82. package/dist/{useAsyncData-Deh5kszT.js → useAsyncData-ChgNtdmE.js} +15 -14
  83. package/dist/{useDateFormatter-D8XruNGG.js → useDateFormatter-c_yJtkAe.js} +2 -2
  84. package/dist/{useDeepCompareMemoize-DXwzRINI.js → useDeepCompareMemoize-Bet7kSsI.js} +1 -1
  85. package/dist/{useIframeCapabilities-DBETvvPl.js → useIframeCapabilities-BkdeVdXC.js} +1 -1
  86. package/dist/{useLifecycle-j7D9uD1o.js → useLifecycle-VVmz_24o.js} +3 -3
  87. package/dist/{useTheme-CRnjkku0.js → useTheme-DympCsQg.js} +5 -5
  88. package/dist/{vega-component-DTG6zxaY.js → vega-component-DnfQeoyt.js} +147 -147
  89. package/dist/{vega-embed-container-BNHE18G9.js → vega-embed-container--LYZqguO.js} +2 -2
  90. package/dist/{vega-loader.browser-DNST6uVf.js → vega-loader.browser-B9Pz0EGP.js} +298 -507
  91. package/dist/{vennDiagram-4TSXK5OY-BSZD8JcZ.js → vennDiagram-4TSXK5OY-D36Zk7t-.js} +1 -1
  92. package/dist/{wardleyDiagram-VM6X3IG4-48cUWkfA.js → wardleyDiagram-VM6X3IG4-BLBolvyQ.js} +1 -1
  93. package/dist/{xychartDiagram-S5SC5T6Z-CswYgYeu.js → xychartDiagram-S5SC5T6Z-CfH57l0v.js} +2 -2
  94. package/package.json +4 -4
  95. package/src/__tests__/fixtures/{pair-preview.ts → pair-config.ts} +3 -2
  96. package/src/__tests__/host-imports.test.ts +1 -0
  97. package/src/__tests__/mount.test.ts +8 -8
  98. package/src/components/app-config/ai-config.tsx +5 -1
  99. package/src/components/chat/__tests__/message-queue.test.tsx +17 -0
  100. package/src/components/chat/chat-panel.tsx +3 -7
  101. package/src/components/chat/chat-utils.ts +4 -8
  102. package/src/components/data-table/__tests__/context-menu-interaction.test.tsx +130 -0
  103. package/src/components/data-table/__tests__/date-popover.test.tsx +72 -0
  104. package/src/components/data-table/__tests__/export-actions.test.tsx +417 -6
  105. package/src/components/data-table/column-visibility-dropdown.tsx +8 -13
  106. package/src/components/data-table/columns.tsx +2 -1
  107. package/src/components/data-table/context-menu.tsx +36 -20
  108. package/src/components/data-table/date-popover.tsx +6 -4
  109. package/src/components/data-table/export-actions.tsx +263 -161
  110. package/src/components/data-table/row-viewer-panel/row-viewer.tsx +2 -2
  111. package/src/components/data-table/schemas.ts +14 -36
  112. package/src/components/editor/Output.tsx +5 -1
  113. package/src/components/editor/__tests__/dynamic-favicon.test.tsx +111 -18
  114. package/src/components/editor/actions/__tests__/pair-with-agent-commands.test.ts +80 -252
  115. package/src/components/editor/actions/__tests__/pair-with-agent-modal.test.tsx +186 -34
  116. package/src/components/editor/actions/pair-with-agent-commands.ts +42 -51
  117. package/src/components/editor/actions/pair-with-agent-modal.tsx +96 -36
  118. package/src/components/editor/ai/add-cell-with-ai.tsx +3 -3
  119. package/src/components/editor/cell/CreateCellButton.tsx +8 -10
  120. package/src/components/editor/cell/code/__tests__/language-toggle.test.tsx +54 -0
  121. package/src/components/editor/cell/code/cell-editor.tsx +1 -1
  122. package/src/components/editor/cell/code/language-toggle.tsx +8 -7
  123. package/src/components/editor/chrome/wrapper/__tests__/attachment-status.test.tsx +277 -78
  124. package/src/components/editor/chrome/wrapper/footer-items/attachment-status.tsx +196 -41
  125. package/src/components/editor/connections/components.tsx +1 -1
  126. package/src/components/editor/controls/__tests__/command-palette.test.tsx +53 -0
  127. package/src/components/editor/controls/command-palette.tsx +22 -10
  128. package/src/components/editor/dynamic-favicon.tsx +21 -40
  129. package/src/components/editor/errors/__tests__/send-error-report-button.test.tsx +139 -36
  130. package/src/components/editor/errors/send-error-report-button.tsx +10 -7
  131. package/src/components/editor/file-tree/file-viewer.tsx +4 -2
  132. package/src/components/editor/output/MarimoTracebackOutput.tsx +28 -22
  133. package/src/components/editor/output/console/ConsoleOutput.tsx +8 -18
  134. package/src/components/pages/home-page.tsx +4 -4
  135. package/src/components/slides/slide-cell-view.tsx +1 -1
  136. package/src/components/tracing/tracing.tsx +2 -2
  137. package/src/core/attachments/__tests__/toast.test.tsx +58 -0
  138. package/src/core/attachments/state.ts +5 -8
  139. package/src/core/attachments/toast.ts +15 -0
  140. package/src/core/cells/__tests__/error-report.test.ts +53 -3
  141. package/src/core/cells/__tests__/notebook-outline.test.ts +113 -0
  142. package/src/core/cells/__tests__/session.test.ts +18 -3
  143. package/src/core/cells/cells.ts +2 -1
  144. package/src/core/cells/error-report.ts +14 -6
  145. package/src/core/cells/pending-delete-service.ts +2 -6
  146. package/src/core/cells/session.ts +19 -1
  147. package/src/core/config/pair.ts +5 -3
  148. package/src/core/datasets/__tests__/data-source-discovery.test.ts +4 -4
  149. package/src/core/datasets/data-source-discovery.ts +4 -6
  150. package/src/core/edit-app.tsx +2 -2
  151. package/src/core/host/AGENTS.md +11 -2
  152. package/src/core/host/__tests__/attachments.test.ts +56 -0
  153. package/src/core/host/__tests__/document.test.ts +126 -0
  154. package/src/core/host/__tests__/events.test.ts +49 -0
  155. package/src/core/host/__tests__/fixtures.ts +41 -0
  156. package/src/core/host/__tests__/notebook-client.test.ts +496 -0
  157. package/src/core/host/connection.ts +81 -0
  158. package/src/core/host/events.ts +68 -0
  159. package/src/core/host/notebook-client.ts +316 -0
  160. package/src/core/host/resources/attachments.ts +19 -0
  161. package/src/core/host/resources/document.ts +101 -0
  162. package/src/core/hotkeys/actions.ts +2 -4
  163. package/src/core/islands/__tests__/bootstrap.test.ts +0 -26
  164. package/src/core/islands/bootstrap.ts +0 -1
  165. package/src/core/network/connection.ts +4 -2
  166. package/src/core/run-app.tsx +2 -2
  167. package/src/core/runtime/__tests__/runtime.test.ts +5 -12
  168. package/src/core/runtime/runtime.ts +5 -5
  169. package/src/core/static/files.ts +2 -0
  170. package/src/core/websocket/__tests__/{useMarimoKernelConnection.test.ts → connection-state.test.ts} +15 -17
  171. package/src/core/websocket/__tests__/{useWebSocket.test.ts → createConnectionTransport.test.ts} +1 -1
  172. package/src/core/websocket/__tests__/useNotebookClient.hook.test.tsx +149 -0
  173. package/src/core/websocket/__tests__/{useMarimoKernelConnection.hook.test.tsx → useNotebookSession.hook.test.tsx} +168 -253
  174. package/src/core/websocket/connection-state.ts +70 -0
  175. package/src/core/websocket/state.ts +7 -0
  176. package/src/core/websocket/transports/__tests__/sse.test.ts +18 -4
  177. package/src/core/websocket/transports/create.ts +31 -0
  178. package/src/core/websocket/transports/sse.ts +14 -2
  179. package/src/core/websocket/transports/transport.ts +12 -23
  180. package/src/core/websocket/types.ts +9 -39
  181. package/src/core/websocket/useNotebookClient.ts +78 -0
  182. package/src/core/websocket/{useMarimoKernelConnection.tsx → useNotebookSession.tsx} +53 -233
  183. package/src/hooks/__tests__/useAsyncData.test.tsx +148 -0
  184. package/src/hooks/useAsyncData.ts +3 -3
  185. package/src/hooks/useDataSourceDiscovery.ts +6 -4
  186. package/src/hooks/useElapsedTime.ts +3 -3
  187. package/src/hooks/useInputHistory.ts +4 -7
  188. package/src/hooks/useResizeObserver.ts +4 -7
  189. package/src/mount.tsx +3 -3
  190. package/src/plugins/core/registerReactComponent.tsx +1 -1
  191. package/src/plugins/impl/DropdownPlugin.tsx +2 -6
  192. package/src/plugins/impl/FormPlugin.tsx +1 -1
  193. package/src/plugins/impl/__tests__/DataTablePlugin.test.tsx +84 -0
  194. package/src/plugins/impl/anywidget/__tests__/registry.test.ts +2 -0
  195. package/src/plugins/impl/anywidget/__tests__/widget-binding.test.ts +241 -2
  196. package/src/plugins/impl/anywidget/runtime.ts +21 -9
  197. package/src/plugins/impl/anywidget/widget-binding.ts +160 -82
  198. package/src/plugins/impl/code/any-language-editor.tsx +2 -2
  199. package/src/plugins/impl/data-editor/glide-data-editor.tsx +8 -5
  200. package/src/plugins/impl/data-editor/glide-utils.ts +3 -1
  201. package/src/plugins/impl/mpl-interactive/MplInteractivePlugin.tsx +5 -3
  202. package/src/plugins/impl/panel/PanelPlugin.tsx +15 -16
  203. package/src/plugins/impl/panel/__tests__/utils.test.ts +39 -1
  204. package/src/plugins/impl/panel/utils.ts +17 -0
  205. package/src/plugins/impl/vega/make-selectable.ts +3 -6
  206. package/src/stories/__tests__/editor-story.test.tsx +33 -0
  207. package/src/stories/editor.stories.tsx +24 -17
  208. package/src/utils/__tests__/arrays.test.ts +35 -0
  209. package/src/utils/__tests__/numbers.test.ts +48 -1
  210. package/src/utils/__tests__/objects.test.ts +21 -0
  211. package/src/utils/arrays.ts +10 -0
  212. package/src/utils/numbers.ts +28 -17
  213. package/src/utils/objects.ts +3 -7
  214. package/src/utils/once.ts +19 -19
  215. package/dist/any-language-editor-DZ_YTVus.js +0 -76
  216. package/dist/mermaid-HWGCJPDP-ByCsZnYF.js +0 -2
  217. package/dist/precisionRound-DMIVQrt2.js +0 -97
  218. package/src/core/host/__tests__/protocol.test.ts +0 -214
  219. package/src/core/host/protocol.ts +0 -1943
  220. package/src/core/websocket/useWebSocket.tsx +0 -88
@@ -11,10 +11,10 @@ import {
11
11
  import { Provider } from "jotai";
12
12
  import type React from "react";
13
13
  import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
14
- import { PAIR_PREVIEW } from "@/__tests__/fixtures/pair-preview";
14
+ import { PAIR_CONFIG } from "@/__tests__/fixtures/pair-config";
15
15
  import { Dialog } from "@/components/ui/dialog";
16
16
  import { TooltipProvider } from "@/components/ui/tooltip";
17
- import { pairPreviewAtom } from "@/core/config/pair";
17
+ import { pairConfigAtom } from "@/core/config/pair";
18
18
  import {
19
19
  type StableSessionId,
20
20
  stableSessionIdAtom,
@@ -26,6 +26,7 @@ import {
26
26
  } from "@/core/runtime/config";
27
27
  import { store } from "@/core/state/jotai";
28
28
  import { PairWithAgentModal } from "../pair-with-agent-modal";
29
+ import { Deferred } from "@/utils/Deferred";
29
30
  import { copyToClipboard } from "@/utils/copy";
30
31
 
31
32
  vi.mock("@/utils/copy", () => ({
@@ -43,11 +44,24 @@ function wrapper({ children }: { children: React.ReactNode }) {
43
44
  }
44
45
 
45
46
  const STABLE_ID = "sess-stable-1" as StableSessionId;
47
+ const NOTEBOOK_ID = "nb-one";
48
+ function responseFor(url: string | URL | Request, token: string | null = null) {
49
+ return Promise.resolve(
50
+ new Response(
51
+ JSON.stringify(
52
+ String(url).endsWith("/api/sessions")
53
+ ? { one: { session_id: STABLE_ID, notebook_id: NOTEBOOK_ID } }
54
+ : { token },
55
+ ),
56
+ { headers: { "Content-Type": "application/json" } },
57
+ ),
58
+ );
59
+ }
46
60
 
47
61
  describe("PairWithAgentModal", () => {
48
62
  beforeEach(() => {
49
63
  vi.clearAllMocks();
50
- store.set(pairPreviewAtom, PAIR_PREVIEW);
64
+ store.set(pairConfigAtom, PAIR_CONFIG);
51
65
  store.set(stableSessionIdAtom, STABLE_ID);
52
66
  store.set(filenameAtom, "/project/my notebook.py");
53
67
  store.set(runtimeConfigAtom, {
@@ -57,19 +71,19 @@ describe("PairWithAgentModal", () => {
57
71
  window.history.replaceState({}, "", "/?file=notebook.py");
58
72
  vi.stubGlobal(
59
73
  "fetch",
60
- vi.fn().mockResolvedValue(new Response(JSON.stringify({ token: null }))),
74
+ vi.fn().mockImplementation((url) => responseFor(url)),
61
75
  );
62
76
  });
63
77
 
64
78
  afterEach(() => {
65
- store.set(pairPreviewAtom, undefined);
79
+ store.set(pairConfigAtom, undefined);
66
80
  store.set(stableSessionIdAtom, null);
67
81
  store.set(filenameAtom, null);
68
82
  vi.unstubAllGlobals();
69
83
  });
70
84
 
71
85
  it.each(["Claude", "Codex", "OpenCode", "Prompt"])(
72
- "omits skill installation and targets the stable session in %s preview",
86
+ "targets the verified host Notebook in %s",
73
87
  async (tab) => {
74
88
  await act(async () => {
75
89
  render(<PairWithAgentModal onClose={vi.fn()} />, { wrapper });
@@ -81,10 +95,13 @@ describe("PairWithAgentModal", () => {
81
95
 
82
96
  const panel = screen.getByRole("tabpanel");
83
97
  expect(within(panel).queryAllByText(/skill/i)).toEqual([]);
84
- expect(panel).toHaveTextContent(STABLE_ID);
98
+ expect(panel).toHaveTextContent(NOTEBOOK_ID);
85
99
  expect(panel).not.toHaveTextContent("--file");
86
100
  expect(panel).not.toHaveTextContent("File:");
87
101
  expect(panel).not.toHaveTextContent("notebook.py");
102
+ expect(
103
+ panel.textContent?.includes("Agent connections require OpenCode 2.x"),
104
+ ).toBe(tab === "OpenCode");
88
105
  expect(panel).toHaveTextContent(
89
106
  tab === "Prompt" ? "1. Copy this prompt" : "1. Run in your terminal",
90
107
  );
@@ -95,13 +112,15 @@ describe("PairWithAgentModal", () => {
95
112
  );
96
113
  await waitFor(() => expect(copyToClipboard).toHaveBeenCalledOnce());
97
114
  const copied = vi.mocked(copyToClipboard).mock.calls[0][0];
98
- expect(copied).toContain(STABLE_ID);
115
+ expect(copied).toContain(NOTEBOOK_ID);
99
116
  expect(copied).not.toContain("notebook.py");
117
+ expect(copied).not.toContain(STABLE_ID);
118
+ expect(panel).not.toHaveTextContent(STABLE_ID);
100
119
  },
101
120
  );
102
121
 
103
122
  it.each(["Claude", "Prompt"])(
104
- "omits the session in %s preview before kernel-ready arrives",
123
+ "waits for kernel-ready before copying the %s target",
105
124
  async (tab) => {
106
125
  store.set(stableSessionIdAtom, null);
107
126
  await act(async () => {
@@ -113,17 +132,21 @@ describe("PairWithAgentModal", () => {
113
132
  });
114
133
 
115
134
  const panel = screen.getByRole("tabpanel");
116
- expect(panel).not.toHaveTextContent("--session");
117
- expect(panel).not.toHaveTextContent("Session:");
135
+ expect(panel).not.toHaveTextContent("--notebook");
136
+ expect(panel).not.toHaveTextContent("Notebook:");
118
137
  expect(panel).toHaveTextContent("http://localhost:8000");
138
+ expect(
139
+ within(panel).getByRole("button", {
140
+ name: tab === "Prompt" ? "Copy prompt" : "Copy command",
141
+ }),
142
+ ).toBeDisabled();
119
143
  },
120
144
  );
121
145
 
122
146
  it.each(["Claude", "Codex", "OpenCode", "Prompt"])(
123
- "preserves the %s skill flow without preview configuration",
147
+ "uses the editor CLI in %s when host is disabled",
124
148
  async (tab) => {
125
- store.set(pairPreviewAtom, undefined);
126
- window.history.replaceState({}, "", "/");
149
+ store.set(pairConfigAtom, { ...PAIR_CONFIG, hostEnabled: false });
127
150
  await act(async () => {
128
151
  render(<PairWithAgentModal onClose={vi.fn()} />, { wrapper });
129
152
  });
@@ -131,27 +154,33 @@ describe("PairWithAgentModal", () => {
131
154
  button: 0,
132
155
  ctrlKey: false,
133
156
  });
134
-
135
157
  const panel = screen.getByRole("tabpanel");
136
- expect(
137
- within(panel).getByText("npx skills add marimo-team/marimo-pair"),
138
- ).toBeVisible();
139
- expect(panel).not.toHaveTextContent(STABLE_ID);
140
- expect(panel).not.toHaveTextContent("--file");
158
+ expect(panel).toHaveTextContent(STABLE_ID);
159
+ expect(panel).not.toHaveTextContent(NOTEBOOK_ID);
160
+ expect(panel).not.toHaveTextContent("skill");
161
+ expect(panel).not.toHaveTextContent("Agent connections require");
141
162
  expect(panel).not.toHaveTextContent("MARIMO_PAIR_NEXT");
142
- expect(panel).not.toHaveTextContent("/project/my notebook.py");
143
- expect(panel).toHaveTextContent(
144
- tab === "Prompt" ? "2. Copy this prompt" : "2. Run in your terminal",
163
+ fireEvent.click(
164
+ within(panel).getByRole("button", {
165
+ name: tab === "Prompt" ? "Copy prompt" : "Copy command",
166
+ }),
145
167
  );
168
+ await waitFor(() => expect(copyToClipboard).toHaveBeenCalledOnce());
169
+ expect(vi.mocked(copyToClipboard).mock.calls[0][0]).toContain(STABLE_ID);
170
+ expect(
171
+ vi
172
+ .mocked(fetch)
173
+ .mock.calls.some(([url]) => String(url).endsWith("/api/sessions")),
174
+ ).toBe(false);
146
175
  },
147
176
  );
148
177
 
149
178
  it.each([true, false])(
150
- "masks the raw token but copies it intact (preview=%s)",
151
- async (preview) => {
152
- store.set(pairPreviewAtom, preview ? PAIR_PREVIEW : undefined);
153
- vi.mocked(fetch).mockResolvedValue(
154
- new Response(JSON.stringify({ token: "secret-token" })),
179
+ "masks the raw token but copies it intact (host mode=%s)",
180
+ async (hostEnabled) => {
181
+ store.set(pairConfigAtom, { ...PAIR_CONFIG, hostEnabled });
182
+ vi.mocked(fetch).mockImplementation((url) =>
183
+ responseFor(url, "secret-token"),
155
184
  );
156
185
  await act(async () => {
157
186
  render(<PairWithAgentModal onClose={vi.fn()} />, { wrapper });
@@ -169,7 +198,7 @@ describe("PairWithAgentModal", () => {
169
198
  );
170
199
  await waitFor(() => expect(copyToClipboard).toHaveBeenCalledOnce());
171
200
  expect(vi.mocked(copyToClipboard).mock.calls[0][0]).toContain(
172
- preview ? "export MARIMO_TOKEN=secret-token" : "--token secret-token",
201
+ "export MARIMO_TOKEN=secret-token",
173
202
  );
174
203
  expect(vi.mocked(copyToClipboard).mock.calls[0][0]).not.toContain(
175
204
  "********oken",
@@ -177,9 +206,132 @@ describe("PairWithAgentModal", () => {
177
206
  },
178
207
  );
179
208
 
180
- it("copies a preview terminal command without the token and lets the user copy the token separately", async () => {
181
- vi.mocked(fetch).mockResolvedValue(
182
- new Response(JSON.stringify({ token: "secret-token" })),
209
+ it("uses the CLI when an older server omits Pair configuration", async () => {
210
+ store.set(pairConfigAtom, undefined);
211
+ await act(async () => {
212
+ render(<PairWithAgentModal onClose={vi.fn()} />, { wrapper });
213
+ });
214
+ const panel = screen.getByRole("tabpanel");
215
+ expect(panel).toHaveTextContent(`--session ${STABLE_ID}`);
216
+ expect(panel).not.toHaveTextContent("skill");
217
+ expect(screen.getByRole("button", { name: "Copy command" })).toBeEnabled();
218
+ });
219
+
220
+ it("ignores a previous Session's late Notebook lookup", async () => {
221
+ const previous = new Deferred<Response>();
222
+ const nextId = "sess-next" as StableSessionId;
223
+ let lookups = 0;
224
+ vi.mocked(fetch).mockImplementation((url) => {
225
+ if (!String(url).endsWith("/api/sessions")) {
226
+ return responseFor(url);
227
+ }
228
+ if (lookups++ === 0) {
229
+ return previous.promise;
230
+ }
231
+ return Promise.resolve(
232
+ new Response(
233
+ JSON.stringify({
234
+ next: { session_id: nextId, notebook_id: "nb-next" },
235
+ }),
236
+ { headers: { "Content-Type": "application/json" } },
237
+ ),
238
+ );
239
+ });
240
+ await act(async () => {
241
+ render(<PairWithAgentModal onClose={vi.fn()} />, { wrapper });
242
+ });
243
+ await act(async () => store.set(stableSessionIdAtom, nextId));
244
+ expect(screen.getByRole("tabpanel")).toHaveTextContent("nb-next");
245
+ await act(async () =>
246
+ previous.resolve(
247
+ new Response(
248
+ JSON.stringify({
249
+ previous: { session_id: STABLE_ID, notebook_id: NOTEBOOK_ID },
250
+ }),
251
+ { headers: { "Content-Type": "application/json" } },
252
+ ),
253
+ ),
254
+ );
255
+ expect(screen.getByRole("tabpanel")).toHaveTextContent("nb-next");
256
+ expect(screen.getByRole("tabpanel")).not.toHaveTextContent(NOTEBOOK_ID);
257
+ });
258
+
259
+ it("disables target copying while a new Session's Notebook is unverified", async () => {
260
+ await act(async () => {
261
+ render(<PairWithAgentModal onClose={vi.fn()} />, { wrapper });
262
+ });
263
+ expect(screen.getByRole("tabpanel")).toHaveTextContent(NOTEBOOK_ID);
264
+ const pending = new Deferred<Response>();
265
+ vi.mocked(fetch).mockImplementation((url) =>
266
+ String(url).endsWith("/api/sessions")
267
+ ? pending.promise
268
+ : responseFor(url),
269
+ );
270
+ const nextId = "sess-stable-2" as StableSessionId;
271
+ act(() => store.set(stableSessionIdAtom, nextId));
272
+ expect(screen.getByRole("button", { name: "Copy command" })).toBeDisabled();
273
+ expect(screen.getByRole("tabpanel")).not.toHaveTextContent(NOTEBOOK_ID);
274
+ fireEvent.click(screen.getByRole("button", { name: "Copy command" }));
275
+ expect(copyToClipboard).not.toHaveBeenCalled();
276
+ await act(async () =>
277
+ pending.resolve(
278
+ new Response(
279
+ JSON.stringify({
280
+ next: { session_id: nextId, notebook_id: "nb-two" },
281
+ }),
282
+ { headers: { "Content-Type": "application/json" } },
283
+ ),
284
+ ),
285
+ );
286
+ expect(screen.getByRole("button", { name: "Copy command" })).toBeEnabled();
287
+ fireEvent.click(screen.getByRole("button", { name: "Copy command" }));
288
+ await waitFor(() => expect(copyToClipboard).toHaveBeenCalledOnce());
289
+ expect(vi.mocked(copyToClipboard).mock.calls[0][0]).toContain("nb-two");
290
+ expect(vi.mocked(copyToClipboard).mock.calls[0][0]).not.toContain(nextId);
291
+ });
292
+
293
+ it("uses the Session after verifying that a notebook is unsaved", async () => {
294
+ vi.mocked(fetch).mockImplementation((url) =>
295
+ String(url).endsWith("/api/sessions")
296
+ ? Promise.resolve(
297
+ new Response(
298
+ JSON.stringify({
299
+ one: { session_id: STABLE_ID, notebook_id: null },
300
+ }),
301
+ { headers: { "Content-Type": "application/json" } },
302
+ ),
303
+ )
304
+ : responseFor(url),
305
+ );
306
+ await act(async () => {
307
+ render(<PairWithAgentModal onClose={vi.fn()} />, { wrapper });
308
+ });
309
+ expect(screen.queryByRole("status")).not.toBeInTheDocument();
310
+ expect(screen.getByRole("button", { name: "Copy command" })).toBeEnabled();
311
+ expect(screen.getByRole("tabpanel")).toHaveTextContent(
312
+ `--session ${STABLE_ID}`,
313
+ );
314
+ expect(screen.getByRole("tabpanel")).not.toHaveTextContent("--notebook");
315
+ });
316
+
317
+ it("keeps copying disabled when Notebook verification fails", async () => {
318
+ vi.mocked(fetch).mockImplementation((url) =>
319
+ String(url).endsWith("/api/sessions")
320
+ ? Promise.reject(new Error("offline"))
321
+ : responseFor(url),
322
+ );
323
+ await act(async () => {
324
+ render(<PairWithAgentModal onClose={vi.fn()} />, { wrapper });
325
+ });
326
+ expect(screen.getByRole("status")).toHaveTextContent(
327
+ "Could not verify this notebook",
328
+ );
329
+ expect(screen.getByRole("button", { name: "Copy command" })).toBeDisabled();
330
+ });
331
+
332
+ it("copies a host mode terminal command without the token and lets the user copy the token separately", async () => {
333
+ vi.mocked(fetch).mockImplementation((url) =>
334
+ responseFor(url, "secret-token"),
183
335
  );
184
336
  await act(async () => {
185
337
  render(<PairWithAgentModal onClose={vi.fn()} />, { wrapper });
@@ -193,9 +345,9 @@ describe("PairWithAgentModal", () => {
193
345
  );
194
346
  await waitFor(() =>
195
347
  expect(copyToClipboard).toHaveBeenCalledWith(
196
- String.raw`claude "$(MARIMO_PAIR_NEXT=1 uvx marimo@latest pair prompt \
348
+ String.raw`claude "$(uvx marimo@latest pair prompt \
197
349
  --url http://localhost:8000/ \
198
- --session ${STABLE_ID} \
350
+ --notebook ${NOTEBOOK_ID} \
199
351
  --with-token)"`,
200
352
  ),
201
353
  );
@@ -1,9 +1,8 @@
1
1
  /* Copyright 2026 Marimo. All rights reserved. */
2
2
 
3
3
  import { assertNever } from "@/utils/assertNever";
4
- import { KnownQueryParams } from "@/core/constants";
5
4
  import { shellQuote } from "@/utils/shell";
6
- import type { PairPreviewConfig } from "@/core/config/pair";
5
+ import type { PairConfig } from "@/core/config/pair";
7
6
 
8
7
  export type AgentTab = "claude" | "codex" | "opencode" | "prompt";
9
8
 
@@ -18,30 +17,18 @@ export const AGENT_LABELS: Record<AgentTab, string> = {
18
17
  prompt: "Prompt",
19
18
  };
20
19
 
21
- export const SKILL_INSTALL = "npx skills add marimo-team/marimo-pair";
22
-
23
20
  /** How to invoke marimo: from the local checkout in dev, else via uvx. */
24
21
  export function getMarimoCommand(): string {
25
22
  return import.meta.env.DEV ? "uv run marimo" : "uvx marimo@latest";
26
23
  }
27
24
 
28
- /** Return the server file key from a page URL, preserving its decoded value. */
29
- export function getFileFromURL(href: string): string | undefined {
30
- const file = new URL(href).searchParams.get(KnownQueryParams.filePath);
31
- return file === null || file === "" ? undefined : file;
32
- }
33
-
34
- function getFileFlag(file: string | undefined): string {
35
- return file ? ` --file ${shellQuote(file)}` : "";
36
- }
37
-
38
25
  /** Identifies the specific running notebook to pair on. */
39
26
  export interface ConnectionInfo {
40
27
  url: string;
41
- /** The server's file key, when known. */
42
- file?: string;
43
- /** The stable session ID, when known. */
28
+ /** Stable Session ID for ordinary editor execution. */
44
29
  session?: string;
30
+ /** The host notebook ID, when known. */
31
+ notebook?: string;
45
32
  }
46
33
 
47
34
  /**
@@ -50,21 +37,23 @@ export interface ConnectionInfo {
50
37
  */
51
38
  export function getTerminalCommand(
52
39
  agent: Exclude<AgentTab, "prompt">,
53
- { url, session }: ConnectionInfo,
40
+ { url, notebook, session }: ConnectionInfo,
54
41
  withToken: boolean,
55
- preview?: PairPreviewConfig,
42
+ config?: PairConfig,
56
43
  ): string {
57
- const command = preview?.command ?? getMarimoCommand();
58
- const prefix = preview ? "MARIMO_PAIR_NEXT=1 " : "";
44
+ const command = config?.command ?? getMarimoCommand();
59
45
  const base = [
60
- `${prefix}${command} pair prompt`,
46
+ `${command} pair prompt`,
61
47
  `--url ${shellQuote(url)}`,
62
- preview && session ? `--session ${shellQuote(session)}` : "",
48
+ notebook
49
+ ? `--notebook ${shellQuote(notebook)}`
50
+ : session
51
+ ? `--session ${shellQuote(session)}`
52
+ : "",
63
53
  withToken ? "--with-token" : "",
64
- preview ? "" : `--${agent}`,
65
54
  ]
66
55
  .filter(Boolean)
67
- .join(preview ? " \\\n " : " ");
56
+ .join(" \\\n ");
68
57
  switch (agent) {
69
58
  case "claude":
70
59
  return `claude "$(${base})"`;
@@ -83,37 +72,39 @@ export function getTerminalCommand(
83
72
  * an agent behaves the same as the terminal commands.
84
73
  */
85
74
  export function getRawPrompt(
86
- { url, file, session }: ConnectionInfo,
75
+ { url, notebook, session }: ConnectionInfo,
87
76
  token: string | null,
88
- preview?: PairPreviewConfig,
77
+ config?: PairConfig,
89
78
  ): string {
90
- if (preview) {
91
- const { command, templates } = preview;
92
- return formatPrompt(templates.prompt, {
79
+ const command = config?.command ?? getMarimoCommand();
80
+ const templates = config?.templates;
81
+ const authentication = token
82
+ ? formatPrompt(
83
+ templates?.token ??
84
+ "\n\nFor authenticated notebook discovery, set `export MARIMO_TOKEN={token}` in the shell that runs marimo.",
85
+ { token: shellQuote(token) },
86
+ )
87
+ : "";
88
+ return formatPrompt(
89
+ templates?.prompt ??
90
+ "Pair with me on this running marimo notebook.\n\nURL: {url}\n{notebook}{session}\nRun `{command} pair --help` first.\nUse `{command}` for all marimo commands.{authentication}",
91
+ {
93
92
  command,
94
93
  url,
95
- session: session ? formatPrompt(templates.session, { session }) : "",
96
- authentication: token
97
- ? formatPrompt(templates.token, { token: shellQuote(token) })
94
+ notebook: notebook
95
+ ? formatPrompt(templates?.notebook ?? "Notebook: {notebook}\n", {
96
+ notebook,
97
+ })
98
98
  : "",
99
- });
100
- }
101
-
102
- const fileFlag = getFileFlag(file);
103
- const fileHint = file ? ` (file ${file})` : "";
104
- const executeCmd = `execute-code.sh --url ${shellQuote(url)}${fileFlag}`;
105
- const tokenHint = token
106
- ? `\n\nUse this auth token when calling \`execute-code.sh\`: \`${executeCmd} --token ${shellQuote(token)}\`.`
107
- : "";
108
- return [
109
- "Use the /marimo-pair skill to pair-program on a running marimo notebook.",
110
- "",
111
- `Connect to the notebook at: ${url}${fileHint}`,
112
- "",
113
- `Use \`${executeCmd}\` from the marimo-pair skill to execute code in the notebook.${tokenHint}`,
114
- "",
115
- "Once you are connected, send a fun toast (mo.status.toast(...)) to the user inside marimo letting them know you're ready to pair.",
116
- ].join("\n");
99
+ session:
100
+ session && !notebook
101
+ ? formatPrompt(templates?.session ?? "Session: {session}\n", {
102
+ session,
103
+ })
104
+ : "",
105
+ authentication,
106
+ },
107
+ );
117
108
  }
118
109
 
119
110
  function formatPrompt(