@marimo-team/frontend 0.25.2 → 0.25.3-dev12

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 (178) hide show
  1. package/dist/assets/{CellStatus-BP2mavAq.js → CellStatus-DVOHpdeJ.js} +1 -1
  2. package/dist/assets/{ConnectedDataExplorerComponent-t8fH_TNc.js → ConnectedDataExplorerComponent-TprnxZeJ.js} +1 -1
  3. package/dist/assets/{ErrorBoundary-Gl5nCA8C.js → ErrorBoundary-CM13wrd8.js} +2 -2
  4. package/dist/assets/JsonOutput-Cx1iyibB.js +11 -0
  5. package/dist/assets/{LazyAnyLanguageCodeMirror-CKcCbG2G.js → LazyAnyLanguageCodeMirror-B4fCknq1.js} +2 -2
  6. package/dist/assets/MarimoErrorOutput-s_5GOX8k.js +10 -0
  7. package/dist/assets/{RenderHTML-BbOqmHy3.js → RenderHTML-DX6i99Op.js} +1 -1
  8. package/dist/assets/{RunButton-CK3LhnP5.js → RunButton-BKoh0zYG.js} +1 -1
  9. package/dist/assets/{add-cell-with-ai-8UBC0xwR.js → add-cell-with-ai-BpD_790A.js} +11 -11
  10. package/dist/assets/{add-connection-dialog-DXQnd6eB.js → add-connection-dialog-B1BXCjex.js} +2 -2
  11. package/dist/assets/{agent-panel-D7kMZLG-.js → agent-panel-CDFUc1uu.js} +5 -5
  12. package/dist/assets/ai-model-dropdown-i1vfGeVi.js +13 -0
  13. package/dist/assets/{any-language-editor-BLCAD3yx.js → any-language-editor-BdTWdMCI.js} +1 -1
  14. package/dist/assets/{app-config-button-BKEG4x9u.js → app-config-button-AdV-QDD7.js} +1 -1
  15. package/dist/assets/{blob-D5nfNHOo.js → blob-BG8yoQmx.js} +1 -1
  16. package/dist/assets/{cell-editor-ADVfycpN.js → cell-editor-BKJ06H5F.js} +9 -9
  17. package/dist/assets/cell-link-8Auigi8P.js +1 -0
  18. package/dist/assets/{cells-BRkCaSsh.js → cells-C6YGYGdi.js} +56 -56
  19. package/dist/assets/{chat-display-Dnagy4Nv.js → chat-display-DhgI2MS9.js} +1 -1
  20. package/dist/assets/{chat-panel-DQ6HkWEF.js → chat-panel-Cau71Ygr.js} +2 -2
  21. package/dist/assets/{chat-ui-Df8Vcj6n.js → chat-ui-C5ql1Up-.js} +1 -1
  22. package/dist/assets/code-visibility-BJImdpDi.js +1 -0
  23. package/dist/assets/coerce-DWjFeXOY.js +1 -0
  24. package/dist/assets/{column-preview-Cm2D6vyX.js → column-preview-BZ_iNhtJ.js} +1 -1
  25. package/dist/assets/{command-palette-DTSQ7gOC.js → command-palette-DDC3kBet.js} +1 -1
  26. package/dist/assets/{common-Cu6CDbuw.js → common-Sl3uthjg.js} +1 -1
  27. package/dist/assets/{components-CGw16R_N.js → components-3cou6Mka.js} +1 -1
  28. package/dist/assets/{components-CP_UoFtT.js → components-CskjK6xZ.js} +1 -1
  29. package/dist/assets/{components-CXfO-F47.js → components-YBmtV0p6.js} +1 -1
  30. package/dist/assets/{config-VPSq73n8.js → config-CA1V-wjh.js} +1 -1
  31. package/dist/assets/config-DRIpOKqh.js +1 -0
  32. package/dist/assets/config-Dy28fDwn.js +1 -0
  33. package/dist/assets/{datasource-Cq6x_5ML.js → datasource-DfbNb83e.js} +1 -1
  34. package/dist/assets/{dates-BbFQ2oE4.js → dates-CfmdTL6B.js} +1 -1
  35. package/dist/assets/{dependency-graph-panel--Kgtktcw.js → dependency-graph-panel-L5cBE5sl.js} +1 -1
  36. package/dist/assets/{documentation-panel-BHmbeznw.js → documentation-panel-D7S8JIc2.js} +1 -1
  37. package/dist/assets/{download-DCJXy66H.js → download-C03FOElq.js} +1 -1
  38. package/dist/assets/edit-page-D_Rg0gQO.js +9 -0
  39. package/dist/assets/{error-banner-Cd69vvyX.js → error-banner-COZ7SfYV.js} +1 -1
  40. package/dist/assets/{error-entries-Dk3VZ-OK.js → error-entries-CLnPDv7b.js} +1 -1
  41. package/dist/assets/{error-panel-BPRYmC5J.js → error-panel-D2xrYLue.js} +1 -1
  42. package/dist/assets/{errors-DCrsa1nH.js → errors-CNsgAw6m.js} +1 -1
  43. package/dist/assets/{feedback-button-Bik2-e33.js → feedback-button-DzPunIqU.js} +2 -2
  44. package/dist/assets/feedback-button-Kla7i5RT.js +1 -0
  45. package/dist/assets/{file-explorer-panel-BEEK5lcl.js → file-explorer-panel-Uk4_QIJQ.js} +1 -1
  46. package/dist/assets/{file-name-input-Cdc-TVqO.js → file-name-input-B4fRaQkI.js} +1 -1
  47. package/dist/assets/{floating-outline-XD9FAV-f.js → floating-outline-B5MaM1JS.js} +1 -1
  48. package/dist/assets/{gallery-page-Dm6mGwBi.js → gallery-page-BefvdsM7.js} +1 -1
  49. package/dist/assets/{glide-data-editor-J9CAb70d.js → glide-data-editor-C3eaYlXg.js} +1 -1
  50. package/dist/assets/{home-page-DMHnsRxm.js → home-page-hmvKlc1w.js} +1 -1
  51. package/dist/assets/{hooks-93IwHd2u.js → hooks-DzML9nez.js} +1 -1
  52. package/dist/assets/index-6b4mf8Ai.css +2 -0
  53. package/dist/assets/index-ptK1gxmo.js +30 -0
  54. package/dist/assets/{layout-DmcNEu3P.js → layout-Ck1RoL1B.js} +5 -5
  55. package/dist/assets/{logs-panel-ABM16htT.js → logs-panel-BRytaWaO.js} +1 -1
  56. package/dist/assets/{markdown-renderer-BG3sp2rs.js → markdown-renderer-DX0Xrrfy.js} +1 -1
  57. package/dist/assets/{mermaid-a3xYymTy.js → mermaid-C4EiVqcd.js} +1 -1
  58. package/dist/assets/{name-cell-input-BgEBmqyL.js → name-cell-input-DlpTvPLa.js} +1 -1
  59. package/dist/assets/{outline-panel-DCXQ3_F6.js → outline-panel-B2WIpz-1.js} +1 -1
  60. package/dist/assets/{packages-panel-BMGOmZkR.js → packages-panel-CI5ff9b-.js} +1 -1
  61. package/dist/assets/{panel-accordion-state-C1sdPp0m.js → panel-accordion-state-BqSH5ttk.js} +1 -1
  62. package/dist/assets/panels-BCcZ_yLK.js +107 -0
  63. package/dist/assets/{process-output-D0KGq04R.js → process-output-f3C3PD0Z.js} +1 -1
  64. package/dist/assets/{readonly-code-display-Do50PHI8.js → readonly-code-display-MnMEoYW5.js} +1 -1
  65. package/dist/assets/{readonly-python-code-DCSMIsTX.js → readonly-python-code-ggs4hAzC.js} +1 -1
  66. package/dist/assets/{renderShortcut-BkVJQ_Zh.js → renderShortcut-C1JwYcqW.js} +1 -1
  67. package/dist/assets/{reveal-component-ltaepAqw.js → reveal-component-PiV1eQk9.js} +1 -1
  68. package/dist/assets/run-page-AfZhtEh1.js +1 -0
  69. package/dist/assets/{runtime-details-state-CSPobEdR.js → runtime-details-state-CoL-p0MC.js} +1 -1
  70. package/dist/assets/{schemas-Dv42wLFq.js → schemas-ChRqMOTL.js} +1 -1
  71. package/dist/assets/{scratchpad-panel-Dwd6W5PE.js → scratchpad-panel-D2rWg4k8.js} +1 -1
  72. package/dist/assets/{secrets-panel-Bctbtkrp.js → secrets-panel-D7KN4tVs.js} +1 -1
  73. package/dist/assets/session-panel-CnANhcTo.js +1 -0
  74. package/dist/assets/{snippets-panel-Bh1oTw4m.js → snippets-panel-ClGHtEKQ.js} +1 -1
  75. package/dist/assets/state-CbjBtkdd.js +3 -0
  76. package/dist/assets/{state-XsPO8hNi.js → state-DtGB7xeJ.js} +1 -1
  77. package/dist/assets/{terminal-Ck_vK-34.js → terminal-wnSHYT8N.js} +1 -1
  78. package/dist/assets/{tracing-Di6pUmX8.js → tracing-Dz2rYOFL.js} +1 -1
  79. package/dist/assets/{tracing-panel-PeRXHD8_.js → tracing-panel-DqQY9V91.js} +2 -2
  80. package/dist/assets/{tree-actions-GM5E_bUs.js → tree-actions-DnUokmva.js} +1 -1
  81. package/dist/assets/{useAddCell-GR7eEQrT.js → useAddCell-IN24thff.js} +1 -1
  82. package/dist/assets/{useBoolean-BDD6_xQQ.js → useBoolean-d0Tty94N.js} +1 -1
  83. package/dist/assets/{useCellActionButton-B-9eG5z_.js → useCellActionButton-DmaFZ4oF.js} +1 -1
  84. package/dist/assets/{useDeleteCell-2_bMClHd.js → useDeleteCell-fQlM46Y2.js} +1 -1
  85. package/dist/assets/{useDependencyPanelTab-k3C3M18S.js → useDependencyPanelTab-ImXYINm_.js} +1 -1
  86. package/dist/assets/{useInstallPackage-iX1eufdl.js → useInstallPackage-BW06eter.js} +1 -1
  87. package/dist/assets/{useNotebookActions-NHN6A-cN.js → useNotebookActions-DHt3s-ia.js} +1 -1
  88. package/dist/assets/{useRunCells-B2vUhREg.js → useRunCells-DSJPXE-j.js} +1 -1
  89. package/dist/assets/{useSplitCell-DEb03htp.js → useSplitCell-DQyMF5gd.js} +1 -1
  90. package/dist/assets/{useTheme-COhSHkCj.js → useTheme-n8Dj1zH7.js} +1 -1
  91. package/dist/assets/{vega-component-CnadTgoE.js → vega-component-IouMe13t.js} +1 -1
  92. package/dist/index.html +34 -35
  93. package/package.json +5 -2
  94. package/src/__tests__/fixtures/{pair-preview.ts → pair-config.ts} +3 -2
  95. package/src/__tests__/host-imports.test.ts +100 -0
  96. package/src/__tests__/mount.test.ts +28 -8
  97. package/src/components/data-table/__tests__/context-menu.test.ts +176 -0
  98. package/src/components/data-table/__tests__/date-filter-inputs.test.tsx +24 -1
  99. package/src/components/data-table/__tests__/export-actions.test.tsx +329 -6
  100. package/src/components/data-table/context-menu.tsx +125 -26
  101. package/src/components/data-table/date-filter-inputs.tsx +15 -42
  102. package/src/components/data-table/export-actions.tsx +192 -116
  103. package/src/components/data-table/schemas.ts +14 -36
  104. package/src/components/data-table/utils.ts +18 -0
  105. package/src/components/editor/Output.tsx +9 -2
  106. package/src/components/editor/actions/__tests__/pair-with-agent-commands.test.ts +80 -252
  107. package/src/components/editor/actions/__tests__/pair-with-agent-modal.test.tsx +186 -34
  108. package/src/components/editor/actions/pair-with-agent-commands.ts +42 -51
  109. package/src/components/editor/actions/pair-with-agent-modal.tsx +96 -36
  110. package/src/components/editor/chrome/panels/error-panel.tsx +1 -0
  111. package/src/components/editor/chrome/wrapper/__tests__/attachment-status.test.tsx +372 -0
  112. package/src/components/editor/chrome/wrapper/footer-item.tsx +32 -24
  113. package/src/components/editor/chrome/wrapper/footer-items/attachment-status.tsx +237 -0
  114. package/src/components/editor/chrome/wrapper/footer.tsx +2 -0
  115. package/src/components/editor/errors/__tests__/send-error-report-button.test.tsx +548 -0
  116. package/src/components/editor/errors/send-error-report-button.tsx +69 -0
  117. package/src/components/editor/output/MarimoErrorOutput.tsx +21 -5
  118. package/src/components/editor/output/MarimoTracebackOutput.tsx +35 -20
  119. package/src/components/editor/output/__tests__/ansi-reduce.test.ts +18 -0
  120. package/src/components/editor/output/ansi-reduce.ts +6 -0
  121. package/src/core/attachments/__tests__/toast.test.tsx +58 -0
  122. package/src/core/attachments/state.ts +21 -0
  123. package/src/core/attachments/toast.ts +15 -0
  124. package/src/core/cells/__tests__/cells.test.ts +52 -0
  125. package/src/core/cells/__tests__/error-report.test.ts +139 -0
  126. package/src/core/cells/__tests__/session.test.ts +50 -1
  127. package/src/core/cells/error-report.ts +63 -0
  128. package/src/core/cells/session.ts +28 -1
  129. package/src/core/config/pair.ts +5 -3
  130. package/src/core/edit-app.tsx +2 -2
  131. package/src/core/host/AGENTS.md +6 -0
  132. package/src/core/host/__tests__/events.test.ts +46 -0
  133. package/src/core/host/__tests__/notebook-client.test.ts +191 -0
  134. package/src/core/host/__tests__/protocol.test.ts +214 -0
  135. package/src/core/host/connection.ts +81 -0
  136. package/src/core/host/events.ts +60 -0
  137. package/src/core/host/notebook-client.ts +226 -0
  138. package/src/core/host/protocol.ts +1943 -0
  139. package/src/core/islands/__tests__/bootstrap.test.ts +26 -0
  140. package/src/core/islands/bootstrap.ts +1 -0
  141. package/src/core/run-app.tsx +2 -2
  142. package/src/core/runtime/__tests__/runtime.test.ts +55 -11
  143. package/src/core/runtime/runtime.ts +14 -1
  144. package/src/core/runtime/types.ts +2 -0
  145. package/src/core/websocket/__tests__/{useMarimoKernelConnection.test.ts → connection-state.test.ts} +15 -17
  146. package/src/core/websocket/__tests__/{useWebSocket.test.ts → createConnectionTransport.test.ts} +1 -1
  147. package/src/core/websocket/__tests__/useNotebookClient.hook.test.tsx +140 -0
  148. package/src/core/websocket/__tests__/{useMarimoKernelConnection.hook.test.tsx → useNotebookSession.hook.test.tsx} +352 -65
  149. package/src/core/websocket/connection-state.ts +70 -0
  150. package/src/core/websocket/transports/__tests__/sse.test.ts +1 -4
  151. package/src/core/websocket/transports/create.ts +31 -0
  152. package/src/core/websocket/transports/transport.ts +12 -23
  153. package/src/core/websocket/types.ts +9 -39
  154. package/src/core/websocket/useNotebookClient.ts +80 -0
  155. package/src/core/websocket/{useMarimoKernelConnection.tsx → useNotebookSession.tsx} +67 -225
  156. package/src/mount.tsx +7 -3
  157. package/src/plugins/impl/__tests__/DataTablePlugin.test.tsx +84 -0
  158. package/src/utils/__tests__/dates.test.ts +116 -0
  159. package/src/utils/dates.ts +94 -0
  160. package/dist/assets/JsonOutput-D6HIIsCb.js +0 -11
  161. package/dist/assets/MarimoErrorOutput-D60XkCny.js +0 -6
  162. package/dist/assets/ai-model-dropdown-B0z6-zhp.js +0 -5
  163. package/dist/assets/api-BeC_VSGv.js +0 -1
  164. package/dist/assets/cell-link-LSth_8hT.js +0 -1
  165. package/dist/assets/code-visibility-B-_GvwR5.js +0 -1
  166. package/dist/assets/coerce-CpE6kUdX.js +0 -1
  167. package/dist/assets/config-BOKzA3tF.js +0 -1
  168. package/dist/assets/config-DX8Y2WbJ.js +0 -1
  169. package/dist/assets/edit-page-ChcSgpke.js +0 -9
  170. package/dist/assets/feedback-button-B2gKC5cn.js +0 -1
  171. package/dist/assets/index-Bcdzex-3.css +0 -2
  172. package/dist/assets/index-s-M1OXjs.js +0 -29
  173. package/dist/assets/pair-CaQyK6En.js +0 -1
  174. package/dist/assets/panels-CxVG8-v2.js +0 -4
  175. package/dist/assets/run-page-DF3c5cDk.js +0 -1
  176. package/dist/assets/session-panel-VdDhEBa-.js +0 -1
  177. package/dist/assets/state-_Qai_QyX.js +0 -3
  178. package/src/core/websocket/useWebSocket.tsx +0 -88
@@ -1,30 +1,19 @@
1
1
  /* Copyright 2026 Marimo. All rights reserved. */
2
- export type ConnectionEvent = "open" | "close" | "message" | "error";
3
2
 
4
- export interface IConnectionTransportMap {
5
- open: WebSocketEventMap["open"];
6
- message: WebSocketEventMap["message"];
7
- close: WebSocketEventMap["close"];
8
- error: WebSocketEventMap["error"];
9
- }
10
-
11
- export type ConnectionTransportCallback<T extends ConnectionEvent> = (
12
- data: IConnectionTransportMap[T],
13
- ) => void;
3
+ import type {
4
+ NotebookTransport,
5
+ ConnectionEvent,
6
+ ConnectionTransportCallback,
7
+ IConnectionTransportMap,
8
+ } from "../../host/connection";
9
+ export type {
10
+ ConnectionEvent,
11
+ ConnectionTransportCallback,
12
+ IConnectionTransportMap,
13
+ } from "../../host/connection";
14
14
 
15
- export interface IConnectionTransport {
16
- reconnect(code?: number | undefined, reason?: string | undefined): void;
17
- close(): void;
15
+ export interface IConnectionTransport extends NotebookTransport {
18
16
  send(data: string | ArrayBuffer | Blob | ArrayBufferView<ArrayBuffer>): void;
19
- addEventListener<T extends ConnectionEvent>(
20
- event: T,
21
- callback: ConnectionTransportCallback<T>,
22
- ): void;
23
- removeEventListener<T extends ConnectionEvent>(
24
- event: T,
25
- callback: ConnectionTransportCallback<T>,
26
- ): void;
27
- readyState: WebSocket["readyState"];
28
17
  }
29
18
 
30
19
  export class ConnectionSubscriptions {
@@ -1,47 +1,17 @@
1
1
  /* Copyright 2026 Marimo. All rights reserved. */
2
2
 
3
+ import type { ConnectionStatus as HostConnectionStatus } from "../host/connection";
3
4
  import type { NotificationMessageData } from "../kernel/messages";
4
5
 
6
+ export {
7
+ ConnectionState,
8
+ ConnectionClosedReason as WebSocketClosedReason,
9
+ } from "../host/connection";
10
+
5
11
  export type ConnectionPhase =
6
12
  | NotificationMessageData<"startup-progress">["phase"]
7
13
  | "reconnecting";
8
14
 
9
- export const ConnectionState = {
10
- NOT_STARTED: "NOT_STARTED",
11
- CONNECTING: "CONNECTING",
12
- OPEN: "OPEN",
13
- CLOSING: "CLOSING",
14
- CLOSED: "CLOSED",
15
- } as const;
16
-
17
- export type ConnectionState =
18
- (typeof ConnectionState)[keyof typeof ConnectionState];
19
-
20
- export const WebSocketClosedReason = {
21
- KERNEL_DISCONNECTED: "KERNEL_DISCONNECTED",
22
- KERNEL_STARTUP_ERROR: "KERNEL_STARTUP_ERROR",
23
- } as const;
24
-
25
- export type WebSocketClosedReason =
26
- (typeof WebSocketClosedReason)[keyof typeof WebSocketClosedReason];
27
-
28
- export type ConnectionStatus =
29
- | {
30
- state: typeof ConnectionState.CLOSED;
31
- code: WebSocketClosedReason;
32
- /**
33
- * Human-readable reason for closing the connection.
34
- */
35
- reason: string;
36
- phase?: ConnectionPhase;
37
- }
38
- | {
39
- state: typeof ConnectionState.CONNECTING;
40
- phase?: ConnectionPhase;
41
- }
42
- | {
43
- state:
44
- | typeof ConnectionState.OPEN
45
- | typeof ConnectionState.CLOSING
46
- | typeof ConnectionState.NOT_STARTED;
47
- };
15
+ export type ConnectionStatus = HostConnectionStatus & {
16
+ phase?: ConnectionPhase;
17
+ };
@@ -0,0 +1,80 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+
3
+ import { useStore } from "jotai";
4
+ import { useEffect, useMemo } from "react";
5
+ import useEvent from "react-use-event-hook";
6
+ import { jsonParseWithSpecialChar } from "@/utils/json/json-parser";
7
+ import type {
8
+ NotificationMessage,
9
+ NotificationPayload,
10
+ } from "../kernel/messages";
11
+ import {
12
+ createNotebookClient,
13
+ type NotebookClient,
14
+ type NotebookConnection,
15
+ } from "../host/notebook-client";
16
+ import { classifyCloseEvent } from "./connection-state";
17
+ import {
18
+ createConnectionTransport,
19
+ type ConnectionTransportOptions,
20
+ } from "./transports/create";
21
+
22
+ type Options = ConnectionTransportOptions & {
23
+ sessionId: string;
24
+ messageFormat: NotebookConnection["messageFormat"];
25
+ waitUntilReady: () => Promise<void>;
26
+ checkHealth: () => Promise<boolean>;
27
+ onNotification: (notification: NotificationPayload) => void;
28
+ onMessageError: NotebookConnection["onMessageError"];
29
+ };
30
+
31
+ export function useNotebookClient(options: Options): NotebookClient {
32
+ const store = useStore();
33
+ const { sessionId, static: isStatic, transportType, messageFormat } = options;
34
+ const controller = useMemo(() => createNotebookClient(store), [store]);
35
+ const url = useEvent(options.url);
36
+ const headers = useEvent(options.headers);
37
+ const waitUntilReady = useEvent(options.waitUntilReady);
38
+ const checkHealth = useEvent(options.checkHealth);
39
+ const onNotification = useEvent(options.onNotification);
40
+ const onMessageError = useEvent(options.onMessageError);
41
+
42
+ useEffect(() => {
43
+ const lifetime = new AbortController();
44
+ controller.connect({
45
+ createTransport: (url) =>
46
+ createConnectionTransport({
47
+ url,
48
+ headers,
49
+ static: isStatic,
50
+ transportType,
51
+ }),
52
+ url,
53
+ messageFormat,
54
+ signal: lifetime.signal,
55
+ waitUntilReady,
56
+ checkHealth,
57
+ onMessageError,
58
+ // The host validates the envelope, not the native notification contents.
59
+ onNotification: (notification) =>
60
+ onNotification({ data: notification as NotificationMessage }),
61
+ decodeJson: jsonParseWithSpecialChar,
62
+ classifyClose: classifyCloseEvent,
63
+ });
64
+ return () => lifetime.abort();
65
+ }, [
66
+ controller,
67
+ sessionId,
68
+ isStatic,
69
+ transportType,
70
+ messageFormat,
71
+ url,
72
+ headers,
73
+ waitUntilReady,
74
+ checkHealth,
75
+ onNotification,
76
+ onMessageError,
77
+ ]);
78
+
79
+ return controller.client;
80
+ }
@@ -1,10 +1,11 @@
1
1
  /* Copyright 2026 Marimo. All rights reserved. */
2
2
 
3
3
  import { invalidatePackageData } from "@/core/packages/package-data";
4
- import { useAtom, useAtomValue, useSetAtom } from "jotai";
5
- import { useRef } from "react";
4
+ import { useAtom, useAtomValue, useSetAtom, useStore } from "jotai";
5
+ import { useLayoutEffect } from "react";
6
6
  import { useErrorBoundary } from "react-error-boundary";
7
7
  import { toast } from "@/components/ui/use-toast";
8
+ import { attachmentsAtom } from "@/core/attachments/state";
8
9
  import { getNotebook, useCellActions } from "@/core/cells/cells";
9
10
  import { applyTransactionChanges } from "@/core/cells/document-changes";
10
11
  import { AUTOCOMPLETER } from "@/core/codemirror/completion/Autocompleter";
@@ -12,8 +13,7 @@ import type {
12
13
  NotificationMessageData,
13
14
  NotificationPayload,
14
15
  } from "@/core/kernel/messages";
15
- import { TRANSPORT_EXHAUSTED_REASON } from "@/core/websocket/transports/ws";
16
- import { useConnectionTransport } from "@/core/websocket/useWebSocket";
16
+ import { useNotebookClient } from "@/core/websocket/useNotebookClient";
17
17
  import { renderHTML } from "@/plugins/core/RenderHTML";
18
18
  import {
19
19
  handleWidgetMessage,
@@ -21,11 +21,7 @@ import {
21
21
  } from "@/plugins/impl/anywidget/registry";
22
22
  import { logNever } from "@/utils/assertNever";
23
23
  import { prettyError } from "@/utils/errors";
24
- import {
25
- type JsonString,
26
- safeExtractSetUIElementMessageBuffers,
27
- } from "@/utils/json/base64";
28
- import { jsonParseWithSpecialChar } from "@/utils/json/json-parser";
24
+ import { safeExtractSetUIElementMessageBuffers } from "@/utils/json/base64";
29
25
  import { Logger } from "@/utils/Logger";
30
26
  import { reloadSafe } from "@/utils/reload-safe";
31
27
  import { useAlertActions } from "../alerts/state";
@@ -70,7 +66,7 @@ import { type LayoutState, useLayoutActions } from "../layout/state";
70
66
  import { kioskModeAtom } from "../mode";
71
67
  import { connectionAtom, startupProgressAtom } from "../network/connection";
72
68
  import type { RequestId } from "../network/DeferredRequestRegistry";
73
- import { useRuntimeManager } from "../runtime/config";
69
+ import { runtimeConfigAtom, useRuntimeManager } from "../runtime/config";
74
70
  import { SECRETS_REGISTRY } from "../secrets/request-registry";
75
71
  import { isStaticNotebook } from "../static/static-state";
76
72
  import {
@@ -81,104 +77,10 @@ import { useStorageActions } from "../storage/state";
81
77
  import { useVariablesActions } from "../variables/state";
82
78
  import type { VariableName } from "../variables/types";
83
79
  import { isWasm } from "../wasm/utils";
84
- import {
85
- type ConnectionStatus,
86
- WebSocketClosedReason,
87
- ConnectionState,
88
- } from "./types";
80
+ import { WebSocketClosedReason, ConnectionState } from "./types";
89
81
 
90
82
  const SUPPORTS_LAZY_KERNELS = true;
91
83
 
92
- // All MARIMO_* reasons except TRANSPORT_EXHAUSTED are emitted by the backend
93
- // (marimo/_server/api/endpoints/ws_endpoint.py and ws/*.py). Keep in sync with
94
- // the backend literals. Over the SSE transport these arrive as in-band
95
- // `close` events rather than WebSocket close frames, so every terminal
96
- // reason must be classified here — an unclassified reason falls into the
97
- // retry path.
98
- export type CloseReason =
99
- | "MARIMO_NO_FILE_KEY"
100
- | "MARIMO_NO_SESSION_ID"
101
- | "MARIMO_NO_SESSION"
102
- | "MARIMO_SHUTDOWN"
103
- | "MARIMO_KERNEL_STARTUP_ERROR"
104
- | "MARIMO_UNAUTHORIZED"
105
- | "MARIMO_KIOSK_NOT_ALLOWED"
106
- | typeof TRANSPORT_EXHAUSTED_REASON;
107
-
108
- export type CloseDecision =
109
- | { kind: "terminal"; status: ConnectionStatus; closeTransport: boolean }
110
- | { kind: "gave-up"; status: ConnectionStatus }
111
- | { kind: "retry"; status: ConnectionStatus };
112
-
113
- export function classifyCloseEvent(event: { reason?: string }): CloseDecision {
114
- switch (event.reason as CloseReason | undefined) {
115
- case TRANSPORT_EXHAUSTED_REASON:
116
- return {
117
- kind: "gave-up",
118
- status: {
119
- state: ConnectionState.CLOSED,
120
- code: WebSocketClosedReason.KERNEL_DISCONNECTED,
121
- reason: "kernel not found",
122
- },
123
- };
124
- case "MARIMO_NO_FILE_KEY":
125
- case "MARIMO_NO_SESSION_ID":
126
- case "MARIMO_NO_SESSION":
127
- case "MARIMO_SHUTDOWN":
128
- return {
129
- kind: "terminal",
130
- status: {
131
- state: ConnectionState.CLOSED,
132
- code: WebSocketClosedReason.KERNEL_DISCONNECTED,
133
- reason: "kernel not found",
134
- },
135
- closeTransport: true,
136
- };
137
- case "MARIMO_UNAUTHORIZED":
138
- return {
139
- kind: "terminal",
140
- status: {
141
- state: ConnectionState.CLOSED,
142
- code: WebSocketClosedReason.KERNEL_DISCONNECTED,
143
- reason: "not authorized",
144
- },
145
- closeTransport: true,
146
- };
147
- case "MARIMO_KIOSK_NOT_ALLOWED":
148
- return {
149
- kind: "terminal",
150
- status: {
151
- state: ConnectionState.CLOSED,
152
- code: WebSocketClosedReason.KERNEL_DISCONNECTED,
153
- reason: "kiosk mode is not available for this session",
154
- },
155
- closeTransport: true,
156
- };
157
- case "MARIMO_KERNEL_STARTUP_ERROR":
158
- return {
159
- kind: "terminal",
160
- status: {
161
- state: ConnectionState.CLOSED,
162
- code: WebSocketClosedReason.KERNEL_STARTUP_ERROR,
163
- reason: "Failed to start kernel sandbox",
164
- },
165
- closeTransport: true,
166
- };
167
- default:
168
- // Empty/undefined reasons are normal transient closes. Anything else is
169
- // an unknown server reason; warn so a new MARIMO_* reason doesn't fall
170
- // silently into the retry path.
171
- if (event.reason) {
172
- logNever(event.reason as never);
173
- }
174
- }
175
-
176
- return {
177
- kind: "retry",
178
- status: { state: ConnectionState.CONNECTING },
179
- };
180
- }
181
-
182
84
  function getExistingCells(): CellData[] | undefined {
183
85
  if (!SUPPORTS_LAZY_KERNELS) {
184
86
  return undefined;
@@ -190,16 +92,12 @@ function getExistingCells(): CellData[] | undefined {
190
92
  );
191
93
  }
192
94
 
193
- /**
194
- * Creates a connection to the Marimo kernel and handles incoming messages.
195
- */
196
- export function useMarimoKernelConnection(opts: {
95
+ /** Coordinate session startup and apply notifications to the editor. */
96
+ export function useNotebookSession(opts: {
197
97
  sessionId: SessionId;
198
98
  autoInstantiate: boolean;
199
99
  setCells: (cells: CellData[], layout: LayoutState) => void;
200
100
  }) {
201
- // Track whether we want to try reconnecting.
202
- const shouldTryReconnecting = useRef<boolean>(true);
203
101
  const { autoInstantiate, sessionId, setCells } = opts;
204
102
  const { showBoundary } = useErrorBoundary();
205
103
 
@@ -238,17 +136,21 @@ export function useMarimoKernelConnection(opts: {
238
136
  const setStableSessionId = useSetAtom(stableSessionIdAtom);
239
137
  const setCapabilities = useSetAtom(capabilitiesAtom);
240
138
  const runtimeManager = useRuntimeManager();
139
+ const { notebookId } = useAtomValue(runtimeConfigAtom);
241
140
  const transportType = useAtomValue(connectionTransportTypeAtom);
242
141
  const setCacheInfo = useSetAtom(cacheInfoAtom);
243
142
  const setKernelStartupError = useSetAtom(kernelStartupErrorAtom);
143
+ const setAttachments = useSetAtom(attachmentsAtom);
244
144
  const {
245
145
  setNamespaces: setStorageNamespaces,
246
146
  filterFromVariables: filterStorageFromVariables,
247
147
  } = useStorageActions();
248
148
 
249
- const handleMessage = (e: MessageEvent<JsonString<NotificationPayload>>) => {
250
- const msg = jsonParseWithSpecialChar(e.data);
149
+ const handleNotification = (msg: NotificationPayload) => {
251
150
  switch (msg.data.op) {
151
+ case "attachments":
152
+ setAttachments(msg.data.attachments);
153
+ return;
252
154
  case "reload":
253
155
  reloadSafe();
254
156
  return;
@@ -283,6 +185,7 @@ export function useMarimoKernelConnection(opts: {
283
185
  existingCells,
284
186
  });
285
187
  if (!msg.data.resumed) {
188
+ setAttachments([]);
286
189
  // A freshly started kernel may expose a different environment
287
190
  // (e.g. new env vars); re-run discovery instead of reusing stale
288
191
  // suggestions from the previous kernel instance.
@@ -505,107 +408,59 @@ export function useMarimoKernelConnection(opts: {
505
408
  }
506
409
  };
507
410
 
508
- const tryReconnecting = (code?: number, reason?: string) => {
509
- // If not properly gated, we could try reconnecting forever if the
510
- // issue is not transient. So we want to try reconnecting only once after an
511
- // open connection is closed.
512
- if (shouldTryReconnecting.current) {
513
- shouldTryReconnecting.current = false;
514
- ws.reconnect(code, reason);
515
- }
411
+ const store = useStore();
412
+ const onMessageError = (error: unknown) => {
413
+ toast({
414
+ title: "Failed to handle message",
415
+ description: prettyError(error),
416
+ variant: "danger",
417
+ });
516
418
  };
517
-
518
- // Manual reconnect. Probes /health first to fail fast when the runtime
519
- // is unreachable, instead of waiting on partysocket's retry budget.
520
- const reconnect = async () => {
521
- if (
522
- ws.readyState === WebSocket.OPEN ||
523
- ws.readyState === WebSocket.CONNECTING
524
- ) {
525
- return;
526
- }
527
- shouldTryReconnecting.current = true;
528
- setKernelStartupError(null);
529
- setConnection({ state: ConnectionState.CONNECTING });
530
- const healthy = await runtimeManager.reconcileFromHealth();
531
- if (!healthy) {
532
- shouldTryReconnecting.current = false;
533
- setConnection({
534
- state: ConnectionState.CLOSED,
535
- code: WebSocketClosedReason.KERNEL_DISCONNECTED,
536
- reason: "kernel not found",
537
- });
538
- return;
539
- }
540
- ws.reconnect();
541
- };
542
-
543
- const ws = useConnectionTransport({
419
+ const notebook = useNotebookClient({
420
+ sessionId,
544
421
  static: isStaticNotebook(),
545
422
  transportType,
546
- /**
547
- * Unique URL for this session.
548
- */
423
+ messageFormat:
424
+ notebookId && transportType === "websocket" ? "host" : "session",
549
425
  url: () =>
550
426
  transportType === "sse"
551
427
  ? runtimeManager.getSseURL(sessionId).toString()
552
428
  : runtimeManager.getWsURL(sessionId).toString(),
553
- /**
554
- * Auth headers for the SSE transport. WebSockets cannot send headers
555
- * and instead put the access token in the URL.
556
- */
429
+ // SSE uses headers for auth. WebSockets carry the token in the URL.
557
430
  headers: () => runtimeManager.headers(),
558
-
559
- /**
560
- * The transport is open; kernel-ready establishes session readiness.
561
- */
562
- onOpen: async () => {
563
- updateStartupProgress(null);
564
- // If we are open, we can reset our reconnecting flag.
565
- shouldTryReconnecting.current = true;
566
- },
567
-
568
- /**
569
- * Wait to connect, in case the remote kernel still starting up.
570
- */
571
- waitToConnect: async () => {
572
- if (isStaticNotebook()) {
573
- return;
431
+ waitUntilReady: async () => {
432
+ if (!isStaticNotebook() && !isWasm() && !runtimeManager.isSameOrigin) {
433
+ await runtimeManager.waitForHealthy();
574
434
  }
575
- if (isWasm()) {
576
- return;
577
- }
578
-
579
- if (runtimeManager.isSameOrigin) {
580
- return;
581
- }
582
- await runtimeManager.waitForHealthy();
583
435
  },
584
-
585
- /**
586
- * Handle messages sent by the kernel.
587
- */
588
- onMessage: (e) => {
436
+ checkHealth: () => runtimeManager.reconcileFromHealth(),
437
+ onMessageError,
438
+ onNotification: (notification) => {
589
439
  try {
590
- handleMessage(e);
440
+ handleNotification(notification);
591
441
  } catch (error) {
592
- Logger.error("Failed to handle message", e.data, error);
593
- toast({
594
- title: "Failed to handle message",
595
- description: prettyError(error),
596
- variant: "danger",
597
- });
442
+ Logger.error("Failed to handle notification", notification, error);
443
+ onMessageError(error);
598
444
  }
599
445
  },
446
+ });
600
447
 
601
- /**
602
- * Handle a close event. We may want to reconnect.
603
- */
604
- onClose: (e) => {
605
- Logger.warn("WebSocket closed", e.code, e.reason);
606
- const decision = classifyCloseEvent(e);
448
+ // Subscribe before passive connection setup: static/WASM transports can emit synchronously.
449
+ useLayoutEffect(() => {
450
+ const updateConnection = () => {
451
+ const status = store.get(notebook.connectionAtom);
452
+ if (status.state === ConnectionState.NOT_STARTED) {
453
+ return;
454
+ }
455
+ setAttachments([]);
456
+ if (status.state === ConnectionState.OPEN) {
457
+ updateStartupProgress(null);
458
+ return; // kernel-ready, not the socket, establishes session readiness.
459
+ }
460
+ if (status.state === ConnectionState.CONNECTING) {
461
+ setKernelStartupError(null);
462
+ }
607
463
  setConnection((previous) => {
608
- const status = decision.status;
609
464
  if (
610
465
  status.state === ConnectionState.CLOSED &&
611
466
  status.code === WebSocketClosedReason.KERNEL_STARTUP_ERROR &&
@@ -624,31 +479,18 @@ export function useMarimoKernelConnection(opts: {
624
479
  }
625
480
  return status;
626
481
  });
627
- if (decision.kind === "terminal" && decision.closeTransport) {
628
- ws.close(); // close to prevent reconnecting
629
- return;
630
- }
631
- if (decision.kind === "retry") {
632
- // Session should be valid
633
- // - browser tab might have been closed or re-opened
634
- // - computer might have just woken from sleep
635
- tryReconnecting(e.code, e.reason);
636
- }
637
- },
638
-
639
- /**
640
- * When we encounter an error, we should close the connection.
641
- */
642
- onError: (e) => {
643
- Logger.warn("WebSocket error", e);
644
- setConnection({
645
- state: ConnectionState.CLOSED,
646
- code: WebSocketClosedReason.KERNEL_DISCONNECTED,
647
- reason: "kernel not found",
648
- });
649
- tryReconnecting();
650
- },
651
- });
652
-
653
- return { connection, reconnect };
482
+ };
483
+ const unsubscribe = store.sub(notebook.connectionAtom, updateConnection);
484
+ updateConnection();
485
+ return unsubscribe;
486
+ }, [
487
+ notebook,
488
+ store,
489
+ setConnection,
490
+ setAttachments,
491
+ setKernelStartupError,
492
+ updateStartupProgress,
493
+ ]);
494
+
495
+ return { notebook, connection, reconnect: notebook.reconnect };
654
496
  }
package/src/mount.tsx CHANGED
@@ -10,7 +10,7 @@ import {
10
10
  userConfigAtom,
11
11
  } from "@/core/config/config";
12
12
  import { KnownQueryParams } from "@/core/constants";
13
- import { pairPreviewAtom, pairPreviewSchema } from "@/core/config/pair";
13
+ import { pairConfigAtom, pairConfigSchema } from "@/core/config/pair";
14
14
  import { getFilenameFromDOM } from "@/core/dom/htmlUtils";
15
15
  import { getMarimoCode } from "@/core/meta/globals";
16
16
  import {
@@ -209,7 +209,7 @@ const mountOptionsSchema = z.object({
209
209
  * marimo config overrides
210
210
  */
211
211
  configOverrides: passthroughObject,
212
- pairPreview: pairPreviewSchema.optional(),
212
+ pairConfig: pairConfigSchema.optional(),
213
213
  /**
214
214
  * marimo app config
215
215
  */
@@ -274,6 +274,8 @@ const mountOptionsSchema = z.object({
274
274
  .transform((val) => val as api.Notebook["NotebookV1"]),
275
275
  ]),
276
276
 
277
+ notebookId: z.string().optional(),
278
+
277
279
  /**
278
280
  * Runtime configs
279
281
  */
@@ -284,6 +286,7 @@ const mountOptionsSchema = z.object({
284
286
  // Lazy by default, but can be overridden by the runtime config
285
287
  lazy: z.boolean().default(true),
286
288
  authToken: z.string().nullish(),
289
+ notebookId: z.string().optional(),
287
290
  }),
288
291
  )
289
292
  .nullish()
@@ -347,7 +350,7 @@ function initStore(options: unknown) {
347
350
  );
348
351
  store.set(userConfigAtom, parseUserConfig(parsedOptions.data.config));
349
352
  store.set(appConfigAtom, parseAppConfig(parsedOptions.data.appConfig));
350
- store.set(pairPreviewAtom, parsedOptions.data.pairPreview);
353
+ store.set(pairConfigAtom, parsedOptions.data.pairConfig);
351
354
 
352
355
  // Runtime config
353
356
  if (parsedOptions.data.runtimeConfig.length > 0) {
@@ -364,6 +367,7 @@ function initStore(options: unknown) {
364
367
  } else {
365
368
  store.set(runtimeConfigAtom, {
366
369
  ...DEFAULT_RUNTIME_CONFIG,
370
+ notebookId: parsedOptions.data.notebookId,
367
371
  lazy: false,
368
372
  serverToken: parsedOptions.data.serverToken,
369
373
  });