@marimo-team/islands 0.23.10-dev9 → 0.23.10

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 (188) hide show
  1. package/dist/{ConnectedDataExplorerComponent-CyV83R2m.js → ConnectedDataExplorerComponent-Z3RP7Vmm.js} +31 -31
  2. package/dist/{ErrorBoundary-rULOrC_p.js → ErrorBoundary-DpbaKVv7.js} +1 -1
  3. package/dist/{any-language-editor-CiES2a2h.js → any-language-editor-CAgFD4Kd.js} +20 -20
  4. package/dist/assets/__vite-browser-external-eshhtsgZ.js +1 -0
  5. package/dist/assets/worker-CC0Oul9k.js +73 -0
  6. package/dist/{chat-ui-K84W5ecY.js → chat-ui-C4-GMW9y.js} +77 -77
  7. package/dist/{check-DTbrK0zt.js → check-BCaJeT-J.js} +1 -1
  8. package/dist/{chunk-5FQGJX7Z-BNjes6Yx.js → chunk-5FQGJX7Z-BbqSm5gU.js} +1227 -1222
  9. package/dist/{code-block-37QAKDTI-m92Yc8pv.js → code-block-37QAKDTI-O6IH6wAi.js} +1 -1
  10. package/dist/{code-visibility-v6ucEVUg.js → code-visibility-DrFozVlm.js} +1943 -1223
  11. package/dist/{copy-5jQ_kGE1.js → copy-UqRYxiOg.js} +1 -1
  12. package/dist/dist-7QfXoMdB.js +5 -0
  13. package/dist/{dist-DgnE8F-r.js → dist-A2846XWO.js} +1 -1
  14. package/dist/dist-BEXXyZig.js +5 -0
  15. package/dist/{dist-B3pZ0Ab6.js → dist-BR_gyG9L.js} +3 -3
  16. package/dist/{dist-CcXxepx6.js → dist-BSAt6RhH.js} +27 -27
  17. package/dist/{dist-Bde4a2kU.js → dist-BY018Paw.js} +8 -8
  18. package/dist/dist-BYj57OV4.js +5 -0
  19. package/dist/{dist-CUCNs1ja.js → dist-BaoDKvdy.js} +2 -2
  20. package/dist/{dist-Cy1WxgBD.js → dist-Bf7SHuNp.js} +5 -5
  21. package/dist/{dist-Bz_sYWbr.js → dist-Bk75fBZA.js} +2 -2
  22. package/dist/dist-BlSvQzNr.js +5 -0
  23. package/dist/{dist-C5VC_yzu.js → dist-BzEzfugY.js} +1 -1
  24. package/dist/dist-CCBlxAgS.js +8 -0
  25. package/dist/dist-CIDTVIUf.js +5 -0
  26. package/dist/{dist-CLUtPrdy.js → dist-CIYBwstr.js} +1 -1
  27. package/dist/{dist-BotSqB48.js → dist-C_Y3oV3C.js} +12 -12
  28. package/dist/{dist-BTfv03uy.js → dist-CcWX6tmx.js} +2 -2
  29. package/dist/{dist-BhM8gdSO.js → dist-CoXAujgg.js} +4 -4
  30. package/dist/{dist-4j4c7bjm.js → dist-CpxNdDkw.js} +3 -3
  31. package/dist/dist-CqQyhAM8.js +8 -0
  32. package/dist/dist-CwRu2Xzh.js +5 -0
  33. package/dist/{dist-BcuoonNH.js → dist-CxJDU6Bh.js} +9 -9
  34. package/dist/{dist-DxvORzUR.js → dist-D-W5ny5a.js} +8 -8
  35. package/dist/dist-D8CDTVgf.js +6 -0
  36. package/dist/dist-D8DNB0nO.js +8 -0
  37. package/dist/dist-DL6N_q-A.js +5 -0
  38. package/dist/{dist-BbbIBDiQ.js → dist-DMjWuVs8.js} +1 -1
  39. package/dist/dist-DOFbNV_b.js +8 -0
  40. package/dist/dist-DPrYzMY0.js +6 -0
  41. package/dist/{dist-h2c8sZvT.js → dist-DZORgqKY.js} +1 -1
  42. package/dist/{dist-B3P2fFpz.js → dist-DZo4nSS0.js} +14 -14
  43. package/dist/{dist-D4CewLk6.js → dist-Dax--nl9.js} +1 -1
  44. package/dist/{dist-DRfcqpxJ.js → dist-DgGbNavJ.js} +2 -2
  45. package/dist/{dist-C1BYNeCR.js → dist-Dk6PV_d3.js} +10 -10
  46. package/dist/{dist-fQ0ViXGs.js → dist-Dv_Y15yk.js} +107 -107
  47. package/dist/{dist-Bfwsv11D.js → dist-DyyjKEYf.js} +2 -2
  48. package/dist/{dist-p2qyWijU.js → dist-GZXUmt0b.js} +2 -2
  49. package/dist/{dist-CLJWPTX2.js → dist-LTU8Hdvn.js} +3 -3
  50. package/dist/{dist-DqAWR3CS.js → dist-M9Vag9Y0.js} +20 -20
  51. package/dist/{dist-DNdhYsgW.js → dist-U4F-tbMs.js} +79 -62
  52. package/dist/{dist-RqXTaiir.js → dist-abid3KgM.js} +11 -11
  53. package/dist/dist-cdmMjgsn.js +5 -0
  54. package/dist/dist-hT4QzYX-.js +1247 -0
  55. package/dist/{dist-luvabDEB.js → dist-t9Kf7xqC.js} +2 -2
  56. package/dist/{error-banner-5bz0L9hS.js → error-banner-CJXYJ6Sb.js} +11 -6
  57. package/dist/esm-BaH2eg5-.js +1171 -0
  58. package/dist/{esm-Duie8iU-.js → esm-ga2Bf3O2.js} +43 -43
  59. package/dist/{extends-CkydH1Q5.js → extends-D_hDsj6R.js} +4 -4
  60. package/dist/files/wasm-intro.py +11 -8
  61. package/dist/{formats-DHxc-FdY.js → formats-C4wO47tk.js} +1 -1
  62. package/dist/{glide-data-editor-CgIxTzAP.js → glide-data-editor-BPkCPs7L.js} +255 -255
  63. package/dist/{html-to-image-B93KtAVY.js → html-to-image-BwfcJJxA.js} +2412 -2282
  64. package/dist/{input-_2sjvfne.js → input-OdWHkobi.js} +263 -245
  65. package/dist/{label-LWtdw5i8.js → label-CC4ytI1X.js} +1 -1
  66. package/dist/main.js +7630 -7558
  67. package/dist/{mermaid-4DMBBIKO-DIdL224_.js → mermaid-4DMBBIKO-Eg3D4FVz.js} +1 -1
  68. package/dist/{mermaid-lXOw5Py9.js → mermaid-BotvIKg9.js} +4 -4
  69. package/dist/{process-output-BOXi9fnS.js → process-output-DsloEDna.js} +3 -3
  70. package/dist/{reveal-component-gRcjQbNL.js → reveal-component-3YQBHWuD.js} +698 -620
  71. package/dist/{spec-B96zNUEA.js → spec-X7FwLJni.js} +4 -4
  72. package/dist/{strings-Bu3vlb6W.js → strings-J57tzLr3.js} +47 -46
  73. package/dist/style.css +1 -1
  74. package/dist/{toDate-x-WRDCH7.js → toDate-d8RCRrRd.js} +2 -2
  75. package/dist/{tooltip-C5FYOpQc.js → tooltip-DpcyNkQ2.js} +2 -2
  76. package/dist/{types-CVvp1fKr.js → types-ChtMFmZ2.js} +1 -1
  77. package/dist/{useAsyncData-iRgKDT5s.js → useAsyncData-PonK__yh.js} +1 -1
  78. package/dist/{useDateFormatter-BA4FCquG.js → useDateFormatter-QB-3MpYr.js} +2 -2
  79. package/dist/{useDeepCompareMemoize-CkQ57VS2.js → useDeepCompareMemoize-D3NGWke6.js} +1 -1
  80. package/dist/{useLifecycle-BBO9PIph.js → useLifecycle-00mO3OSS.js} +2 -2
  81. package/dist/{useTheme-DHIrRQOe.js → useTheme-DEhDzATN.js} +1 -1
  82. package/dist/{vega-component-Dq-SH463.js → vega-component-DGPUhbDs.js} +8 -8
  83. package/dist/{zod-CoBiJ5v4.js → zod-aLSua2NL.js} +24 -23
  84. package/package.json +4 -3
  85. package/src/__tests__/setup.ts +16 -0
  86. package/src/components/chat/chat-display.tsx +2 -2
  87. package/src/components/data-table/TableBottomBar.tsx +1 -15
  88. package/src/components/data-table/TableTopBar.tsx +8 -13
  89. package/src/components/data-table/__tests__/TableBottomBar.test.tsx +6 -12
  90. package/src/components/data-table/__tests__/column-visibility-dropdown.test.tsx +227 -0
  91. package/src/components/data-table/column-visibility-dropdown.tsx +204 -0
  92. package/src/components/data-table/data-table.tsx +1 -1
  93. package/src/components/data-table/filter-by-values-picker.tsx +39 -17
  94. package/src/components/data-table/filter-pills.tsx +1 -1
  95. package/src/components/datasources/__tests__/filter-empty.test.ts +183 -0
  96. package/src/components/datasources/datasources.tsx +92 -3
  97. package/src/components/editor/cell/code/cell-editor.tsx +70 -32
  98. package/src/components/editor/cell/code/code-placeholder.css +18 -0
  99. package/src/components/editor/cell/code/code-placeholder.tsx +47 -0
  100. package/src/components/editor/cell/code/language-toggle.tsx +7 -1
  101. package/src/components/editor/chrome/wrapper/app-chrome.tsx +97 -52
  102. package/src/components/editor/chrome/wrapper/lazy-panels.ts +91 -0
  103. package/src/components/editor/chrome/wrapper/sidebar.tsx +2 -0
  104. package/src/components/editor/connections/storage/__tests__/__snapshots__/as-code.test.ts.snap +9 -2
  105. package/src/components/editor/connections/storage/__tests__/as-code.test.ts +62 -27
  106. package/src/components/editor/connections/storage/add-storage-form.tsx +5 -1
  107. package/src/components/editor/connections/storage/as-code.ts +20 -6
  108. package/src/components/editor/documentation.css +35 -0
  109. package/src/components/editor/file-tree/file-explorer.tsx +8 -18
  110. package/src/components/editor/file-tree/tree-actions.tsx +46 -1
  111. package/src/components/editor/notebook-cell.tsx +3 -3
  112. package/src/components/editor/renderers/slides-layout/__tests__/plugin.test.ts +20 -0
  113. package/src/components/editor/renderers/slides-layout/types.ts +1 -0
  114. package/src/components/markdown/__tests__/markdown-renderer.test.tsx +43 -0
  115. package/src/components/markdown/markdown-renderer.tsx +24 -1
  116. package/src/components/slides/__tests__/minimap-actions.test.tsx +166 -0
  117. package/src/components/slides/__tests__/reveal-component.test.ts +425 -0
  118. package/src/components/slides/minimap.tsx +127 -10
  119. package/src/components/slides/reveal-component.tsx +287 -61
  120. package/src/components/slides/slide-cell-view.tsx +26 -2
  121. package/src/components/slides/slide-form.tsx +26 -4
  122. package/src/components/storage/__tests__/storage-inspector.test.ts +180 -0
  123. package/src/components/storage/storage-inspector.tsx +535 -81
  124. package/src/components/ui/combobox.tsx +51 -32
  125. package/src/components/ui/number-field.tsx +5 -1
  126. package/src/components/ui/reorderable-list.tsx +13 -0
  127. package/src/components/ui/select-core/__tests__/use-select-list.test.ts +294 -0
  128. package/src/components/ui/select-core/__tests__/utils.test.ts +222 -0
  129. package/src/components/ui/select-core/index.ts +16 -0
  130. package/src/components/ui/select-core/option-row.tsx +33 -0
  131. package/src/components/ui/select-core/render-slot.ts +20 -0
  132. package/src/components/ui/select-core/select-list.tsx +248 -0
  133. package/src/components/ui/select-core/types.ts +44 -0
  134. package/src/components/ui/select-core/use-select-list.ts +347 -0
  135. package/src/components/ui/select-core/utils.ts +121 -0
  136. package/src/components/ui/toast.tsx +1 -0
  137. package/src/core/cells/__tests__/document-changes.test.ts +2 -2
  138. package/src/core/cells/__tests__/focus.test.ts +45 -2
  139. package/src/core/cells/focus.ts +1 -1
  140. package/src/core/cells/scrollCellIntoView.ts +4 -0
  141. package/src/core/cells/utils.ts +1 -1
  142. package/src/core/codemirror/__tests__/editor-mount-scheduler.test.ts +143 -0
  143. package/src/core/codemirror/copilot/getCodes.ts +3 -1
  144. package/src/core/codemirror/editor-mount-scheduler.ts +101 -0
  145. package/src/core/codemirror/language/languages/python.ts +2 -0
  146. package/src/core/codemirror/language/languages/sql/utils.ts +3 -1
  147. package/src/core/codemirror/lsp/__tests__/markdown-renderer.test.ts +41 -0
  148. package/src/core/codemirror/lsp/markdown-renderer.ts +59 -0
  149. package/src/core/datasets/data-source-connections.ts +2 -0
  150. package/src/core/errors/errors.ts +1 -1
  151. package/src/core/storage/__tests__/state.test.ts +49 -0
  152. package/src/core/storage/__tests__/useStorageEntries.test.tsx +199 -3
  153. package/src/core/storage/state.ts +124 -13
  154. package/src/core/storage/types.ts +6 -0
  155. package/src/core/wasm/worker/bootstrap.ts +12 -4
  156. package/src/plugins/core/__test__/registerReactComponent.test.ts +96 -0
  157. package/src/plugins/core/registerReactComponent.tsx +42 -10
  158. package/src/plugins/impl/MultiselectPlugin.tsx +19 -142
  159. package/src/plugins/impl/SearchableSelect.tsx +16 -97
  160. package/src/plugins/impl/SliderPlugin.tsx +132 -18
  161. package/src/plugins/impl/__tests__/DropdownPlugin.test.tsx +5 -2
  162. package/src/plugins/impl/__tests__/MultiSelectPlugin.test.ts +1 -1
  163. package/src/plugins/impl/__tests__/SliderPlugin.test.tsx +278 -8
  164. package/src/utils/__tests__/numbers.test.ts +20 -0
  165. package/src/utils/errors.ts +9 -0
  166. package/src/utils/lazy.ts +6 -1
  167. package/src/utils/numbers.ts +27 -0
  168. package/src/utils/schedule-task.ts +71 -0
  169. package/dist/assets/__vite-browser-external-Ci2ZQfXU.js +0 -1
  170. package/dist/assets/worker-ip3AI_sN.js +0 -73
  171. package/dist/dist-0Fif7jnk.js +0 -5
  172. package/dist/dist-B5h_9sHB.js +0 -6
  173. package/dist/dist-B9M6R5ye.js +0 -5
  174. package/dist/dist-BCt3tnck.js +0 -8
  175. package/dist/dist-BUIJwMwn.js +0 -8
  176. package/dist/dist-BpquMd3k.js +0 -5
  177. package/dist/dist-BzJsqYfz.js +0 -5
  178. package/dist/dist-CA5ELXAf.js +0 -6
  179. package/dist/dist-CLBRs6Uv.js +0 -5
  180. package/dist/dist-CStVCMbq.js +0 -5
  181. package/dist/dist-CZRIEY3Y.js +0 -8
  182. package/dist/dist-CuUHbFD0.js +0 -5
  183. package/dist/dist-DV7Iabxb.js +0 -8
  184. package/dist/dist-DhHh0jLg.js +0 -1247
  185. package/dist/dist-DuEeHMvL.js +0 -5
  186. package/dist/esm-CCuYCd3R.js +0 -1171
  187. package/src/plugins/impl/multiselectFilterFn.tsx +0 -22
  188. /package/src/components/{data-table → ui}/value-chips.tsx +0 -0
@@ -34,6 +34,26 @@ import { ErrorBoundary } from "../../boundary/ErrorBoundary";
34
34
  import { raf2 } from "../../navigation/focus-utils";
35
35
  import { ContextAwarePanel } from "../panels/context-aware-panel/context-aware-panel";
36
36
  import { PanelSectionProvider } from "../panels/panel-context";
37
+ import { useTheme } from "@/theme/useTheme";
38
+ import {
39
+ LazyAgentPanel,
40
+ LazyCachePanel,
41
+ LazyChatPanel,
42
+ LazyDependencyGraphPanel,
43
+ LazyDocumentationPanel,
44
+ LazyErrorsPanel,
45
+ LazyFileExplorerPanel,
46
+ LazyLogsPanel,
47
+ LazyOutlinePanel,
48
+ LazyPackagesPanel,
49
+ LazyScratchpadPanel,
50
+ LazySecretsPanel,
51
+ LazySessionPanel,
52
+ LazySnippetsPanel,
53
+ LazyTerminal,
54
+ LazyTracingPanel,
55
+ PANEL_PRELOADERS,
56
+ } from "./lazy-panels";
37
57
  import { panelLayoutAtom, useChromeActions, useChromeState } from "../state";
38
58
  import {
39
59
  isPanelHidden,
@@ -50,33 +70,26 @@ import { useAiPanelTab } from "./useAiPanel";
50
70
  import { useDependencyPanelTab } from "./useDependencyPanelTab";
51
71
  import { handleDragging } from "./utils";
52
72
 
53
- const LazyTerminal = React.lazy(() => import("@/components/terminal/terminal"));
54
- const LazyChatPanel = React.lazy(() => import("@/components/chat/chat-panel"));
55
- const LazyAgentPanel = React.lazy(
56
- () => import("@/components/chat/acp/agent-panel"),
57
- );
58
- const LazyDependencyGraphPanel = React.lazy(
59
- () => import("@/components/editor/chrome/panels/dependency-graph-panel"),
60
- );
61
- const LazySessionPanel = React.lazy(() => import("../panels/session-panel"));
62
- const LazyDocumentationPanel = React.lazy(
63
- () => import("../panels/documentation-panel"),
64
- );
65
- const LazyErrorsPanel = React.lazy(() => import("../panels/error-panel"));
66
- const LazyFileExplorerPanel = React.lazy(
67
- () => import("../panels/file-explorer-panel"),
68
- );
69
- const LazyLogsPanel = React.lazy(() => import("../panels/logs-panel"));
70
- const LazyOutlinePanel = React.lazy(() => import("../panels/outline-panel"));
71
- const LazyPackagesPanel = React.lazy(() => import("../panels/packages-panel"));
72
- const LazyScratchpadPanel = React.lazy(
73
- () => import("../panels/scratchpad-panel"),
74
- );
75
- const LazySecretsPanel = React.lazy(() => import("../panels/secrets-panel"));
76
- const LazySnippetsPanel = React.lazy(() => import("../panels/snippets-panel"));
77
- const LazyTracingPanel = React.lazy(() => import("../panels/tracing-panel"));
78
- const LazyCachePanel = React.lazy(() => import("../panels/cache-panel"));
79
-
73
+ // Placeholder that matches the eventual xterm theme background so the
74
+ // transition into the loaded terminal is seamless rather than a blank flash.
75
+ const TerminalSkeleton: React.FC = () => {
76
+ const { theme } = useTheme();
77
+ const isDark = theme === "dark";
78
+ return (
79
+ <div
80
+ aria-label="Loading terminal"
81
+ role="status"
82
+ className="w-full h-full flex items-start p-3 font-mono text-xs select-none"
83
+ style={{
84
+ background: isDark ? "#0f172a" : "#ffffff",
85
+ color: isDark ? "#94a3b8" : "#64748b",
86
+ }}
87
+ >
88
+ <span className="opacity-70">Starting terminal</span>
89
+ <span className="ml-1 inline-block w-2 h-3.5 align-middle bg-current animate-pulse" />
90
+ </div>
91
+ );
92
+ };
80
93
  export const AppChrome: React.FC<PropsWithChildren> = ({ children }) => {
81
94
  const {
82
95
  isSidebarOpen,
@@ -96,6 +109,33 @@ export const AppChrome: React.FC<PropsWithChildren> = ({ children }) => {
96
109
  const capabilities = useAtomValue(capabilitiesAtom);
97
110
  const aiEnabled = useAtomValue(aiEnabledAtom);
98
111
 
112
+ // On mount, idle-preload whichever panels the user had open at last unload,
113
+ // so the first interaction with the sidebar/dev panel doesn't hit a cold
114
+ // chunk fetch. Runs once.
115
+ // oxlint-disable-next-line react-hooks/exhaustive-deps
116
+ useEffect(() => {
117
+ const preloadOpenPanels = () => {
118
+ if (isSidebarOpen && selectedPanel) {
119
+ PANEL_PRELOADERS[selectedPanel]?.();
120
+ }
121
+ if (isDeveloperPanelOpen && selectedDeveloperPanelTab) {
122
+ PANEL_PRELOADERS[selectedDeveloperPanelTab]?.();
123
+ }
124
+ };
125
+
126
+ const canIdle =
127
+ typeof window !== "undefined" &&
128
+ typeof window.requestIdleCallback === "function";
129
+ if (canIdle) {
130
+ const handle = window.requestIdleCallback(preloadOpenPanels, {
131
+ timeout: 2000,
132
+ });
133
+ return () => window.cancelIdleCallback(handle);
134
+ }
135
+ const handle = setTimeout(preloadOpenPanels, 300);
136
+ return () => clearTimeout(handle);
137
+ }, []);
138
+
99
139
  // Convert current developer panel items to PanelDescriptors
100
140
  // Filter out hidden panels (e.g., terminal when capability is not available)
101
141
  const devPanelItems = useMemo(() => {
@@ -256,32 +296,34 @@ export const AppChrome: React.FC<PropsWithChildren> = ({ children }) => {
256
296
 
257
297
  const renderAiPanel = () => {
258
298
  if (agentsEnabled && aiPanelTab === "agents") {
259
- return <LazyAgentPanel />;
299
+ return <LazyAgentPanel.Component />;
260
300
  }
261
- return <LazyChatPanel />;
301
+ return <LazyChatPanel.Component />;
262
302
  };
263
303
 
264
304
  const SIDEBAR_PANELS: Record<PanelType, React.ReactNode> = {
265
- files: <LazyFileExplorerPanel />,
266
- variables: <LazySessionPanel />,
267
- dependencies: <LazyDependencyGraphPanel />,
268
- packages: <LazyPackagesPanel />,
269
- outline: <LazyOutlinePanel />,
270
- documentation: <LazyDocumentationPanel />,
271
- snippets: <LazySnippetsPanel />,
305
+ files: <LazyFileExplorerPanel.Component />,
306
+ variables: <LazySessionPanel.Component />,
307
+ dependencies: <LazyDependencyGraphPanel.Component />,
308
+ packages: <LazyPackagesPanel.Component />,
309
+ outline: <LazyOutlinePanel.Component />,
310
+ documentation: <LazyDocumentationPanel.Component />,
311
+ snippets: <LazySnippetsPanel.Component />,
272
312
  ai: renderAiPanel(),
273
- errors: <LazyErrorsPanel />,
274
- scratchpad: <LazyScratchpadPanel />,
275
- tracing: <LazyTracingPanel />,
276
- secrets: <LazySecretsPanel />,
277
- logs: <LazyLogsPanel />,
313
+ errors: <LazyErrorsPanel.Component />,
314
+ scratchpad: <LazyScratchpadPanel.Component />,
315
+ tracing: <LazyTracingPanel.Component />,
316
+ secrets: <LazySecretsPanel.Component />,
317
+ logs: <LazyLogsPanel.Component />,
278
318
  terminal: (
279
- <LazyTerminal
280
- visible={isSidebarOpen && selectedPanel === "terminal"}
281
- onClose={() => setIsSidebarOpen(false)}
282
- />
319
+ <Suspense fallback={<TerminalSkeleton />}>
320
+ <LazyTerminal.Component
321
+ visible={isSidebarOpen && selectedPanel === "terminal"}
322
+ onClose={() => setIsSidebarOpen(false)}
323
+ />
324
+ </Suspense>
283
325
  ),
284
- cache: <LazyCachePanel />,
326
+ cache: <LazyCachePanel.Component />,
285
327
  };
286
328
 
287
329
  const helpPaneBody = (
@@ -414,12 +456,14 @@ export const AppChrome: React.FC<PropsWithChildren> = ({ children }) => {
414
456
  const DEVELOPER_PANELS: Record<PanelType, React.ReactNode> = {
415
457
  ...SIDEBAR_PANELS,
416
458
  terminal: (
417
- <LazyTerminal
418
- visible={
419
- isDeveloperPanelOpen && selectedDeveloperPanelTab === "terminal"
420
- }
421
- onClose={() => setIsDeveloperPanelOpen(false)}
422
- />
459
+ <Suspense fallback={<TerminalSkeleton />}>
460
+ <LazyTerminal.Component
461
+ visible={
462
+ isDeveloperPanelOpen && selectedDeveloperPanelTab === "terminal"
463
+ }
464
+ onClose={() => setIsDeveloperPanelOpen(false)}
465
+ />
466
+ </Suspense>
423
467
  ),
424
468
  };
425
469
 
@@ -474,6 +518,7 @@ export const AppChrome: React.FC<PropsWithChildren> = ({ children }) => {
474
518
  className="flex flex-row gap-1"
475
519
  minItems={0}
476
520
  onAction={(panel) => openApplication(panel.type)}
521
+ onItemPreloadHint={(panel) => PANEL_PRELOADERS[panel.type]?.()}
477
522
  renderItem={(panel) => (
478
523
  <div
479
524
  className={cn(
@@ -0,0 +1,91 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+
3
+ import { reactLazyWithPreload } from "@/utils/lazy";
4
+ import { Logger } from "@/utils/Logger";
5
+ import type { PanelType } from "../types";
6
+
7
+ // Preloading is best-effort: a chunk-load failure here should not surface as
8
+ // an unhandled rejection (the panel will retry the import when actually
9
+ // rendered, where Suspense/ErrorBoundary handle the failure).
10
+ const safePreload = (lazy: { preload: () => Promise<unknown> }) => (): void => {
11
+ void lazy.preload().catch((error) => {
12
+ Logger.debug("Failed to preload panel chunk", error);
13
+ });
14
+ };
15
+
16
+ // Centralized lazy panels. Using reactLazyWithPreload (instead of React.lazy)
17
+ // gives each panel a .preload() method so the chunk can be fetched on intent
18
+ // (hovering the sidebar icon or developer-panel tab) before the user clicks.
19
+
20
+ export const LazyTerminal = reactLazyWithPreload(
21
+ () => import("@/components/terminal/terminal"),
22
+ );
23
+ export const LazyChatPanel = reactLazyWithPreload(
24
+ () => import("@/components/chat/chat-panel"),
25
+ );
26
+ export const LazyAgentPanel = reactLazyWithPreload(
27
+ () => import("@/components/chat/acp/agent-panel"),
28
+ );
29
+ export const LazyDependencyGraphPanel = reactLazyWithPreload(
30
+ () => import("../panels/dependency-graph-panel"),
31
+ );
32
+ export const LazySessionPanel = reactLazyWithPreload(
33
+ () => import("../panels/session-panel"),
34
+ );
35
+ export const LazyDocumentationPanel = reactLazyWithPreload(
36
+ () => import("../panels/documentation-panel"),
37
+ );
38
+ export const LazyErrorsPanel = reactLazyWithPreload(
39
+ () => import("../panels/error-panel"),
40
+ );
41
+ export const LazyFileExplorerPanel = reactLazyWithPreload(
42
+ () => import("../panels/file-explorer-panel"),
43
+ );
44
+ export const LazyLogsPanel = reactLazyWithPreload(
45
+ () => import("../panels/logs-panel"),
46
+ );
47
+ export const LazyOutlinePanel = reactLazyWithPreload(
48
+ () => import("../panels/outline-panel"),
49
+ );
50
+ export const LazyPackagesPanel = reactLazyWithPreload(
51
+ () => import("../panels/packages-panel"),
52
+ );
53
+ export const LazyScratchpadPanel = reactLazyWithPreload(
54
+ () => import("../panels/scratchpad-panel"),
55
+ );
56
+ export const LazySecretsPanel = reactLazyWithPreload(
57
+ () => import("../panels/secrets-panel"),
58
+ );
59
+ export const LazySnippetsPanel = reactLazyWithPreload(
60
+ () => import("../panels/snippets-panel"),
61
+ );
62
+ export const LazyTracingPanel = reactLazyWithPreload(
63
+ () => import("../panels/tracing-panel"),
64
+ );
65
+ export const LazyCachePanel = reactLazyWithPreload(
66
+ () => import("../panels/cache-panel"),
67
+ );
68
+
69
+ // Preloader registry: hovering an icon/tab calls into this map to warm the
70
+ // corresponding chunk. Two panel types (chat and agents) share the "ai" slot,
71
+ // so we preload both.
72
+ export const PANEL_PRELOADERS: Record<PanelType, () => void> = {
73
+ files: safePreload(LazyFileExplorerPanel),
74
+ variables: safePreload(LazySessionPanel),
75
+ dependencies: safePreload(LazyDependencyGraphPanel),
76
+ packages: safePreload(LazyPackagesPanel),
77
+ outline: safePreload(LazyOutlinePanel),
78
+ documentation: safePreload(LazyDocumentationPanel),
79
+ snippets: safePreload(LazySnippetsPanel),
80
+ ai: () => {
81
+ safePreload(LazyChatPanel)();
82
+ safePreload(LazyAgentPanel)();
83
+ },
84
+ errors: safePreload(LazyErrorsPanel),
85
+ scratchpad: safePreload(LazyScratchpadPanel),
86
+ tracing: safePreload(LazyTracingPanel),
87
+ secrets: safePreload(LazySecretsPanel),
88
+ logs: safePreload(LazyLogsPanel),
89
+ terminal: safePreload(LazyTerminal),
90
+ cache: safePreload(LazyCachePanel),
91
+ };
@@ -22,6 +22,7 @@ import {
22
22
  PANELS,
23
23
  type PanelDescriptor,
24
24
  } from "../types";
25
+ import { PANEL_PRELOADERS } from "./lazy-panels";
25
26
 
26
27
  export const Sidebar: React.FC = () => {
27
28
  const { selectedPanel, selectedDeveloperPanelTab, isSidebarOpen } =
@@ -141,6 +142,7 @@ export const Sidebar: React.FC = () => {
141
142
  className="flex flex-col gap-0"
142
143
  minItems={0}
143
144
  onAction={(panel) => toggleApplication(panel.type)}
145
+ onItemPreloadHint={(panel) => PANEL_PRELOADERS[panel.type]?.()}
144
146
  renderItem={(panel) => (
145
147
  <SidebarItem
146
148
  tooltip={panel.tooltip}
@@ -82,10 +82,17 @@ exports[`generateStorageCode > GCS > without service account key (default creden
82
82
  store = GCSStore("my-bucket")"
83
83
  `;
84
84
 
85
- exports[`generateStorageCode > Google Drive > with browser auth (no credentials) 1`] = `
85
+ exports[`generateStorageCode > Google Drive > with default auth (no credentials) 1`] = `
86
86
  "from gdrive_fsspec import GoogleDriveFileSystem
87
87
 
88
- fs = GoogleDriveFileSystem(token="browser", use_listings_cache=False)"
88
+ fs = GoogleDriveFileSystem(use_listings_cache=False)"
89
+ `;
90
+
91
+ exports[`generateStorageCode > Google Drive > with embedded auth (no credentials) 1`] = `
92
+ "from gdrive_fsspec import GoogleDriveFileSystem
93
+
94
+ fs = GoogleDriveFileSystem(use_listings_cache=False, auth_kwargs={"use_local_webserver": False})
95
+ print("Google Drive connected! Important: Run this cell again to clear the console")"
89
96
  `;
90
97
 
91
98
  exports[`generateStorageCode > Google Drive > with service account credentials 1`] = `
@@ -43,7 +43,9 @@ describe("generateStorageCode", () => {
43
43
 
44
44
  describe("S3", () => {
45
45
  it("basic connection with all fields", () => {
46
- expect(generateStorageCode(baseS3, "obstore")).toMatchSnapshot();
46
+ expect(
47
+ generateStorageCode(baseS3, { library: "obstore" }),
48
+ ).toMatchSnapshot();
47
49
  });
48
50
 
49
51
  it("minimal connection (bucket only)", () => {
@@ -51,7 +53,9 @@ describe("generateStorageCode", () => {
51
53
  type: "s3",
52
54
  bucket: "my-bucket",
53
55
  };
54
- expect(generateStorageCode(conn, "obstore")).toMatchSnapshot();
56
+ expect(
57
+ generateStorageCode(conn, { library: "obstore" }),
58
+ ).toMatchSnapshot();
55
59
  });
56
60
 
57
61
  it("with custom endpoint", () => {
@@ -59,7 +63,9 @@ describe("generateStorageCode", () => {
59
63
  ...baseS3,
60
64
  endpoint_url: "https://minio.example.com:9000",
61
65
  };
62
- expect(generateStorageCode(conn, "obstore")).toMatchSnapshot();
66
+ expect(
67
+ generateStorageCode(conn, { library: "obstore" }),
68
+ ).toMatchSnapshot();
63
69
  });
64
70
 
65
71
  it("with secrets", () => {
@@ -70,13 +76,17 @@ describe("generateStorageCode", () => {
70
76
  access_key_id: prefixSecret("AWS_ACCESS_KEY_ID"),
71
77
  secret_access_key: prefixSecret("AWS_SECRET_ACCESS_KEY"),
72
78
  };
73
- expect(generateStorageCode(conn, "obstore")).toMatchSnapshot();
79
+ expect(
80
+ generateStorageCode(conn, { library: "obstore" }),
81
+ ).toMatchSnapshot();
74
82
  });
75
83
  });
76
84
 
77
85
  describe("GCS", () => {
78
86
  it("with service account key", () => {
79
- expect(generateStorageCode(baseGCS, "obstore")).toMatchSnapshot();
87
+ expect(
88
+ generateStorageCode(baseGCS, { library: "obstore" }),
89
+ ).toMatchSnapshot();
80
90
  });
81
91
 
82
92
  it("without service account key (default credentials)", () => {
@@ -84,13 +94,17 @@ describe("generateStorageCode", () => {
84
94
  type: "gcs",
85
95
  bucket: "my-bucket",
86
96
  };
87
- expect(generateStorageCode(conn, "obstore")).toMatchSnapshot();
97
+ expect(
98
+ generateStorageCode(conn, { library: "obstore" }),
99
+ ).toMatchSnapshot();
88
100
  });
89
101
  });
90
102
 
91
103
  describe("Azure", () => {
92
104
  it("basic connection with account key", () => {
93
- expect(generateStorageCode(baseAzure, "obstore")).toMatchSnapshot();
105
+ expect(
106
+ generateStorageCode(baseAzure, { library: "obstore" }),
107
+ ).toMatchSnapshot();
94
108
  });
95
109
 
96
110
  it("without account key", () => {
@@ -99,7 +113,9 @@ describe("generateStorageCode", () => {
99
113
  container: "my-container",
100
114
  account_name: "storageaccount",
101
115
  };
102
- expect(generateStorageCode(conn, "obstore")).toMatchSnapshot();
116
+ expect(
117
+ generateStorageCode(conn, { library: "obstore" }),
118
+ ).toMatchSnapshot();
103
119
  });
104
120
 
105
121
  it("with secrets", () => {
@@ -109,13 +125,17 @@ describe("generateStorageCode", () => {
109
125
  account_name: prefixSecret("AZURE_ACCOUNT"),
110
126
  account_key: prefixSecret("AZURE_KEY"),
111
127
  };
112
- expect(generateStorageCode(conn, "obstore")).toMatchSnapshot();
128
+ expect(
129
+ generateStorageCode(conn, { library: "obstore" }),
130
+ ).toMatchSnapshot();
113
131
  });
114
132
  });
115
133
 
116
134
  describe("CoreWeave", () => {
117
135
  it("basic connection with all fields", () => {
118
- expect(generateStorageCode(baseCoreWeave, "obstore")).toMatchSnapshot();
136
+ expect(
137
+ generateStorageCode(baseCoreWeave, { library: "obstore" }),
138
+ ).toMatchSnapshot();
119
139
  });
120
140
 
121
141
  it("minimal connection (bucket and region only)", () => {
@@ -124,7 +144,9 @@ describe("generateStorageCode", () => {
124
144
  bucket: "operator-bucket",
125
145
  region: "US-EAST-04A",
126
146
  };
127
- expect(generateStorageCode(conn, "obstore")).toMatchSnapshot();
147
+ expect(
148
+ generateStorageCode(conn, { library: "obstore" }),
149
+ ).toMatchSnapshot();
128
150
  });
129
151
 
130
152
  it("with secrets", () => {
@@ -135,39 +157,52 @@ describe("generateStorageCode", () => {
135
157
  access_key_id: prefixSecret("COREWEAVE_OBJECT_STORAGE_KEY"),
136
158
  secret_access_key: prefixSecret("COREWEAVE_OBJECT_STORAGE_SECRET"),
137
159
  };
138
- expect(generateStorageCode(conn, "obstore")).toMatchSnapshot();
160
+ expect(
161
+ generateStorageCode(conn, { library: "obstore" }),
162
+ ).toMatchSnapshot();
139
163
  });
140
164
  });
141
165
 
142
166
  describe("Google Drive", () => {
143
167
  it("with service account credentials", () => {
144
- expect(generateStorageCode(baseGDrive, "fsspec")).toMatchSnapshot();
168
+ expect(
169
+ generateStorageCode(baseGDrive, { library: "fsspec" }),
170
+ ).toMatchSnapshot();
145
171
  });
146
172
 
147
- it("with browser auth (no credentials)", () => {
173
+ it("with default auth (no credentials)", () => {
148
174
  const conn: StorageConnection = {
149
175
  type: "gdrive",
150
176
  };
151
- expect(generateStorageCode(conn, "fsspec")).toMatchSnapshot();
177
+ expect(
178
+ generateStorageCode(conn, { library: "fsspec" }),
179
+ ).toMatchSnapshot();
180
+ });
181
+
182
+ it("with embedded auth (no credentials)", () => {
183
+ const conn: StorageConnection = {
184
+ type: "gdrive",
185
+ };
186
+ expect(
187
+ generateStorageCode(conn, { library: "fsspec", isEmbedded: true }),
188
+ ).toMatchSnapshot();
152
189
  });
153
190
  });
154
191
 
155
192
  describe("invalid cases", () => {
156
193
  it("throws for empty S3 bucket", () => {
157
194
  expect(() =>
158
- generateStorageCode(
159
- { type: "s3", bucket: "" } as StorageConnection,
160
- "obstore",
161
- ),
195
+ generateStorageCode({ type: "s3", bucket: "" } as StorageConnection, {
196
+ library: "obstore",
197
+ }),
162
198
  ).toThrow();
163
199
  });
164
200
 
165
201
  it("throws for empty GCS bucket", () => {
166
202
  expect(() =>
167
- generateStorageCode(
168
- { type: "gcs", bucket: "" } as StorageConnection,
169
- "obstore",
170
- ),
203
+ generateStorageCode({ type: "gcs", bucket: "" } as StorageConnection, {
204
+ library: "obstore",
205
+ }),
171
206
  ).toThrow();
172
207
  });
173
208
 
@@ -179,7 +214,7 @@ describe("generateStorageCode", () => {
179
214
  container: "",
180
215
  account_name: "acct",
181
216
  } as StorageConnection,
182
- "obstore",
217
+ { library: "obstore" },
183
218
  ),
184
219
  ).toThrow();
185
220
  });
@@ -192,7 +227,7 @@ describe("generateStorageCode", () => {
192
227
  container: "my-container",
193
228
  account_name: "",
194
229
  } as StorageConnection,
195
- "obstore",
230
+ { library: "obstore" },
196
231
  ),
197
232
  ).toThrow();
198
233
  });
@@ -205,7 +240,7 @@ describe("generateStorageCode", () => {
205
240
  bucket: "",
206
241
  region: "US-EAST-04A",
207
242
  } as StorageConnection,
208
- "obstore",
243
+ { library: "obstore" },
209
244
  ),
210
245
  ).toThrow();
211
246
  });
@@ -218,7 +253,7 @@ describe("generateStorageCode", () => {
218
253
  bucket: "operator-bucket",
219
254
  region: "",
220
255
  } as StorageConnection,
221
- "obstore",
256
+ { library: "obstore" },
222
257
  ),
223
258
  ).toThrow();
224
259
  });
@@ -5,6 +5,7 @@ import type { FieldValues } from "react-hook-form";
5
5
  import type { z } from "zod";
6
6
  import { ProtocolIcon } from "@/components/storage/components";
7
7
  import type { KnownStorageProtocol } from "@/core/storage/types";
8
+ import { useIframeCapabilities } from "@/hooks/useIframeCapabilities";
8
9
  import { ConnectionForm, SelectorButton, SelectorGrid } from "../components";
9
10
  import {
10
11
  generateStorageCode,
@@ -108,6 +109,7 @@ export const AddStorageForm: React.FC<{
108
109
  onSubmit: () => void;
109
110
  header?: React.ReactNode;
110
111
  }> = ({ onSubmit, header }) => {
112
+ const { isEmbedded } = useIframeCapabilities();
111
113
  const [selectedSchema, setSelectedSchema] = useState<z.ZodType<
112
114
  StorageConnection,
113
115
  FieldValues
@@ -134,7 +136,9 @@ export const AddStorageForm: React.FC<{
134
136
  preferredLibrary={libs?.preferred ?? "obstore"}
135
137
  displayNames={StorageLibraryDisplayNames}
136
138
  libraryLabel="Preferred storage library"
137
- generateCode={(values, library) => generateStorageCode(values, library)}
139
+ generateCode={(values, library) =>
140
+ generateStorageCode(values, { library, isEmbedded })
141
+ }
138
142
  onSubmit={onSubmit}
139
143
  onBack={() => setSelectedSchema(null)}
140
144
  />
@@ -7,6 +7,11 @@ import { type StorageConnection, StorageConnectionSchema } from "./schemas";
7
7
 
8
8
  export type StorageLibrary = "obstore" | "fsspec";
9
9
 
10
+ export interface StorageCodeOptions {
11
+ library: StorageLibrary;
12
+ isEmbedded?: boolean;
13
+ }
14
+
10
15
  export const StorageLibraryDisplayNames: Record<StorageLibrary, string> = {
11
16
  obstore: "obstore",
12
17
  fsspec: "fsspec",
@@ -161,8 +166,9 @@ function generateCoreWeaveCode(
161
166
 
162
167
  function generateGDriveCode(
163
168
  connection: Extract<StorageConnection, { type: "gdrive" }>,
164
- secrets: SecretContainer,
169
+ options: { secrets: SecretContainer; isEmbedded?: boolean },
165
170
  ): { imports: Set<string>; code: string } {
171
+ const { secrets, isEmbedded = false } = options;
166
172
  const imports = new Set(["from gdrive_fsspec import GoogleDriveFileSystem"]);
167
173
 
168
174
  if (connection.credentials_json) {
@@ -178,15 +184,20 @@ function generateGDriveCode(
178
184
  return { imports, code };
179
185
  }
180
186
 
181
- const code = dedent(`
182
- fs = GoogleDriveFileSystem(token="browser", use_listings_cache=False)
183
- `);
187
+ const code = isEmbedded
188
+ ? dedent(`
189
+ fs = GoogleDriveFileSystem(use_listings_cache=False, auth_kwargs={"use_local_webserver": False})
190
+ print("Google Drive connected! Important: Run this cell again to clear the console")
191
+ `)
192
+ : dedent(`
193
+ fs = GoogleDriveFileSystem(use_listings_cache=False)
194
+ `);
184
195
  return { imports, code };
185
196
  }
186
197
 
187
198
  export function generateStorageCode(
188
199
  connection: StorageConnection,
189
- _library: StorageLibrary,
200
+ options: StorageCodeOptions,
190
201
  ): string {
191
202
  StorageConnectionSchema.parse(connection);
192
203
 
@@ -207,7 +218,10 @@ export function generateStorageCode(
207
218
  result = generateCoreWeaveCode(connection, secrets);
208
219
  break;
209
220
  case "gdrive":
210
- result = generateGDriveCode(connection, secrets);
221
+ result = generateGDriveCode(connection, {
222
+ secrets,
223
+ isEmbedded: options.isEmbedded,
224
+ });
211
225
  break;
212
226
  default:
213
227
  assertNever(connection);
@@ -51,6 +51,22 @@
51
51
  }
52
52
  }
53
53
 
54
+ a {
55
+ cursor: pointer;
56
+ text-decoration: inherit;
57
+
58
+ @apply text-link;
59
+ }
60
+
61
+ a:hover,
62
+ a:active {
63
+ text-decoration: underline;
64
+ }
65
+
66
+ a:visited {
67
+ @apply text-link-visited;
68
+ }
69
+
54
70
  code {
55
71
  @apply border px-1 rounded font-mono bg-[var(--slate-2)] text-sm mb-4 mt-2;
56
72
  }
@@ -137,3 +153,22 @@
137
153
  }
138
154
  }
139
155
  }
156
+
157
+ /* Syntax highlighting for LSP hover code blocks (lezer classHighlighter tok-* classes) */
158
+ .docs-documentation,
159
+ .cm-tooltip .documentation {
160
+ .tok-keyword { color: light-dark(#708, #c678dd); font-weight: 500; }
161
+ .tok-string,
162
+ .tok-string2 { color: light-dark(#a11, #98c379); }
163
+ .tok-number { color: light-dark(#164, #d19a66); }
164
+ .tok-bool,
165
+ .tok-atom { color: light-dark(#219, #d19a66); }
166
+ .tok-comment { color: var(--cm-comment); }
167
+ .tok-className { color: light-dark(#00f, #61afef); }
168
+ .tok-typeName,
169
+ .tok-namespace { color: light-dark(#085, #56b6c2); }
170
+ .tok-operator { color: light-dark(#a2f, #56b6c2); }
171
+ .tok-propertyName { color: light-dark(#05a, #e5c07b); }
172
+ .tok-variableName { color: light-dark(#000, #abb2bf); }
173
+ .tok-punctuation { color: light-dark(#000, #abb2bf); }
174
+ }