@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
@@ -20,7 +20,7 @@ import { XIcon } from "lucide-react";
20
20
  import { Button } from "@/components/ui/button";
21
21
  import { ReorderableList } from "@/components/ui/reorderable-list";
22
22
  import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
23
- import { LazyMount } from "@/components/utils/lazy-mount";
23
+ import { LazyActivity } from "@/components/utils/lazy-mount";
24
24
  import { cellErrorCount } from "@/core/cells/cells";
25
25
  import { capabilitiesAtom } from "@/core/config/capabilities";
26
26
  import { getFeatureFlag } from "@/core/config/feature-flag";
@@ -34,6 +34,7 @@ import {
34
34
  PANEL_MAP,
35
35
  PANELS,
36
36
  type PanelDescriptor,
37
+ type PanelType,
37
38
  } from "../types";
38
39
  import { BackendConnectionStatus } from "./footer-items/backend-status";
39
40
  import { PanelsWrapper } from "./panels";
@@ -253,6 +254,29 @@ export const AppChrome: React.FC<PropsWithChildren> = ({ children }) => {
253
254
  return <LazyChatPanel />;
254
255
  };
255
256
 
257
+ const SIDEBAR_PANELS: Record<PanelType, React.ReactNode> = {
258
+ files: <LazyFileExplorerPanel />,
259
+ variables: <LazySessionPanel />,
260
+ dependencies: <LazyDependencyGraphPanel />,
261
+ packages: <LazyPackagesPanel />,
262
+ outline: <LazyOutlinePanel />,
263
+ documentation: <LazyDocumentationPanel />,
264
+ snippets: <LazySnippetsPanel />,
265
+ ai: renderAiPanel(),
266
+ errors: <LazyErrorsPanel />,
267
+ scratchpad: <LazyScratchpadPanel />,
268
+ tracing: <LazyTracingPanel />,
269
+ secrets: <LazySecretsPanel />,
270
+ logs: <LazyLogsPanel />,
271
+ terminal: (
272
+ <LazyTerminal
273
+ visible={isSidebarOpen && selectedPanel === "terminal"}
274
+ onClose={() => setIsSidebarOpen(false)}
275
+ />
276
+ ),
277
+ cache: <LazyCachePanel />,
278
+ };
279
+
256
280
  const helpPaneBody = (
257
281
  <ErrorBoundary>
258
282
  <PanelSectionProvider value="sidebar">
@@ -328,26 +352,14 @@ export const AppChrome: React.FC<PropsWithChildren> = ({ children }) => {
328
352
  </div>
329
353
  <Suspense>
330
354
  <TooltipProvider>
331
- {selectedPanel === "files" && <LazyFileExplorerPanel />}
332
- {selectedPanel === "variables" && <LazySessionPanel />}
333
- {selectedPanel === "dependencies" && <LazyDependencyGraphPanel />}
334
- {selectedPanel === "packages" && <LazyPackagesPanel />}
335
- {selectedPanel === "outline" && <LazyOutlinePanel />}
336
- {selectedPanel === "documentation" && <LazyDocumentationPanel />}
337
- {selectedPanel === "snippets" && <LazySnippetsPanel />}
338
- {selectedPanel === "ai" && renderAiPanel()}
339
- {selectedPanel === "errors" && <LazyErrorsPanel />}
340
- {selectedPanel === "scratchpad" && <LazyScratchpadPanel />}
341
- {selectedPanel === "tracing" && <LazyTracingPanel />}
342
- {selectedPanel === "secrets" && <LazySecretsPanel />}
343
- {selectedPanel === "logs" && <LazyLogsPanel />}
344
- {selectedPanel === "terminal" && (
345
- <LazyTerminal
346
- visible={isSidebarOpen}
347
- onClose={() => setIsSidebarOpen(false)}
348
- />
349
- )}
350
- {selectedPanel === "cache" && <LazyCachePanel />}
355
+ {Object.entries(SIDEBAR_PANELS).map(([key, Panel]) => (
356
+ <LazyActivity
357
+ key={key}
358
+ mode={selectedPanel === key ? "visible" : "hidden"}
359
+ >
360
+ {Panel}
361
+ </LazyActivity>
362
+ ))}
351
363
  </TooltipProvider>
352
364
  </Suspense>
353
365
  </div>
@@ -388,6 +400,18 @@ export const AppChrome: React.FC<PropsWithChildren> = ({ children }) => {
388
400
  </Panel>
389
401
  );
390
402
 
403
+ const DEVELOPER_PANELS: Record<PanelType, React.ReactNode> = {
404
+ ...SIDEBAR_PANELS,
405
+ terminal: (
406
+ <LazyTerminal
407
+ visible={
408
+ isDeveloperPanelOpen && selectedDeveloperPanelTab === "terminal"
409
+ }
410
+ onClose={() => setIsDeveloperPanelOpen(false)}
411
+ />
412
+ ),
413
+ };
414
+
391
415
  const bottomPanel = (
392
416
  <Panel
393
417
  ref={developerPanelRef}
@@ -475,43 +499,16 @@ export const AppChrome: React.FC<PropsWithChildren> = ({ children }) => {
475
499
  <Suspense fallback={<div />}>
476
500
  <PanelSectionProvider value="developer-panel">
477
501
  <div className="flex-1 overflow-hidden">
478
- {selectedDeveloperPanelTab === "files" && (
479
- <LazyFileExplorerPanel />
480
- )}
481
- {selectedDeveloperPanelTab === "variables" && (
482
- <LazySessionPanel />
483
- )}
484
- {selectedDeveloperPanelTab === "dependencies" && (
485
- <LazyDependencyGraphPanel />
486
- )}
487
- {selectedDeveloperPanelTab === "packages" && (
488
- <LazyPackagesPanel />
489
- )}
490
- {selectedDeveloperPanelTab === "outline" && <LazyOutlinePanel />}
491
- {selectedDeveloperPanelTab === "documentation" && (
492
- <LazyDocumentationPanel />
493
- )}
494
- {selectedDeveloperPanelTab === "snippets" && (
495
- <LazySnippetsPanel />
496
- )}
497
- {selectedDeveloperPanelTab === "ai" && renderAiPanel()}
498
- {selectedDeveloperPanelTab === "errors" && <LazyErrorsPanel />}
499
- {selectedDeveloperPanelTab === "scratchpad" && (
500
- <LazyScratchpadPanel />
501
- )}
502
- {selectedDeveloperPanelTab === "tracing" && <LazyTracingPanel />}
503
- {selectedDeveloperPanelTab === "secrets" && <LazySecretsPanel />}
504
- {selectedDeveloperPanelTab === "logs" && <LazyLogsPanel />}
505
- {/* LazyMount needed for Terminal to avoid spurious connection */}
506
- {selectedDeveloperPanelTab === "terminal" && (
507
- <LazyMount isOpen={isDeveloperPanelOpen}>
508
- <LazyTerminal
509
- visible={isDeveloperPanelOpen}
510
- onClose={() => setIsDeveloperPanelOpen(false)}
511
- />
512
- </LazyMount>
513
- )}
514
- {selectedDeveloperPanelTab === "cache" && <LazyCachePanel />}
502
+ {Object.entries(DEVELOPER_PANELS).map(([key, Panel]) => (
503
+ <LazyActivity
504
+ key={key}
505
+ mode={
506
+ selectedDeveloperPanelTab === key ? "visible" : "hidden"
507
+ }
508
+ >
509
+ {Panel}
510
+ </LazyActivity>
511
+ ))}
515
512
  </div>
516
513
  </PanelSectionProvider>
517
514
  </Suspense>
@@ -2,6 +2,8 @@
2
2
 
3
3
  import {
4
4
  ChevronDownIcon,
5
+ ExternalLinkIcon,
6
+ InfoIcon,
5
7
  PowerOffIcon,
6
8
  ZapIcon,
7
9
  ZapOffIcon,
@@ -15,7 +17,9 @@ import {
15
17
  DropdownMenuSeparator,
16
18
  DropdownMenuTrigger,
17
19
  } from "@/components/ui/dropdown-menu";
20
+ import { ExternalLink } from "@/components/ui/links";
18
21
  import { Switch } from "@/components/ui/switch";
22
+ import { Tooltip, TooltipProvider } from "@/components/ui/tooltip";
19
23
  import { useResolvedMarimoConfig } from "@/core/config/config";
20
24
  import { useRequestClient } from "@/core/network/requests";
21
25
  import { isWasm } from "@/core/wasm/utils";
@@ -99,117 +103,167 @@ export const RuntimeSettings: React.FC<RuntimeSettingsProps> = ({
99
103
  </FooterItem>
100
104
  </DropdownMenuTrigger>
101
105
  <DropdownMenuContent align="start" className="w-64">
102
- <DropdownMenuLabel>Runtime reactivity</DropdownMenuLabel>
106
+ <DropdownMenuLabel>
107
+ <div className="flex items-center justify-between w-full">
108
+ <span>Runtime reactivity</span>
109
+ <ExternalLink href="https://links.marimo.app/runtime-configuration">
110
+ <span className="text-xs font-normal flex items-center gap-1">
111
+ Docs
112
+ <ExternalLinkIcon className="w-3 h-3" />
113
+ </span>
114
+ </ExternalLink>
115
+ </div>
116
+ </DropdownMenuLabel>
103
117
  <DropdownMenuSeparator />
104
118
 
105
- {/* On startup toggle */}
106
- <DisableIfOverridden name="runtime.auto_instantiate">
107
- <div className="flex items-center justify-between px-2 py-2">
108
- <div className="flex items-center space-x-2">
109
- {config.runtime.auto_instantiate ? (
110
- <ZapIcon size={14} className="text-amber-500" />
111
- ) : (
112
- <ZapOffIcon size={14} className="text-muted-foreground" />
113
- )}
114
- <div>
115
- <div className="text-sm font-medium">On startup</div>
116
- <div className="text-xs text-muted-foreground">
117
- {config.runtime.auto_instantiate ? "autorun" : "lazy"}
119
+ <TooltipProvider>
120
+ {/* On startup toggle */}
121
+ <DisableIfOverridden name="runtime.auto_instantiate">
122
+ <div className="flex items-center justify-between px-2 py-2">
123
+ <div className="flex items-center space-x-2">
124
+ {config.runtime.auto_instantiate ? (
125
+ <ZapIcon size={14} className="text-amber-500" />
126
+ ) : (
127
+ <ZapOffIcon size={14} className="text-muted-foreground" />
128
+ )}
129
+ <div>
130
+ <div className="text-sm font-medium flex items-center gap-1">
131
+ On startup
132
+ <Tooltip
133
+ content={
134
+ <div className="max-w-[200px]">
135
+ Whether to automatically run the notebook on startup
136
+ </div>
137
+ }
138
+ >
139
+ <InfoIcon className="w-3 h-3" />
140
+ </Tooltip>
141
+ </div>
142
+ <div className="text-xs text-muted-foreground">
143
+ {config.runtime.auto_instantiate ? "autorun" : "lazy"}
144
+ </div>
118
145
  </div>
119
146
  </div>
147
+ <Switch
148
+ checked={config.runtime.auto_instantiate}
149
+ onCheckedChange={handleStartupToggle}
150
+ size="sm"
151
+ />
120
152
  </div>
121
- <Switch
122
- checked={config.runtime.auto_instantiate}
123
- onCheckedChange={handleStartupToggle}
124
- size="sm"
125
- />
126
- </div>
127
- </DisableIfOverridden>
153
+ </DisableIfOverridden>
128
154
 
129
- <DropdownMenuSeparator />
155
+ <DropdownMenuSeparator />
130
156
 
131
- {/* On cell change toggle */}
132
- <DisableIfOverridden name="runtime.on_cell_change">
133
- <div className="flex items-center justify-between px-2 py-2">
134
- <div className="flex items-center space-x-2">
135
- {config.runtime.on_cell_change === "autorun" ? (
136
- <ZapIcon size={14} className="text-amber-500" />
137
- ) : (
138
- <ZapOffIcon size={14} className="text-muted-foreground" />
139
- )}
140
- <div>
141
- <div className="text-sm font-medium">On cell change</div>
142
- <div className="text-xs text-muted-foreground">
143
- {config.runtime.on_cell_change}
157
+ {/* On cell change toggle */}
158
+ <DisableIfOverridden name="runtime.on_cell_change">
159
+ <div className="flex items-center justify-between px-2 py-2">
160
+ <div className="flex items-center space-x-2">
161
+ {config.runtime.on_cell_change === "autorun" ? (
162
+ <ZapIcon size={14} className="text-amber-500" />
163
+ ) : (
164
+ <ZapOffIcon size={14} className="text-muted-foreground" />
165
+ )}
166
+ <div>
167
+ <div className="text-sm font-medium flex items-center gap-1">
168
+ On cell change
169
+ <Tooltip
170
+ content={
171
+ <div className="max-w-[300px]">
172
+ Whether to automatically run dependent cells after
173
+ running a cell
174
+ </div>
175
+ }
176
+ >
177
+ <InfoIcon className="w-3 h-3" />
178
+ </Tooltip>
179
+ </div>
180
+ <div className="text-xs text-muted-foreground">
181
+ {config.runtime.on_cell_change}
182
+ </div>
144
183
  </div>
145
184
  </div>
185
+ <Switch
186
+ checked={config.runtime.on_cell_change === "autorun"}
187
+ onCheckedChange={handleCellChangeToggle}
188
+ size="sm"
189
+ />
146
190
  </div>
147
- <Switch
148
- checked={config.runtime.on_cell_change === "autorun"}
149
- onCheckedChange={handleCellChangeToggle}
150
- size="sm"
151
- />
152
- </div>
153
- </DisableIfOverridden>
191
+ </DisableIfOverridden>
154
192
 
155
- {!isWasm() && (
156
- <>
157
- <DropdownMenuSeparator />
193
+ {!isWasm() && (
194
+ <>
195
+ <DropdownMenuSeparator />
158
196
 
159
- {/* On module change dropdown */}
160
- <DisableIfOverridden name="runtime.auto_reload">
161
- <div className="px-2 py-1">
162
- <div className="flex items-center space-x-2 mb-2">
163
- {config.runtime.auto_reload === "off" && (
164
- <PowerOffIcon size={14} className="text-muted-foreground" />
165
- )}
166
- {config.runtime.auto_reload === "lazy" && (
167
- <ZapOffIcon size={14} className="text-muted-foreground" />
168
- )}
169
- {config.runtime.auto_reload === "autorun" && (
170
- <ZapIcon size={14} className="text-amber-500" />
171
- )}
172
- <div>
173
- <div className="text-sm font-medium">On module change</div>
174
- <div className="text-xs text-muted-foreground">
175
- {config.runtime.auto_reload}
197
+ {/* On module change dropdown */}
198
+ <DisableIfOverridden name="runtime.auto_reload">
199
+ <div className="px-2 py-1">
200
+ <div className="flex items-center space-x-2 mb-2">
201
+ {config.runtime.auto_reload === "off" && (
202
+ <PowerOffIcon
203
+ size={14}
204
+ className="text-muted-foreground"
205
+ />
206
+ )}
207
+ {config.runtime.auto_reload === "lazy" && (
208
+ <ZapOffIcon size={14} className="text-muted-foreground" />
209
+ )}
210
+ {config.runtime.auto_reload === "autorun" && (
211
+ <ZapIcon size={14} className="text-amber-500" />
212
+ )}
213
+ <div>
214
+ <div className="text-sm font-medium flex items-center gap-1">
215
+ On module change
216
+ <Tooltip
217
+ content={
218
+ <div className="max-w-[300px]">
219
+ Whether to run affected cells, mark them as stale,
220
+ or do nothing when an external module is updated
221
+ </div>
222
+ }
223
+ >
224
+ <InfoIcon className="w-3 h-3" />
225
+ </Tooltip>
226
+ </div>
227
+ <div className="text-xs text-muted-foreground">
228
+ {config.runtime.auto_reload}
229
+ </div>
176
230
  </div>
177
231
  </div>
232
+ <div className="space-y-1">
233
+ {["off", "lazy", "autorun"].map((option) => (
234
+ <button
235
+ key={option}
236
+ onClick={() =>
237
+ handleModuleReloadChange(
238
+ option as "off" | "lazy" | "autorun",
239
+ )
240
+ }
241
+ className={cn(
242
+ "w-full flex items-center px-2 py-1 text-sm rounded hover:bg-accent",
243
+ option === config.runtime.auto_reload && "bg-accent",
244
+ )}
245
+ >
246
+ {option === "off" && (
247
+ <PowerOffIcon size={12} className="mr-2" />
248
+ )}
249
+ {option === "lazy" && (
250
+ <ZapOffIcon size={12} className="mr-2" />
251
+ )}
252
+ {option === "autorun" && (
253
+ <ZapIcon size={12} className="mr-2" />
254
+ )}
255
+ <span className="capitalize">{option}</span>
256
+ {option === config.runtime.auto_reload && (
257
+ <span className="ml-auto">✓</span>
258
+ )}
259
+ </button>
260
+ ))}
261
+ </div>
178
262
  </div>
179
- <div className="space-y-1">
180
- {["off", "lazy", "autorun"].map((option) => (
181
- <button
182
- key={option}
183
- onClick={() =>
184
- handleModuleReloadChange(
185
- option as "off" | "lazy" | "autorun",
186
- )
187
- }
188
- className={cn(
189
- "w-full flex items-center px-2 py-1 text-sm rounded hover:bg-accent",
190
- option === config.runtime.auto_reload && "bg-accent",
191
- )}
192
- >
193
- {option === "off" && (
194
- <PowerOffIcon size={12} className="mr-2" />
195
- )}
196
- {option === "lazy" && (
197
- <ZapOffIcon size={12} className="mr-2" />
198
- )}
199
- {option === "autorun" && (
200
- <ZapIcon size={12} className="mr-2" />
201
- )}
202
- <span className="capitalize">{option}</span>
203
- {option === config.runtime.auto_reload && (
204
- <span className="ml-auto">✓</span>
205
- )}
206
- </button>
207
- ))}
208
- </div>
209
- </div>
210
- </DisableIfOverridden>
211
- </>
212
- )}
263
+ </DisableIfOverridden>
264
+ </>
265
+ )}
266
+ </TooltipProvider>
213
267
  </DropdownMenuContent>
214
268
  </DropdownMenu>
215
269
  );
@@ -12,6 +12,8 @@ describe("useFocusFirstEditor", () => {
12
12
  cb(0);
13
13
  return 0;
14
14
  });
15
+ // Mock document.hasFocus() to return true so focus logic runs
16
+ vi.spyOn(document, "hasFocus").mockReturnValue(true);
15
17
  });
16
18
 
17
19
  afterEach(() => {
@@ -120,4 +122,29 @@ describe("useFocusFirstEditor", () => {
120
122
  writable: true,
121
123
  });
122
124
  });
125
+
126
+ it("should not focus when document does not have focus", () => {
127
+ // Mock document.hasFocus() to return false (e.g., when embedded in iframe)
128
+ vi.spyOn(document, "hasFocus").mockReturnValue(false);
129
+
130
+ const mockEditor = { focus: vi.fn() };
131
+ vi.spyOn(cellsModule, "getNotebook").mockReturnValue({
132
+ cellIds: { iterateTopLevelIds: ["cell-1"] },
133
+ cellData: {
134
+ "cell-1": { config: { hide_code: false } },
135
+ },
136
+ cellHandles: {
137
+ "cell-1": { current: { editorView: mockEditor } },
138
+ },
139
+ } as unknown as cellsModule.NotebookState);
140
+
141
+ renderHook(() => useFocusFirstEditor());
142
+
143
+ act(() => {
144
+ vi.advanceTimersByTime(100);
145
+ });
146
+
147
+ // Focus should NOT be called when document doesn't have focus
148
+ expect(mockEditor.focus).not.toHaveBeenCalled();
149
+ });
123
150
  });
@@ -19,6 +19,12 @@ export function useFocusFirstEditor() {
19
19
  const timeout = setTimeout(() => {
20
20
  // Let the DOM render
21
21
  requestAnimationFrame(() => {
22
+ // Skip auto-focus if the document doesn't have focus to avoid
23
+ // stealing focus from outside (e.g., when embedded in an iframe)
24
+ if (!document.hasFocus()) {
25
+ return;
26
+ }
27
+
22
28
  // Check if the URL contains a scrollTo parameter
23
29
  const hash = window.location.hash;
24
30
  const cellName = extractCellNameFromHash(hash);
@@ -1,5 +1,9 @@
1
1
  /* Copyright 2026 Marimo. All rights reserved. */
2
- import React, { type PropsWithChildren } from "react";
2
+ import React, {
3
+ Activity,
4
+ type ActivityProps,
5
+ type PropsWithChildren,
6
+ } from "react";
3
7
 
4
8
  interface Props {
5
9
  isOpen: boolean;
@@ -12,13 +16,30 @@ export const LazyMount: React.FC<PropsWithChildren<Props>> = ({
12
16
  isOpen,
13
17
  children,
14
18
  }) => {
15
- const [isMounted, setIsMounted] = React.useState(false);
19
+ const [hasMountedBefore, setHasMountedBefore] = React.useState(false);
16
20
 
17
- React.useEffect(() => {
18
- if (isOpen && !isMounted) {
19
- setIsMounted(true);
20
- }
21
- }, [isOpen, isMounted]);
21
+ if (isOpen && !hasMountedBefore) {
22
+ setHasMountedBefore(true);
23
+ }
22
24
 
23
- return isMounted ? children : null;
25
+ return hasMountedBefore || isOpen ? children : null;
26
+ };
27
+
28
+ /**
29
+ * Wraps a component in an Activity component. It is not mounted until it is open for the first time.
30
+ */
31
+ export const LazyActivity: React.FC<PropsWithChildren<ActivityProps>> = (
32
+ props,
33
+ ) => {
34
+ const [hasMountedBefore, setHasMountedBefore] = React.useState(false);
35
+
36
+ if (props.mode === "visible" && !hasMountedBefore) {
37
+ setHasMountedBefore(true);
38
+ }
39
+
40
+ if (hasMountedBefore) {
41
+ return <Activity {...props} />;
42
+ }
43
+
44
+ return null;
24
45
  };
@@ -12,6 +12,7 @@ import { getInitialAppMode } from "@/core/mode";
12
12
  import { CssVariables } from "@/theme/ThemeProvider";
13
13
  import { reactLazyWithPreload } from "@/utils/lazy";
14
14
  import { ErrorBoundary } from "../components/editor/boundary/ErrorBoundary";
15
+ import { KernelStartupErrorModal } from "../components/editor/KernelStartupErrorModal";
15
16
  import { ModalProvider } from "../components/modal/ImperativeModal";
16
17
  import { Toaster } from "../components/ui/toaster";
17
18
  import { TooltipProvider } from "../components/ui/tooltip";
@@ -91,6 +92,7 @@ const Providers = memo(({ children }: PropsWithChildren) => {
91
92
  {children}
92
93
  <Toaster />
93
94
  <TailwindIndicator />
95
+ <KernelStartupErrorModal />
94
96
  </ModalProvider>
95
97
  </LocaleProvider>
96
98
  </SlotzProvider>
@@ -1594,6 +1594,8 @@ const cellDataAtoms = splitAtom(
1594
1594
  );
1595
1595
  export const useCellDataAtoms = () => useAtom(cellDataAtoms);
1596
1596
 
1597
+ export const cellsRuntimeAtom = atom((get) => get(notebookAtom).cellRuntime);
1598
+
1597
1599
  export const notebookIsRunningAtom = atom((get) =>
1598
1600
  notebookIsRunning(get(notebookAtom)),
1599
1601
  );
@@ -53,8 +53,9 @@ export function focusAndScrollCellIntoView({
53
53
  Logger.warn("scrollCellIntoView: editor not found", cellId);
54
54
  return;
55
55
  }
56
- // If already focused, do nothing.
57
- if (editor.hasFocus) {
56
+ // Skip auto-focus if already focused, or if the document doesn't have
57
+ // focus to avoid stealing focus from outside (e.g., when embedded in an iframe)
58
+ if (editor.hasFocus || !document.hasFocus()) {
58
59
  return;
59
60
  }
60
61
 
@@ -64,6 +64,7 @@ import { getCurrentLanguageAdapter } from "./language/commands";
64
64
  import { adaptiveLanguageConfiguration } from "./language/extension";
65
65
  import { dndBundle } from "./misc/dnd";
66
66
  import { pasteBundle } from "./misc/paste";
67
+ import { stringsAutoCloseBraces } from "./misc/string-braces";
67
68
  import { reactiveReferencesBundle } from "./reactive-references/extension";
68
69
  import { darkTheme } from "./theme/dark";
69
70
  import { lightTheme } from "./theme/light";
@@ -203,6 +204,7 @@ export const basicBundle = (opts: CodeMirrorSetupOpts): Extension[] => {
203
204
  hintTooltip(lspConfig),
204
205
  copilotBundle(completionConfig),
205
206
  foldGutter(),
207
+ stringsAutoCloseBraces(),
206
208
  closeBrackets(),
207
209
  completionKeymap(),
208
210
  // to avoid clash with charDeleteBackward keymap