@marimo-team/frontend 0.25.3-dev80 → 0.25.3-dev82

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 (106) hide show
  1. package/dist/assets/{CellStatus-cD3S0wEA.js → CellStatus-DXroYLOP.js} +1 -1
  2. package/dist/assets/{ErrorBoundary-DJhsFCsX.js → ErrorBoundary-DaQ9THH_.js} +2 -2
  3. package/dist/assets/{JsonOutput-Pm6OCP3J.js → JsonOutput-VxzHyItt.js} +1 -1
  4. package/dist/assets/{MarimoErrorOutput-DlEjLCZ-.js → MarimoErrorOutput-D-hLaDA-.js} +1 -1
  5. package/dist/assets/{RenderHTML-CfQ6i3QV.js → RenderHTML-DjOEZL25.js} +1 -1
  6. package/dist/assets/{RunButton-DvuvkgrG.js → RunButton-CuhNTo5_.js} +1 -1
  7. package/dist/assets/{add-cell-with-ai-By51zSlC.js → add-cell-with-ai-BbVntTh7.js} +1 -1
  8. package/dist/assets/{add-connection-dialog-D7EYSraq.js → add-connection-dialog-Qf63aKV6.js} +1 -1
  9. package/dist/assets/{agent-panel-aCWa3Yai.js → agent-panel-DMOfF3qF.js} +1 -1
  10. package/dist/assets/{ai-model-dropdown-bwTRyFn4.js → ai-model-dropdown-B15l0R62.js} +2 -2
  11. package/dist/assets/{app-config-button-DGhsM0xw.js → app-config-button-BIMB8qjD.js} +1 -1
  12. package/dist/assets/{blob-e6VlOmQP.js → blob-De9GR2s6.js} +1 -1
  13. package/dist/assets/{cell-editor-M8dEnDzk.js → cell-editor-Di0cSFhC.js} +32 -11
  14. package/dist/assets/{cell-link-DFqfW-Ym.js → cell-link-DR3W2HC7.js} +1 -1
  15. package/dist/assets/{cells-T9_Y4Jlv.js → cells-styV7IX8.js} +6 -6
  16. package/dist/assets/{chat-display-DXn2Yrjr.js → chat-display-BzdYdN39.js} +1 -1
  17. package/dist/assets/{chat-panel-D3iUyZId.js → chat-panel-De3YzkSF.js} +1 -1
  18. package/dist/assets/{chat-ui-YuJtv3UJ.js → chat-ui-NTNapdtP.js} +1 -1
  19. package/dist/assets/{code-visibility-CV3S4xuf.js → code-visibility-csc3tgA0.js} +1 -1
  20. package/dist/assets/{column-preview-C6uXRJry.js → column-preview-DkH-oD8f.js} +1 -1
  21. package/dist/assets/{command-palette-CGNUQdgW.js → command-palette-Dg_xstl0.js} +1 -1
  22. package/dist/assets/{common-DeeLgjWQ.js → common-D7VAS6rd.js} +1 -1
  23. package/dist/assets/{components-DyAF3_Nf.js → components-CNmvC781.js} +1 -1
  24. package/dist/assets/config-BQT0uYUo.js +1 -0
  25. package/dist/assets/{config-CjQ4WJnJ.js → config-BbRbT9Ww.js} +1 -1
  26. package/dist/assets/{datasource-gqFgG9Qd.js → datasource-C3yfeDa2.js} +1 -1
  27. package/dist/assets/{dependency-graph-panel-B3T9kSTs.js → dependency-graph-panel-CjoA6L0w.js} +1 -1
  28. package/dist/assets/{documentation-panel-BBpGbT-f.js → documentation-panel-DHWFOiG1.js} +1 -1
  29. package/dist/assets/{download-C8Gw-D0Y.js → download-BrCpRZKN.js} +1 -1
  30. package/dist/assets/{edit-page-CBftLPze.js → edit-page-BPMo_x-T.js} +4 -4
  31. package/dist/assets/{error-entries-uyqOG5tg.js → error-entries-BkEAmmnD.js} +1 -1
  32. package/dist/assets/{error-panel-Cli5zPWi.js → error-panel-BhTfPA_i.js} +1 -1
  33. package/dist/assets/feedback-button-BAcClv7c.js +1 -0
  34. package/dist/assets/{feedback-button-CvrwBGr7.js → feedback-button-D9InK87G.js} +1 -1
  35. package/dist/assets/{file-explorer-panel-BSU5ultY.js → file-explorer-panel-BSd_ZbUC.js} +1 -1
  36. package/dist/assets/{file-name-input-CDrRlIiX.js → file-name-input-RHURglL_.js} +1 -1
  37. package/dist/assets/{floating-outline-GQD9km0W.js → floating-outline-Cm49rYYe.js} +1 -1
  38. package/dist/assets/{gallery-page-B-5H1fEt.js → gallery-page-DdMdOtdv.js} +1 -1
  39. package/dist/assets/{glide-data-editor-B1kpoetH.js → glide-data-editor-eiSlEUyR.js} +1 -1
  40. package/dist/assets/{home-page-3qsPTb4v.js → home-page-DwWvBB0R.js} +1 -1
  41. package/dist/assets/{hooks-BrFYiTJS.js → hooks-CHeUZHPp.js} +1 -1
  42. package/dist/assets/{index-D0IWODMU.js → index-DckSn2w6.js} +8 -8
  43. package/dist/assets/{layout-CGC8ajpe.js → layout-_lp06T0C.js} +3 -3
  44. package/dist/assets/{logs-panel-CdUc5vtA.js → logs-panel-CAAk72Gb.js} +1 -1
  45. package/dist/assets/{markdown-renderer-pKc_bFac.js → markdown-renderer-CAyCgogC.js} +1 -1
  46. package/dist/assets/{name-cell-input-csH3Cy54.js → name-cell-input-DA8hU9XT.js} +1 -1
  47. package/dist/assets/{outline-panel-DhsHYTuo.js → outline-panel-Cc5oQ4LL.js} +1 -1
  48. package/dist/assets/{packages-panel-BtkXVz3L.js → packages-panel-DTfKez6I.js} +1 -1
  49. package/dist/assets/{panel-accordion-state-Bco6nsUI.js → panel-accordion-state-BZHex38W.js} +1 -1
  50. package/dist/assets/panels-DU-SaRHy.js +105 -0
  51. package/dist/assets/{process-output-B2-bT4cq.js → process-output-CrDc_O1D.js} +1 -1
  52. package/dist/assets/{readonly-code-display-Dsp6vWPh.js → readonly-code-display-BR-_jUj8.js} +1 -1
  53. package/dist/assets/{readonly-python-code-B81AjzXM.js → readonly-python-code-D7jcy9ds.js} +1 -1
  54. package/dist/assets/{reveal-component-Cxo3hLGA.js → reveal-component-eLftBwOe.js} +1 -1
  55. package/dist/assets/{run-page-CLP1s8uX.js → run-page-mURyATDJ.js} +1 -1
  56. package/dist/assets/{runtime-details-state-CznIe40L.js → runtime-details-state-hb7A1jgR.js} +1 -1
  57. package/dist/assets/{scratchpad-panel-yHIW63VQ.js → scratchpad-panel-B_gEnoqj.js} +1 -1
  58. package/dist/assets/{session-panel-C7-wmZhc.js → session-panel-DmDjaPsN.js} +1 -1
  59. package/dist/assets/{snippets-panel-BARpOPyS.js → snippets-panel-o9KL5jMi.js} +1 -1
  60. package/dist/assets/state-BkAqJoW7.js +3 -0
  61. package/dist/assets/{state-Bye6rCo4.js → state-Os7LSUUZ.js} +1 -1
  62. package/dist/assets/{terminal-Bhcn_y1N.js → terminal-BI85khrS.js} +1 -1
  63. package/dist/assets/{tracing-DDohxo24.js → tracing-AsNGuDsh.js} +1 -1
  64. package/dist/assets/{tracing-panel-DYhGDAo5.js → tracing-panel-CBcmea6L.js} +2 -2
  65. package/dist/assets/{tree-actions-Cb-nUK1F.js → tree-actions-Cx5EDpA6.js} +1 -1
  66. package/dist/assets/{useAddCell-Bielffe0.js → useAddCell-uJJYtgwa.js} +1 -1
  67. package/dist/assets/{useBoolean-CMgj9hnq.js → useBoolean-8wlrTeus.js} +1 -1
  68. package/dist/assets/{useCellActionButton-DAcQhse_.js → useCellActionButton-CmQ-OcPa.js} +1 -1
  69. package/dist/assets/{useDeleteCell-BV4VemCv.js → useDeleteCell-CXgOCDqY.js} +1 -1
  70. package/dist/assets/{useDependencyPanelTab-PtXIVh53.js → useDependencyPanelTab-Ch6gm7uM.js} +1 -1
  71. package/dist/assets/{useInstallPackage-C7neMPwh.js → useInstallPackage-CYQoMdMQ.js} +1 -1
  72. package/dist/assets/{useNotebookActions-DZBB98fP.js → useNotebookActions-DGShHZ_1.js} +1 -1
  73. package/dist/assets/{useRunCells-BFeZMi7f.js → useRunCells-DAfDmOMC.js} +1 -1
  74. package/dist/assets/{useSplitCell-DllkNFke.js → useSplitCell-NLULgcq7.js} +1 -1
  75. package/dist/assets/{vega-component-D7JFthDD.js → vega-component-BKJU3uaB.js} +1 -1
  76. package/dist/index.html +24 -24
  77. package/package.json +1 -1
  78. package/src/core/host/AGENTS.md +6 -5
  79. package/src/core/host/__tests__/notebook-client.test.ts +368 -350
  80. package/src/core/host/connection.ts +1 -40
  81. package/src/core/host/notebook-client.ts +129 -186
  82. package/src/core/wasm/bridge.ts +0 -8
  83. package/src/core/websocket/__tests__/connection-state.test.ts +0 -12
  84. package/src/core/websocket/__tests__/useNotebookClient.hook.test.tsx +133 -59
  85. package/src/core/websocket/__tests__/useNotebookSession.hook.test.tsx +152 -250
  86. package/src/core/websocket/connection-state.ts +0 -10
  87. package/src/core/websocket/transports/__tests__/notebook.test.ts +502 -0
  88. package/src/core/websocket/transports/notebook.ts +213 -0
  89. package/src/core/websocket/useNotebookClient.ts +28 -17
  90. package/dist/assets/config-DzdxZb17.js +0 -1
  91. package/dist/assets/feedback-button-DknbRCN_.js +0 -1
  92. package/dist/assets/panels-D1i80TGn.js +0 -107
  93. package/dist/assets/state-BRYi026f.js +0 -3
  94. package/dist/assets/ws-BXWY541V.js +0 -22
  95. package/src/core/host/__tests__/events.test.ts +0 -49
  96. package/src/core/host/events.ts +0 -68
  97. package/src/core/websocket/__tests__/createConnectionTransport.test.ts +0 -49
  98. package/src/core/websocket/transports/__tests__/basic.test.ts +0 -275
  99. package/src/core/websocket/transports/__tests__/sse.test.ts +0 -352
  100. package/src/core/websocket/transports/__tests__/transport.test.ts +0 -308
  101. package/src/core/websocket/transports/__tests__/ws.test.ts +0 -125
  102. package/src/core/websocket/transports/basic.ts +0 -102
  103. package/src/core/websocket/transports/create.ts +0 -31
  104. package/src/core/websocket/transports/sse.ts +0 -320
  105. package/src/core/websocket/transports/transport.ts +0 -50
  106. package/src/core/websocket/transports/ws.ts +0 -98
@@ -5,52 +5,48 @@ import { act, renderHook } from "@testing-library/react";
5
5
  import { createStore, Provider } from "jotai";
6
6
  import type { PropsWithChildren } from "react";
7
7
  import { beforeEach, describe, expect, it, vi } from "vitest";
8
+ import type { NotebookTransport } from "@marimo-team/client";
8
9
  import { ConnectionState } from "../types";
9
- import { createConnectionTransport } from "../transports/create";
10
- import type { IConnectionTransport } from "../transports/transport";
10
+ import { Deferred } from "@/utils/Deferred";
11
+ import { createNotebookTransport } from "../transports/notebook";
11
12
  import { useNotebookClient } from "../useNotebookClient";
12
13
  import { fixtureMessages } from "../../host/__tests__/fixtures";
13
14
  import { agentAttachmentsAtom } from "../../attachments/state";
14
15
  import { notebookClientAtom } from "../state";
15
16
 
16
- vi.mock("../transports/create", () => ({ createConnectionTransport: vi.fn() }));
17
+ vi.mock("../transports/notebook", () => ({ createNotebookTransport: vi.fn() }));
17
18
 
18
- function makeTransport(url?: () => string) {
19
- const events = new EventTarget();
20
- const transport: IConnectionTransport = {
21
- readyState: WebSocket.CLOSED,
22
- reconnect: vi.fn(() => {
23
- url?.();
24
- transport.readyState = WebSocket.OPEN;
25
- events.dispatchEvent(new Event("open"));
26
- }),
27
- close: vi.fn(() => {
28
- transport.readyState = WebSocket.CLOSED;
29
- events.dispatchEvent(new CloseEvent("close"));
30
- }),
31
- send: vi.fn(),
32
- addEventListener: events.addEventListener.bind(events),
33
- removeEventListener: events.removeEventListener.bind(events),
34
- };
19
+ /** A transport whose connections the test drives. */
20
+ function makeTransport() {
21
+ const opened: {
22
+ signal: AbortSignal;
23
+ open(): void;
24
+ receive(message: unknown): void;
25
+ }[] = [];
26
+ const transport: NotebookTransport = ({ signal }, handlers) =>
27
+ new Promise((resolve) => {
28
+ opened.push({ signal, open: handlers.open, receive: handlers.message });
29
+ signal.addEventListener("abort", () => resolve(), { once: true });
30
+ });
35
31
  return {
36
32
  transport,
37
- receive: (message: unknown) =>
38
- events.dispatchEvent(
39
- new MessageEvent("message", { data: JSON.stringify(message) }),
40
- ),
33
+ opened,
34
+ /** Open the latest connection and deliver a host-framed notification. */
41
35
  notify: () =>
42
- events.dispatchEvent(
43
- new MessageEvent("message", {
44
- data: JSON.stringify({
45
- event: "marimo",
46
- data: { marimo: { op: "reconnected" } },
47
- }),
48
- }),
49
- ),
36
+ opened.at(-1)?.receive({
37
+ event: "marimo",
38
+ data: { marimo: { op: "reconnected" } },
39
+ }),
50
40
  };
51
41
  }
52
42
 
53
- function setup(strict = false) {
43
+ function setup(
44
+ strict = false,
45
+ readiness: {
46
+ waitUntilReady?: () => Promise<void>;
47
+ checkHealth?: () => Promise<boolean>;
48
+ } = {},
49
+ ) {
54
50
  const store = createStore();
55
51
  const options = {
56
52
  static: false,
@@ -60,6 +56,7 @@ function setup(strict = false) {
60
56
  waitUntilReady: async () => undefined,
61
57
  checkHealth: async () => true,
62
58
  onNotification: vi.fn(),
59
+ ...readiness,
63
60
  };
64
61
  const wrapper = ({ children }: PropsWithChildren) => (
65
62
  <Provider store={store}>{children}</Provider>
@@ -73,19 +70,97 @@ function setup(strict = false) {
73
70
  }
74
71
 
75
72
  beforeEach(() => {
76
- vi.mocked(createConnectionTransport).mockReset();
73
+ vi.mocked(createNotebookTransport).mockReset();
77
74
  });
78
75
 
79
76
  describe("useNotebookClient", () => {
77
+ it.each([true, false])(
78
+ "rechecks health after failed startup (healthy: %s)",
79
+ async (healthy) => {
80
+ const fake = makeTransport();
81
+ vi.mocked(createNotebookTransport).mockImplementation(
82
+ ({ url }) =>
83
+ async (attempt, handlers) => {
84
+ if (typeof url === "function") {
85
+ await url();
86
+ }
87
+ if (!attempt.signal.aborted) {
88
+ return fake.transport(attempt, handlers);
89
+ }
90
+ },
91
+ );
92
+ const waitUntilReady = vi
93
+ .fn()
94
+ .mockRejectedValue(new Error("Startup gave up"));
95
+ const checkHealth = vi.fn().mockResolvedValue(healthy);
96
+ const { result, store, unmount } = setup(false, {
97
+ waitUntilReady,
98
+ checkHealth,
99
+ });
100
+ await act(async () => Promise.resolve());
101
+ expect(store.get(result.current.connectionAtom).state).toBe(
102
+ ConnectionState.CLOSED,
103
+ );
104
+
105
+ await act(async () => result.current.reconnect());
106
+ expect({
107
+ healthChecks: checkHealth.mock.calls.length,
108
+ initialWaits: waitUntilReady.mock.calls.length,
109
+ connections: fake.opened.length,
110
+ state: store.get(result.current.connectionAtom).state,
111
+ }).toEqual({
112
+ healthChecks: 1,
113
+ initialWaits: 1,
114
+ connections: healthy ? 1 : 0,
115
+ state: healthy ? ConnectionState.CONNECTING : ConnectionState.CLOSED,
116
+ });
117
+ act(() => fake.opened.at(-1)?.open());
118
+ expect(store.get(result.current.connectionAtom).state).toBe(
119
+ healthy ? ConnectionState.OPEN : ConnectionState.CLOSED,
120
+ );
121
+ unmount();
122
+ },
123
+ );
124
+
125
+ it("waits for initial readiness without reviving an unmounted connection", async () => {
126
+ const ready = new Deferred<void>();
127
+ const resolvedUrl = vi.fn();
128
+ vi.mocked(createNotebookTransport).mockImplementation(
129
+ ({ url }) =>
130
+ async () => {
131
+ if (typeof url === "function") {
132
+ await url();
133
+ resolvedUrl();
134
+ }
135
+ },
136
+ );
137
+ const { result, store, unmount } = setup(false, {
138
+ waitUntilReady: () => ready.promise,
139
+ });
140
+ await act(async () => Promise.resolve());
141
+ expect(resolvedUrl).not.toHaveBeenCalled();
142
+ expect(store.get(result.current.connectionAtom)).toEqual({
143
+ state: ConnectionState.CONNECTING,
144
+ });
145
+ unmount();
146
+ await act(async () => ready.resolve());
147
+ expect(store.get(result.current.connectionAtom)).toEqual({
148
+ state: ConnectionState.NOT_STARTED,
149
+ });
150
+ expect(store.get(notebookClientAtom)).toBeNull();
151
+ });
152
+
80
153
  it("exposes host presence to the UI without waiting for kernel-ready", async () => {
81
- const transport = makeTransport();
82
- vi.mocked(createConnectionTransport).mockReturnValue(transport.transport);
154
+ const fake = makeTransport();
155
+ vi.mocked(createNotebookTransport).mockReturnValue(fake.transport);
83
156
  const { result, store, unmount } = setup();
84
157
  await act(async () => Promise.resolve());
85
158
  expect(store.get(notebookClientAtom)).toBe(result.current);
86
- act(() =>
87
- fixtureMessages("notebook-events.sse").forEach(transport.receive),
88
- );
159
+ const [connection] = fake.opened;
160
+ act(() => {
161
+ connection.open();
162
+ fixtureMessages("notebook-events.sse").forEach(connection.receive);
163
+ });
89
164
  expect(store.get(agentAttachmentsAtom)).toEqual([
90
165
  {
91
166
  id: "a2",
@@ -101,49 +176,48 @@ describe("useNotebookClient", () => {
101
176
  });
102
177
 
103
178
  it("uses current callbacks and URL providers without reconnecting on a rerender", async () => {
104
- const first = makeTransport();
105
- vi.mocked(createConnectionTransport).mockReturnValue(first.transport);
179
+ const fake = makeTransport();
180
+ vi.mocked(createNotebookTransport).mockReturnValue(fake.transport);
106
181
  const { rerender, options, unmount } = setup();
107
182
  await act(async () => Promise.resolve());
108
183
  const onNotification = vi.fn();
109
184
  rerender({ ...options, onNotification, url: () => "ws://redirected/ws" });
110
- act(() => first.notify());
185
+ act(() => fake.notify());
111
186
  expect(options.onNotification).not.toHaveBeenCalled();
112
187
  expect(onNotification).toHaveBeenCalledExactlyOnceWith({
113
188
  data: { op: "reconnected" },
114
189
  });
115
- expect(createConnectionTransport).toHaveBeenCalledOnce();
116
- expect(vi.mocked(createConnectionTransport).mock.calls[0][0].url()).toBe(
190
+ expect(createNotebookTransport).toHaveBeenCalledOnce();
191
+ const [{ url }] = vi.mocked(createNotebookTransport).mock.calls[0];
192
+ expect(String(await (url as () => Promise<string>)())).toBe(
117
193
  "ws://redirected/ws",
118
194
  );
119
195
  unmount();
120
196
  });
121
197
 
122
- it("cleans up every transport under Strict Mode and only connects the active lifetime", async () => {
123
- const transports: ReturnType<typeof makeTransport>[] = [];
124
- vi.mocked(createConnectionTransport).mockImplementation(() => {
198
+ it("closes every stream under Strict Mode and only follows the active lifetime", async () => {
199
+ const fakes: ReturnType<typeof makeTransport>[] = [];
200
+ vi.mocked(createNotebookTransport).mockImplementation(() => {
125
201
  const next = makeTransport();
126
- transports.push(next);
202
+ fakes.push(next);
127
203
  return next.transport;
128
204
  });
129
205
  const { result, store, options, unmount } = setup(true);
130
206
  await act(async () => Promise.resolve());
131
- expect(transports).toHaveLength(2);
132
- expect(transports[0].transport.reconnect).not.toHaveBeenCalled();
133
- expect(transports[0].transport.close).toHaveBeenCalledOnce();
134
- expect(transports[1].transport.reconnect).toHaveBeenCalledOnce();
207
+ expect(fakes).toHaveLength(2);
208
+ expect(fakes[0].opened.every((attempt) => attempt.signal.aborted)).toBe(
209
+ true,
210
+ );
211
+ const [active] = fakes[1].opened;
212
+ act(() => active.open());
135
213
  expect(store.get(result.current.connectionAtom)).toEqual({
136
214
  state: ConnectionState.OPEN,
137
215
  });
138
- act(() => {
139
- for (const transport of transports) {
140
- transport.notify();
141
- }
142
- });
216
+ act(() => fakes.forEach((fake) => fake.notify()));
143
217
  expect(options.onNotification).toHaveBeenCalledOnce();
144
218
  unmount();
145
- expect(transports[1].transport.close).toHaveBeenCalledOnce();
219
+ expect(active.signal.aborted).toBe(true);
146
220
  await result.current.reconnect();
147
- expect(transports[1].transport.reconnect).toHaveBeenCalledOnce();
221
+ expect(fakes[1].opened).toHaveLength(1);
148
222
  });
149
223
  });