@marimo-team/frontend 0.19.3-dev4 → 0.19.3-dev42

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 (179) hide show
  1. package/dist/assets/{CellStatus-Ba6Af_Tb.js → CellStatus-b7Yo2X9j.js} +1 -1
  2. package/dist/assets/{ConnectedDataExplorerComponent-KlUs_Sz3.js → ConnectedDataExplorerComponent-Cr6-n9Em.js} +1 -1
  3. package/dist/assets/{ErrorBoundary-Drf1manw.js → ErrorBoundary-C7JBxSzd.js} +1 -1
  4. package/dist/assets/{ImperativeModal-q6QlC2aZ.js → ImperativeModal-DVhvP4lH.js} +1 -1
  5. package/dist/assets/{JsonOutput-4ruRfyOj.js → JsonOutput-C8Eo1zBR.js} +5 -5
  6. package/dist/assets/{LazyAnyLanguageCodeMirror-jpEDlD0M.js → LazyAnyLanguageCodeMirror-Cp2punaU.js} +2 -2
  7. package/dist/assets/{MarimoErrorOutput-DnjH3pD8.js → MarimoErrorOutput-CXBGzjO2.js} +2 -2
  8. package/dist/assets/{RenderHTML-DaJXe2U2.js → RenderHTML-SoetmcW2.js} +1 -1
  9. package/dist/assets/VisuallyHidden-B9t3FhTP.js +1 -0
  10. package/dist/assets/{add-cell-with-ai-Bsds_6SU.js → add-cell-with-ai-D2qS3Nos.js} +21 -21
  11. package/dist/assets/{add-database-form-CqIp3_WN.js → add-database-form-BBkiGMZ_.js} +2 -2
  12. package/dist/assets/agent-panel-BzV4XUTo.js +287 -0
  13. package/dist/assets/{ai-model-dropdown-LK8Wr5iu.js → ai-model-dropdown-CrMTCgo7.js} +1 -1
  14. package/dist/assets/{alert-dialog-k5KxevGr.js → alert-dialog-jcHA5geR.js} +1 -1
  15. package/dist/assets/{any-language-editor-DQu1Tt2N.js → any-language-editor-Cm83E7D_.js} +1 -1
  16. package/dist/assets/{app-config-button-BaVc4Y5z.js → app-config-button-9izWmQ0X.js} +1 -1
  17. package/dist/assets/button-B8cGZzP5.js +1 -0
  18. package/dist/assets/{cache-panel-C1So4Zu3.js → cache-panel-1FqnpB9y.js} +1 -1
  19. package/dist/assets/cell-editor-Do6lWWk9.js +23 -0
  20. package/dist/assets/cell-link-BP7_Ns0N.js +1 -0
  21. package/dist/assets/{cells-KYKWFk6C.js → cells-Cv9PtwL9.js} +49 -49
  22. package/dist/assets/{chat-components-O6DUIpBx.js → chat-components-Be6BPrbT.js} +1 -1
  23. package/dist/assets/{chat-display-DD3KokYi.js → chat-display-BRKfnhbm.js} +1 -1
  24. package/dist/assets/{chat-panel-D4DIcOM1.js → chat-panel-71zcilvi.js} +2 -2
  25. package/dist/assets/client-CGOlSEYr.js +4 -0
  26. package/dist/assets/{column-preview-EpCGr4Xp.js → column-preview-MC6VOHbd.js} +1 -1
  27. package/dist/assets/{command-Dqe0kvHp.js → command-n_oMaKjl.js} +1 -1
  28. package/dist/assets/{command-palette-DWacsFDk.js → command-palette-DfZNcw7W.js} +1 -1
  29. package/dist/assets/common-MUZIZluQ.js +1 -0
  30. package/dist/assets/config-DFDEcYvy.js +1 -0
  31. package/dist/assets/context-DHfVoQfl.js +1 -0
  32. package/dist/assets/{copy-icon-B69c-352.js → copy-icon-jWsqdLn1.js} +1 -1
  33. package/dist/assets/{datasource-JeWYnuIr.js → datasource-CEsMStKs.js} +2 -2
  34. package/dist/assets/{dependency-graph-panel-BJibnwCO.js → dependency-graph-panel-CNTGbfLZ.js} +4 -4
  35. package/dist/assets/{dialog-DUEuLcT2.js → dialog-CF5DtF1E.js} +1 -1
  36. package/dist/assets/{dist-DOFFh6Ii.js → dist-Dg7UO_Vw.js} +1 -1
  37. package/dist/assets/{documentation-panel-B2W3q2YB.js → documentation-panel-Cb9AHO2C.js} +1 -1
  38. package/dist/assets/{download-NfnO_JCs.js → download-24bI2vH0.js} +1 -1
  39. package/dist/assets/edit-page-DSuXLdcn.js +12 -0
  40. package/dist/assets/{error-banner-DU5Qb8a8.js → error-banner-DvT0IGDZ.js} +1 -1
  41. package/dist/assets/{error-panel-Bv-7GYgJ.js → error-panel-CpYH0GfR.js} +1 -1
  42. package/dist/assets/{es-KtEicG7U.js → es-BITbuY9w.js} +1 -1
  43. package/dist/assets/{field-DDKGFzpC.js → field-Clr_fqUr.js} +1 -1
  44. package/dist/assets/{file-explorer-panel-CToUezud.js → file-explorer-panel-CdA81LHh.js} +1 -1
  45. package/dist/assets/{floating-outline-Db40vhG8.js → floating-outline-BbJ4ldyu.js} +1 -1
  46. package/dist/assets/{focus-BCdX47jS.js → focus-D1y1tXyC.js} +1 -1
  47. package/dist/assets/{form-DwtJQd_Z.js → form-BAtvsPJL.js} +2 -2
  48. package/dist/assets/{glide-data-editor-D_bRnWfy.js → glide-data-editor-Dv8ZW9dk.js} +1 -1
  49. package/dist/assets/{globals-MS86g8oR.js → globals-C6OH39EA.js} +1 -1
  50. package/dist/assets/{home-page-BfVf41OG.js → home-page-B_YprqxM.js} +2 -2
  51. package/dist/assets/house-CncUa_LL.js +1 -0
  52. package/dist/assets/index-C30GhE0W.css +2 -0
  53. package/dist/assets/index-DoE3JZXY.js +43 -0
  54. package/dist/assets/input-B80Yt1uu.js +1 -0
  55. package/dist/assets/{kiosk-mode-CEhvsEr0.js → kiosk-mode-DfyjlR7p.js} +1 -1
  56. package/dist/assets/{label-qwandMoh.js → label-CNZLffHW.js} +1 -1
  57. package/dist/assets/{layout-Cvaok8Kj.js → layout-9uQoV-6h.js} +4 -4
  58. package/dist/assets/links-DbDrjRnm.js +1 -0
  59. package/dist/assets/{logs-panel-J2FKnKaj.js → logs-panel-svcirwjp.js} +1 -1
  60. package/dist/assets/{markdown-renderer-BlG9DgUG.js → markdown-renderer-DlVqlHOL.js} +2 -2
  61. package/dist/assets/mode-PeuS_Lp-.js +1 -0
  62. package/dist/assets/{multi-map-fjX9ImVF.js → multi-map-CQd4MZr5.js} +1 -1
  63. package/dist/assets/name-cell-input-YMoA0SQj.js +1 -0
  64. package/dist/assets/{outline-panel-Doj3GJrQ.js → outline-panel-RKJ5Mqrt.js} +1 -1
  65. package/dist/assets/{packages-panel-nqWXQzKf.js → packages-panel-BuiAGEBw.js} +1 -1
  66. package/dist/assets/panels-BKsZUDjc.js +1 -0
  67. package/dist/assets/{process-output-DiSW8Nbo.js → process-output-KJWsSvCT.js} +1 -1
  68. package/dist/assets/{readonly-python-code-CKY5LsMp.js → readonly-python-code-HPlG_YPX.js} +1 -1
  69. package/dist/assets/run-page-CBDzVDX3.js +1 -0
  70. package/dist/assets/scratchpad-panel-CarbQVYs.js +1 -0
  71. package/dist/assets/{secrets-panel-CDWmmmBS.js → secrets-panel-BMY6PPth.js} +1 -1
  72. package/dist/assets/{select-D0g5GnIs.js → select-D9lTzMzP.js} +1 -1
  73. package/dist/assets/{session-panel-CGFRSBw9.js → session-panel-Cv14Ehfm.js} +1 -1
  74. package/dist/assets/{slides-component-MkPkpql1.js → slides-component-Dp0Yv5b0.js} +1 -1
  75. package/dist/assets/{snippets-panel-ClHeSpc5.js → snippets-panel-OAdQXQ93.js} +1 -1
  76. package/dist/assets/state-DYG6kYly.js +1 -0
  77. package/dist/assets/state-xh6GqNrp.js +1 -0
  78. package/dist/assets/{switch-BmbGJWHc.js → switch-DPeh0R76.js} +1 -1
  79. package/dist/assets/{terminal-BvgBa6Ri.js → terminal-BbAhzgnR.js} +1 -1
  80. package/dist/assets/{textarea-WklymBeK.js → textarea-wbzgrXvB.js} +1 -1
  81. package/dist/assets/{tracing-D0WYhZdr.js → tracing-Bh3EJxAS.js} +1 -1
  82. package/dist/assets/{tracing-panel-CNxN58z7.js → tracing-panel-BzSQ7qvB.js} +2 -2
  83. package/dist/assets/{types-BrgXpvGt.js → types-B8Qb1FfB.js} +1 -1
  84. package/dist/assets/{useAddCell-a9qZ0_KE.js → useAddCell-DBGvrN8K.js} +1 -1
  85. package/dist/assets/{useBoolean-5kuXz69O.js → useBoolean-CyOFPk5r.js} +1 -1
  86. package/dist/assets/{useCellActionButton-9W_R41MM.js → useCellActionButton-BlS_HKk-.js} +1 -1
  87. package/dist/assets/{useDateFormatter-CV0QXb5P.js → useDateFormatter-DsANziQR.js} +1 -1
  88. package/dist/assets/useDeleteCell-BvQIJfpI.js +1 -0
  89. package/dist/assets/{useDependencyPanelTab-0reaqvvh.js → useDependencyPanelTab-BqEhbPr2.js} +1 -1
  90. package/dist/assets/useInterval-BGPIviJp.js +1 -0
  91. package/dist/assets/useNotebookActions-D1Woz3AV.js +1 -0
  92. package/dist/assets/{useNumberFormatter-D8ks3oPN.js → useNumberFormatter-FoXhpyAb.js} +1 -1
  93. package/dist/assets/usePress-DTwIUo40.js +7 -0
  94. package/dist/assets/useRunCells-B9Xr4tcH.js +1 -0
  95. package/dist/assets/useSplitCell-7xBW3b8-.js +1 -0
  96. package/dist/assets/utilities.esm-xahhGpny.js +3 -0
  97. package/dist/assets/{vega-component-DpAAiTdH.js → vega-component-dUiiVmIx.js} +1 -1
  98. package/dist/assets/{write-secret-modal-CLm48gMe.js → write-secret-modal-hOetwavI.js} +1 -1
  99. package/dist/index.html +54 -54
  100. package/package.json +5 -5
  101. package/src/__mocks__/requests.ts +1 -0
  102. package/src/__tests__/mount.test.ts +128 -0
  103. package/src/components/app-config/__tests__/get-dirty-values.test.ts +1 -1
  104. package/src/components/app-config/user-config-form.tsx +1 -1
  105. package/src/components/chat/acp/agent-panel.tsx +56 -43
  106. package/src/components/chat/chat-utils.ts +0 -19
  107. package/src/components/data-table/column-header.tsx +1 -1
  108. package/src/components/editor/KernelStartupErrorModal.tsx +101 -0
  109. package/src/components/editor/actions/name-cell-input.tsx +10 -4
  110. package/src/components/editor/ai/completion-handlers.tsx +1 -1
  111. package/src/components/editor/alerts/connecting-alert.tsx +33 -6
  112. package/src/components/editor/chrome/types.ts +2 -4
  113. package/src/components/editor/chrome/wrapper/app-chrome.tsx +55 -58
  114. package/src/components/editor/chrome/wrapper/footer-items/runtime-settings.tsx +150 -96
  115. package/src/components/editor/renderers/vertical-layout/__tests__/useFocusFirstEditor.test.ts +27 -0
  116. package/src/components/editor/renderers/vertical-layout/useFocusFirstEditor.ts +6 -0
  117. package/src/components/utils/lazy-mount.tsx +29 -8
  118. package/src/core/MarimoApp.tsx +2 -0
  119. package/src/core/cells/cells.ts +2 -0
  120. package/src/core/cells/scrollCellIntoView.ts +3 -2
  121. package/src/core/codemirror/cm.ts +2 -0
  122. package/src/core/codemirror/lsp/__tests__/notebook-lsp.test.ts +123 -0
  123. package/src/core/codemirror/lsp/notebook-lsp.ts +44 -4
  124. package/src/core/codemirror/misc/__tests__/string-braces.test.ts +200 -0
  125. package/src/core/codemirror/misc/string-braces.ts +37 -0
  126. package/src/core/errors/state.ts +7 -1
  127. package/src/core/export/__tests__/hooks.test.ts +504 -0
  128. package/src/core/export/hooks.ts +93 -4
  129. package/src/core/islands/bridge.ts +1 -0
  130. package/src/core/islands/main.ts +2 -0
  131. package/src/core/kernel/__tests__/handlers.test.ts +2 -2
  132. package/src/core/kernel/state.ts +1 -0
  133. package/src/core/network/__tests__/requests-lazy.test.ts +1 -1
  134. package/src/core/network/__tests__/requests-network.test.ts +0 -18
  135. package/src/core/network/requests-lazy.ts +3 -2
  136. package/src/core/network/requests-network.ts +10 -7
  137. package/src/core/network/requests-static.ts +1 -0
  138. package/src/core/network/requests-toasting.tsx +1 -0
  139. package/src/core/network/types.ts +2 -0
  140. package/src/core/wasm/bridge.ts +1 -0
  141. package/src/core/websocket/types.ts +1 -0
  142. package/src/core/websocket/useMarimoKernelConnection.tsx +18 -1
  143. package/src/css/globals.css +2 -0
  144. package/src/hooks/__tests__/useInterval.test.tsx +104 -0
  145. package/src/hooks/useInterval.ts +32 -6
  146. package/src/mount.tsx +6 -0
  147. package/src/plugins/impl/chat/chat-ui.tsx +16 -4
  148. package/src/plugins/impl/chat/types.ts +5 -12
  149. package/src/plugins/impl/data-frames/DataFramePlugin.tsx +3 -1
  150. package/src/utils/events.ts +1 -0
  151. package/dist/assets/VisuallyHidden-BodIky8L.js +0 -1
  152. package/dist/assets/agent-panel-CaAPVPdJ.js +0 -287
  153. package/dist/assets/button-DuYGqRtX.js +0 -1
  154. package/dist/assets/cell-editor-OFm-OSAP.js +0 -23
  155. package/dist/assets/cell-link-CfLJRl3p.js +0 -1
  156. package/dist/assets/client-Cha_JfGC.js +0 -4
  157. package/dist/assets/common-A6YWtmpq.js +0 -1
  158. package/dist/assets/config-babG4OBR.js +0 -1
  159. package/dist/assets/context-BAYdLMF_.js +0 -1
  160. package/dist/assets/edit-page-nuU4FVXi.js +0 -12
  161. package/dist/assets/globe-CY9im410.js +0 -1
  162. package/dist/assets/index-BI88xbv4.js +0 -43
  163. package/dist/assets/index-Chgc_07S.css +0 -2
  164. package/dist/assets/input-CaEtLL8p.js +0 -1
  165. package/dist/assets/links-ENMiP32L.js +0 -1
  166. package/dist/assets/mode-CK5Oq-Jz.js +0 -1
  167. package/dist/assets/name-cell-input-D7axzd6k.js +0 -1
  168. package/dist/assets/panels-CdYbZBqo.js +0 -1
  169. package/dist/assets/run-page-GP8eGE39.js +0 -1
  170. package/dist/assets/scratchpad-panel-B1p8zqAE.js +0 -1
  171. package/dist/assets/state-BBgXjqJI.js +0 -1
  172. package/dist/assets/state-CP7_TGWl.js +0 -1
  173. package/dist/assets/useDeleteCell-5kJUaejE.js +0 -1
  174. package/dist/assets/useInterval-DpipYmgs.js +0 -1
  175. package/dist/assets/useNotebookActions-o341ZCMJ.js +0 -1
  176. package/dist/assets/usePress-C2LPFxyv.js +0 -7
  177. package/dist/assets/useRunCells-wXhl9zOP.js +0 -1
  178. package/dist/assets/useSplitCell-mmm5jxn2.js +0 -1
  179. package/dist/assets/utilities.esm-Ckt5kMF-.js +0 -3
@@ -0,0 +1,128 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+
3
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
4
+ import { connectionAtom } from "@/core/network/connection";
5
+ import { store } from "@/core/state/jotai";
6
+ import { WebSocketState } from "@/core/websocket/types";
7
+ import { mount, visibleForTesting } from "../mount";
8
+
9
+ // Mock React DOM
10
+ vi.mock("react-dom/client", () => ({
11
+ createRoot: vi.fn(() => ({
12
+ render: vi.fn(),
13
+ })),
14
+ }));
15
+
16
+ // Mock static state
17
+ vi.mock("@/core/static/static-state", () => ({
18
+ isStaticNotebook: vi.fn(() => false),
19
+ }));
20
+
21
+ // Mock other side-effect modules
22
+ vi.mock("@/core/vscode/vscode-bindings", () => ({
23
+ maybeRegisterVSCodeBindings: vi.fn(),
24
+ }));
25
+
26
+ vi.mock("@/plugins/plugins", () => ({
27
+ initializePlugins: vi.fn(),
28
+ }));
29
+
30
+ vi.mock("@/core/network/auth", () => ({
31
+ cleanupAuthQueryParams: vi.fn(),
32
+ }));
33
+
34
+ vi.mock("@/utils/vitals", () => ({
35
+ reportVitals: vi.fn(),
36
+ }));
37
+
38
+ // Mock preloadPage
39
+ vi.mock("@/core/MarimoApp", () => ({
40
+ MarimoApp: () => null,
41
+ preloadPage: vi.fn(),
42
+ }));
43
+
44
+ describe("mount", () => {
45
+ const mockElement = document.createElement("div");
46
+
47
+ beforeEach(() => {
48
+ visibleForTesting.reset();
49
+ // Reset connection atom to initial state
50
+ store.set(connectionAtom, { state: WebSocketState.NOT_STARTED });
51
+ });
52
+
53
+ afterEach(() => {
54
+ vi.clearAllMocks();
55
+ });
56
+
57
+ const baseOptions = {
58
+ filename: "test.py",
59
+ code: "",
60
+ version: "0.0.1",
61
+ mode: "edit" as const,
62
+ config: {},
63
+ configOverrides: {},
64
+ appConfig: {},
65
+ view: { showAppCode: true },
66
+ serverToken: "",
67
+ };
68
+
69
+ describe("connection state initialization", () => {
70
+ it("should set connection to CONNECTING when runtimeConfig has lazy=false", () => {
71
+ mount(
72
+ {
73
+ ...baseOptions,
74
+ runtimeConfig: [{ url: "http://localhost:8080", lazy: false }],
75
+ },
76
+ mockElement,
77
+ );
78
+
79
+ const connection = store.get(connectionAtom);
80
+ expect(connection.state).toBe(WebSocketState.CONNECTING);
81
+ });
82
+
83
+ it("should keep connection as NOT_STARTED when runtimeConfig has lazy=true", () => {
84
+ mount(
85
+ {
86
+ ...baseOptions,
87
+ runtimeConfig: [{ url: "http://localhost:8080", lazy: true }],
88
+ },
89
+ mockElement,
90
+ );
91
+
92
+ const connection = store.get(connectionAtom);
93
+ expect(connection.state).toBe(WebSocketState.NOT_STARTED);
94
+ });
95
+
96
+ it("should keep connection as NOT_STARTED when no runtimeConfig is provided", () => {
97
+ mount(
98
+ {
99
+ ...baseOptions,
100
+ runtimeConfig: [],
101
+ },
102
+ mockElement,
103
+ );
104
+
105
+ const connection = store.get(connectionAtom);
106
+ expect(connection.state).toBe(WebSocketState.NOT_STARTED);
107
+ });
108
+
109
+ it("should keep connection as NOT_STARTED for static notebooks even with lazy=false", async () => {
110
+ const { isStaticNotebook } = await import("@/core/static/static-state");
111
+ vi.mocked(isStaticNotebook).mockReturnValue(true);
112
+
113
+ // Reset mount state to allow another mount
114
+ visibleForTesting.reset();
115
+
116
+ mount(
117
+ {
118
+ ...baseOptions,
119
+ runtimeConfig: [{ url: "http://localhost:8080", lazy: false }],
120
+ },
121
+ mockElement,
122
+ );
123
+
124
+ const connection = store.get(connectionAtom);
125
+ expect(connection.state).toBe(WebSocketState.NOT_STARTED);
126
+ });
127
+ });
128
+ });
@@ -1,4 +1,4 @@
1
- /* Copyright 2024 Marimo. All rights reserved. */
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
2
 
3
3
  import { describe, expect, test } from "vitest";
4
4
  import { getDirtyValues } from "../user-config-form";
@@ -69,7 +69,7 @@ export function getDirtyValues<T extends FieldValues>(
69
69
  dirtyFields: Partial<Record<keyof T, unknown>>,
70
70
  ): Partial<T> {
71
71
  const result: Partial<T> = {};
72
- for (const key of Object.keys(dirtyFields) as Array<keyof T>) {
72
+ for (const key of Object.keys(dirtyFields) as (keyof T)[]) {
73
73
  const dirty = dirtyFields[key];
74
74
  if (dirty === true) {
75
75
  result[key] = values[key];
@@ -677,7 +677,7 @@ const AgentPanel: React.FC = () => {
677
677
  ? getAgentWebSocketUrl(selectedTab.agentId)
678
678
  : NO_WS_SET;
679
679
  const { sendUpdateFile, sendFileDetails } = useRequestClient();
680
- const isCreatingNewSession = useRef(false);
680
+ const creatingOrResumingSession = useRef(false);
681
681
 
682
682
  const acpClient = useAcpClient({
683
683
  wsUrl,
@@ -716,6 +716,7 @@ const AgentPanel: React.FC = () => {
716
716
  sessionMode,
717
717
  activeSessionId,
718
718
  agent,
719
+ clearNotifications,
719
720
  } = acpClient;
720
721
 
721
722
  useEffect(() => {
@@ -757,40 +758,42 @@ const AgentPanel: React.FC = () => {
757
758
  return;
758
759
  }
759
760
 
760
- // If there is an active session, we should stop it
761
- if (activeSessionId) {
762
- setActiveSessionId(null);
763
- await agent.cancel({ sessionId: activeSessionId }).catch((error) => {
764
- logger.error("Failed to cancel active session", { error });
765
- });
766
- }
761
+ creatingOrResumingSession.current = true;
762
+
763
+ try {
764
+ // If there is an active session, we should stop it
765
+ if (activeSessionId) {
766
+ await agent.cancel({ sessionId: activeSessionId }).catch((error) => {
767
+ logger.error("Failed to cancel active session", { error });
768
+ });
769
+ clearNotifications(activeSessionId);
770
+ setActiveSessionId(null);
771
+ }
767
772
 
768
- // Get the selected model from the current session state
769
- const currentModel = selectedTab?.selectedModel ?? null;
770
- logger.debug("Creating new agent session", { model: currentModel });
771
- isCreatingNewSession.current = true;
772
- const newSession = await agent
773
- .newSession({
773
+ // Get the selected model from the current session state
774
+ const currentModel = selectedTab?.selectedModel ?? null;
775
+ logger.debug("Creating new agent session", { model: currentModel });
776
+ const newSession = await agent.newSession({
774
777
  cwd: getCwd(),
775
778
  mcpServers: [],
776
779
  _meta: currentModel ? { model: currentModel } : undefined,
777
- })
778
- .finally(() => {
779
- isCreatingNewSession.current = false;
780
780
  });
781
781
 
782
- // Capture models from the response
783
- if (newSession.models) {
784
- logger.debug("Session models received", { models: newSession.models });
785
- setSessionModels(newSession.models);
786
- }
782
+ // Capture models from the response
783
+ if (newSession.models) {
784
+ logger.debug("Session models received", { models: newSession.models });
785
+ setSessionModels(newSession.models);
786
+ }
787
787
 
788
- setSessionState((prev) =>
789
- updateSessionExternalAgentSessionId(
790
- prev,
791
- newSession.sessionId as ExternalAgentSessionId,
792
- ),
793
- );
788
+ setSessionState((prev) =>
789
+ updateSessionExternalAgentSessionId(
790
+ prev,
791
+ newSession.sessionId as ExternalAgentSessionId,
792
+ ),
793
+ );
794
+ } finally {
795
+ creatingOrResumingSession.current = false;
796
+ }
794
797
  });
795
798
 
796
799
  const handleResumeSession = useEvent(
@@ -804,23 +807,28 @@ const AgentPanel: React.FC = () => {
804
807
  if (!agent.loadSession) {
805
808
  throw new Error("Agent does not support loading sessions");
806
809
  }
807
- const loadedSession = await agent.loadSession({
808
- sessionId: previousSessionId,
809
- cwd: getCwd(),
810
- mcpServers: [],
811
- });
812
-
813
- // Capture models from the response if available
814
- if (loadedSession?.models) {
815
- logger.debug("Session models received", {
816
- models: loadedSession.models,
810
+ creatingOrResumingSession.current = true;
811
+ try {
812
+ const loadedSession = await agent.loadSession({
813
+ sessionId: previousSessionId,
814
+ cwd: getCwd(),
815
+ mcpServers: [],
817
816
  });
818
- setSessionModels(loadedSession.models);
819
- }
820
817
 
821
- setSessionState((prev) =>
822
- updateSessionExternalAgentSessionId(prev, previousSessionId),
823
- );
818
+ // Capture models from the response if available
819
+ if (loadedSession?.models) {
820
+ logger.debug("Session models received", {
821
+ models: loadedSession.models,
822
+ });
823
+ setSessionModels(loadedSession.models);
824
+ }
825
+
826
+ setSessionState((prev) =>
827
+ updateSessionExternalAgentSessionId(prev, previousSessionId),
828
+ );
829
+ } finally {
830
+ creatingOrResumingSession.current = false;
831
+ }
824
832
  },
825
833
  );
826
834
 
@@ -838,6 +846,11 @@ const AgentPanel: React.FC = () => {
838
846
  return;
839
847
  }
840
848
 
849
+ // Prevent race conditions
850
+ if (creatingOrResumingSession.current) {
851
+ return;
852
+ }
853
+
841
854
  // If there is an available session, resume it, otherwise create a new one
842
855
  const createOrResumeSession = async () => {
843
856
  const availableSession = tabLastActiveSessionId ?? activeSessionId;
@@ -8,7 +8,6 @@ import type {
8
8
  InvokeAiToolRequest,
9
9
  InvokeAiToolResponse,
10
10
  } from "@/core/network/types";
11
- import type { ChatMessage } from "@/plugins/impl/chat/types";
12
11
  import { blobToString } from "@/utils/fileToBase64";
13
12
  import { Logger } from "@/utils/Logger";
14
13
  import { getAICompletionBodyWithAttachments } from "../editor/ai/completion-utils";
@@ -68,7 +67,6 @@ function stringifyTextParts(parts: UIMessage["parts"]): string {
68
67
  export async function buildCompletionRequestBody(
69
68
  messages: UIMessage[],
70
69
  ): Promise<{
71
- messages: ChatMessage[]; // Deprecated. TODO: Remove in the future
72
70
  uiMessages: UIMessage[];
73
71
  context?: (null | components["schemas"]["AiCompletionContext"]) | undefined;
74
72
  includeOtherCode: string;
@@ -91,25 +89,8 @@ export async function buildCompletionRequestBody(
91
89
  };
92
90
  }
93
91
 
94
- function toChatMessage(message: UIMessage, isLast: boolean): ChatMessage {
95
- // Clone parts to avoid mutating the original message
96
- const parts = [...message.parts];
97
- if (isLast) {
98
- parts.push(...completionBody.attachments);
99
- }
100
- return {
101
- id: message.id,
102
- role: message.role,
103
- content: stringifyTextParts(message.parts), // This is no longer used in the backend
104
- parts,
105
- };
106
- }
107
-
108
92
  return {
109
93
  ...completionBody.body,
110
- messages: messages.map((m, idx) =>
111
- toChatMessage(m, idx === messages.length - 1),
112
- ),
113
94
  uiMessages: messages.map((m, idx) =>
114
95
  addAttachmentsToMessage(m, idx === messages.length - 1),
115
96
  ),
@@ -593,7 +593,7 @@ const PopoverFilterByValues = <TData, TValue>({
593
593
  <>
594
594
  <Command className="text-sm outline-hidden" shouldFilter={false}>
595
595
  <CommandInput
596
- placeholder="Search"
596
+ placeholder={`Search among the top ${data.length} values`}
597
597
  autoFocus={true}
598
598
  onValueChange={(value) => setQuery(value.trim())}
599
599
  />
@@ -0,0 +1,101 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+
3
+ import { useAtom } from "jotai";
4
+ import { CopyIcon, HomeIcon, XCircleIcon } from "lucide-react";
5
+ import { kernelStartupErrorAtom } from "@/core/errors/state";
6
+ import {
7
+ AlertDialog,
8
+ AlertDialogAction,
9
+ AlertDialogContent,
10
+ AlertDialogDescription,
11
+ AlertDialogFooter,
12
+ AlertDialogHeader,
13
+ AlertDialogTitle,
14
+ } from "../ui/alert-dialog";
15
+ import { Button } from "../ui/button";
16
+ import { toast } from "../ui/use-toast";
17
+
18
+ /**
19
+ * Modal that displays kernel startup errors.
20
+ * Shows when the kernel fails to start in sandbox mode,
21
+ * displaying the stderr output so users can diagnose the issue.
22
+ */
23
+ export const KernelStartupErrorModal: React.FC = () => {
24
+ const [error, setError] = useAtom(kernelStartupErrorAtom);
25
+
26
+ if (error === null) {
27
+ return null;
28
+ }
29
+
30
+ const handleCopy = async () => {
31
+ try {
32
+ await navigator.clipboard.writeText(error);
33
+ toast({
34
+ title: "Copied to clipboard",
35
+ description: "Error details have been copied to your clipboard.",
36
+ });
37
+ } catch {
38
+ toast({
39
+ title: "Failed to copy",
40
+ description: "Could not copy to clipboard.",
41
+ variant: "danger",
42
+ });
43
+ }
44
+ };
45
+
46
+ const handleClose = () => {
47
+ setError(null);
48
+ };
49
+
50
+ const handleReturnHome = () => {
51
+ const withoutSearch = document.baseURI.split("?")[0];
52
+ window.open(withoutSearch, "_self");
53
+ };
54
+
55
+ return (
56
+ <AlertDialog open={true} onOpenChange={(open) => !open && handleClose()}>
57
+ <AlertDialogContent className="max-w-2xl">
58
+ <AlertDialogHeader>
59
+ <AlertDialogTitle className="flex items-center gap-2 text-destructive">
60
+ <XCircleIcon className="h-5 w-5" />
61
+ Kernel Startup Failed
62
+ </AlertDialogTitle>
63
+ <AlertDialogDescription>
64
+ The kernel failed to start. This usually happens when the package
65
+ manager can't install your notebook's dependencies.
66
+ </AlertDialogDescription>
67
+ </AlertDialogHeader>
68
+ <div className="my-4">
69
+ <div className="flex items-center justify-between mb-2">
70
+ <span className="text-sm font-medium text-muted-foreground">
71
+ Error Details
72
+ </span>
73
+ <Button
74
+ variant="outline"
75
+ size="xs"
76
+ onClick={handleCopy}
77
+ className="flex items-center gap-1"
78
+ >
79
+ <CopyIcon className="h-3 w-3" />
80
+ Copy
81
+ </Button>
82
+ </div>
83
+ <pre className="bg-muted p-4 rounded-md text-sm font-mono overflow-auto max-h-80 whitespace-pre-wrap break-words">
84
+ {error}
85
+ </pre>
86
+ </div>
87
+ <AlertDialogFooter>
88
+ <Button
89
+ variant="outline"
90
+ onClick={handleReturnHome}
91
+ className="flex items-center gap-2"
92
+ >
93
+ <HomeIcon className="h-4 w-4" />
94
+ Return to Home
95
+ </Button>
96
+ <AlertDialogAction onClick={handleClose}>Dismiss</AlertDialogAction>
97
+ </AlertDialogFooter>
98
+ </AlertDialogContent>
99
+ </AlertDialog>
100
+ );
101
+ };
@@ -93,11 +93,17 @@ export const NameCellContentEditable: React.FC<{
93
93
  onChange={inputProps.onChange}
94
94
  onBlur={inputProps.onBlur}
95
95
  onFocus={inputProps.onFocus}
96
- onKeyDown={Events.onEnter((e) => {
97
- if (e.target instanceof HTMLElement) {
98
- e.target.blur();
96
+ onKeyDown={(e) => {
97
+ // Prevent all key presses from triggering hotkeys
98
+ e.stopPropagation();
99
+
100
+ // On Enter, blur the input to commit the change
101
+ if (e.key === "Enter") {
102
+ if (e.target instanceof HTMLElement) {
103
+ e.target.blur();
104
+ }
99
105
  }
100
- })}
106
+ }}
101
107
  >
102
108
  {value}
103
109
  </span>
@@ -124,7 +124,7 @@ export const AcceptCompletionButton: React.FC<{
124
124
  size={size}
125
125
  disabled={isLoading}
126
126
  onClick={handleAcceptAndRun}
127
- className={`${baseClasses} rounded-l-none px-1.5 ${borderless && "border-0 border-l-1"} ${playButtonStyles}`}
127
+ className={`${baseClasses} rounded-l-none px-1.5 ${borderless && "border-0 border-l"} ${playButtonStyles}`}
128
128
  >
129
129
  <PlayIcon className="h-2.5 w-2.5" />
130
130
  </Button>
@@ -2,6 +2,7 @@
2
2
 
3
3
  import { useAtomValue } from "jotai";
4
4
  import { LoadingEllipsis } from "@/components/icons/loading-ellipsis";
5
+ import { Spinner } from "@/components/icons/spinner";
5
6
  import { Button } from "@/components/ui/button";
6
7
  import { DelayMount } from "@/components/utils/delay-mount";
7
8
  import {
@@ -10,24 +11,50 @@ import {
10
11
  isNotStartedAtom,
11
12
  } from "@/core/network/connection";
12
13
  import { useConnectToRuntime } from "@/core/runtime/config";
14
+ import { Banner } from "@/plugins/impl/common/error-banner";
13
15
  import { Tooltip } from "../../ui/tooltip";
14
16
  import { FloatingAlert } from "./floating-alert";
15
17
 
16
18
  const SHORT_DELAY_MS = 1000; // 1 second
19
+ const LONG_DELAY_MS = 5000; // 5 seconds
17
20
 
18
21
  export const ConnectingAlert: React.FC = () => {
19
22
  const isConnecting = useAtomValue(isConnectingAtom);
20
23
  const isClosed = useAtomValue(isClosedAtom);
21
24
 
22
25
  if (isConnecting) {
26
+ const subtleNotification = (
27
+ <Tooltip content="Connecting to a marimo runtime">
28
+ <div className="flex items-center">
29
+ <LoadingEllipsis size={5} className="text-yellow-500" />
30
+ </div>
31
+ </Tooltip>
32
+ );
33
+
34
+ const longNotification = (
35
+ <Banner
36
+ kind="info"
37
+ className="flex flex-col rounded py-2 px-4 animate-in slide-in-from-top w-fit"
38
+ >
39
+ <div className="flex flex-col gap-4 justify-between items-start text-muted-foreground text-base">
40
+ <div className="flex items-center gap-2">
41
+ <Spinner className="h-4 w-4" />
42
+ <p>Connecting to a marimo runtime ...</p>
43
+ </div>
44
+ </div>
45
+ </Banner>
46
+ );
47
+
48
+ // This waits for 1 second to show the subtle notification, then shows the long notification after 5 seconds.
23
49
  return (
24
50
  <DelayMount milliseconds={SHORT_DELAY_MS}>
25
- <div className="absolute top-4 m-0 flex items-center min-h-[28px] left-1/2 transform -translate-x-1/2 z-200 ">
26
- <Tooltip content="Connecting to a marimo runtime">
27
- <div className="flex items-center">
28
- <LoadingEllipsis size={5} className="text-yellow-500" />
29
- </div>
30
- </Tooltip>
51
+ <div className="m-0 flex items-center min-h-[28px] fixed top-5 left-1/2 transform -translate-x-1/2 z-200">
52
+ <DelayMount
53
+ milliseconds={LONG_DELAY_MS}
54
+ fallback={subtleNotification}
55
+ >
56
+ {longNotification}
57
+ </DelayMount>
31
58
  </div>
32
59
  </DelayMount>
33
60
  );
@@ -194,10 +194,8 @@ export function isPanelHidden(
194
194
  if (panel.hidden) {
195
195
  return true;
196
196
  }
197
- if (panel.requiredCapability) {
198
- if (!capabilities[panel.requiredCapability]) {
199
- return true;
200
- }
197
+ if (panel.requiredCapability && !capabilities[panel.requiredCapability]) {
198
+ return true;
201
199
  }
202
200
  return false;
203
201
  }