@marimo-team/frontend 0.24.1-dev116 → 0.24.1-dev119

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 (115) hide show
  1. package/dist/assets/{CellStatus-BnX3kFgK.js → CellStatus-LoTwk7fR.js} +1 -1
  2. package/dist/assets/{ConnectedDataExplorerComponent-Slfh2UI3.js → ConnectedDataExplorerComponent-DW6cJ-Lr.js} +1 -1
  3. package/dist/assets/{JsonOutput-i6wa-ZZg.js → JsonOutput-Bgxc9G7d.js} +2 -2
  4. package/dist/assets/{MarimoErrorOutput-CIYHG601.js → MarimoErrorOutput-DqpoYOvw.js} +1 -1
  5. package/dist/assets/{RenderHTML-BIg2a2gf.js → RenderHTML-DwhMAd9l.js} +1 -1
  6. package/dist/assets/{RunButton-z5x8Nygi.js → RunButton-BlpbweyH.js} +1 -1
  7. package/dist/assets/{VisuallyHidden-K_kroCIL.js → VisuallyHidden-CBMDmozt.js} +1 -1
  8. package/dist/assets/{add-cell-with-ai-P7MBEjnv.js → add-cell-with-ai-R-3DS79K.js} +1 -1
  9. package/dist/assets/{add-connection-dialog-B7yGNrvC.js → add-connection-dialog-BGfpw5lC.js} +1 -1
  10. package/dist/assets/{agent-panel-DUUWyz59.js → agent-panel-B03P2qSx.js} +1 -1
  11. package/dist/assets/ai-model-dropdown-DCmiuk7x.js +3 -0
  12. package/dist/assets/{app-config-button-vxdvoC0x.js → app-config-button-DUJC6b6x.js} +1 -1
  13. package/dist/assets/{blob-DDxD75AS.js → blob-B2d_dHwI.js} +1 -1
  14. package/dist/assets/{cell-editor-D6Cyy4UF.js → cell-editor-QmA8u1Ay.js} +1 -1
  15. package/dist/assets/{cell-link-B7DFi5Hf.js → cell-link-6burT2XF.js} +1 -1
  16. package/dist/assets/{cells-CS_eEQ19.js → cells-Cp-G34Rz.js} +1 -1
  17. package/dist/assets/{chat-display-CtnwRDHx.js → chat-display-Bu405iyc.js} +1 -1
  18. package/dist/assets/{chat-panel-DScfSsWl.js → chat-panel-BlUwNY5j.js} +1 -1
  19. package/dist/assets/{chat-ui-9G115u_7.js → chat-ui-DGiVDzNh.js} +1 -1
  20. package/dist/assets/{column-preview-BVtft2uw.js → column-preview-DHe0-7z4.js} +1 -1
  21. package/dist/assets/{command-palette-uGrOQhzq.js → command-palette-D-kPO0tm.js} +1 -1
  22. package/dist/assets/{common-CQLiJ8JV.js → common-CHR7w7aQ.js} +1 -1
  23. package/dist/assets/{components-B5BY9RR4.js → components-C7mWrmXr.js} +1 -1
  24. package/dist/assets/{components-DAhqHptJ.js → components-DUYCXngU.js} +1 -1
  25. package/dist/assets/{config-CJhe6_P2.js → config-B-r81uZ6.js} +1 -1
  26. package/dist/assets/{config-BRRSKtP3.js → config-BlNM0caL.js} +1 -1
  27. package/dist/assets/{datasource-Br61QFhd.js → datasource-DrODSIs0.js} +1 -1
  28. package/dist/assets/{dependency-graph-panel-DoAAA7Y1.js → dependency-graph-panel-AY5BEVH2.js} +1 -1
  29. package/dist/assets/{documentation-panel-CYUIPHeO.js → documentation-panel-CaJF7e-j.js} +1 -1
  30. package/dist/assets/{download-D2Ha98tp.js → download-DMxOzI7b.js} +1 -1
  31. package/dist/assets/{edit-page-ClGPnVOK.js → edit-page-CdCPUPqZ.js} +4 -4
  32. package/dist/assets/{error-entries-ooCnKQtX.js → error-entries-Cv2m1yhc.js} +1 -1
  33. package/dist/assets/{error-panel-CRtClD-k.js → error-panel-Bhlvt6_H.js} +1 -1
  34. package/dist/assets/file-explorer-panel-DMKcHVIX.js +1 -0
  35. package/dist/assets/{file-icons-D0QvDCq_.js → file-icons-DdGYuADJ.js} +1 -1
  36. package/dist/assets/file-name-input-K4MKESI_.js +3 -0
  37. package/dist/assets/{floating-outline-FQva8xB9.js → floating-outline-DaUHnl_e.js} +1 -1
  38. package/dist/assets/{gallery-page-LX7vPBcZ.js → gallery-page-C3pBpHlY.js} +1 -1
  39. package/dist/assets/{glide-data-editor-DgWXD84U.js → glide-data-editor-sICUMEhk.js} +1 -1
  40. package/dist/assets/{home-page-DtRwYsH-.js → home-page-Cir7n_EF.js} +1 -1
  41. package/dist/assets/index-BGT7have.css +2 -0
  42. package/dist/assets/{index-BphlL86Q.js → index-ClZGC-u9.js} +6 -6
  43. package/dist/assets/{input-C-RazPBA.js → input-DEbY281O.js} +1 -1
  44. package/dist/assets/{kiosk-mode-BqYH0GIF.js → kiosk-mode-mxgb5lWf.js} +1 -1
  45. package/dist/assets/{layout-C4U2fp5O.js → layout-CC2AHMrW.js} +3 -3
  46. package/dist/assets/{logs-panel-BPfZpDxG.js → logs-panel-BGS2v3ba.js} +1 -1
  47. package/dist/assets/{markdown-renderer-CbdXI4uO.js → markdown-renderer-BZKlbm2Q.js} +1 -1
  48. package/dist/assets/{mermaid-BJLFqJtJ.js → mermaid-l6lQkCib.js} +1 -1
  49. package/dist/assets/{name-cell-input-CObHxs3L.js → name-cell-input-BMCdK-el.js} +1 -1
  50. package/dist/assets/{outline-panel-Dyr_2yws.js → outline-panel-Dr5u73V6.js} +1 -1
  51. package/dist/assets/{packages-panel-Cato7lRU.js → packages-panel-BdpRg9tX.js} +1 -1
  52. package/dist/assets/{pair-with-agent-modal-bYFRKgrw.js → pair-with-agent-modal-CbUFKJHP.js} +1 -1
  53. package/dist/assets/{panel-accordion-state-CB4RQUbp.js → panel-accordion-state-DjUTN7i5.js} +1 -1
  54. package/dist/assets/{panels-CaAkyFQ2.js → panels-BsXw4GTa.js} +1 -1
  55. package/dist/assets/{process-output-BvjsWKqP.js → process-output-Ck7-P_xp.js} +1 -1
  56. package/dist/assets/{readonly-code-display-D0VodUl8.js → readonly-code-display--S2daDa6.js} +1 -1
  57. package/dist/assets/{readonly-python-code-DlajH1tG.js → readonly-python-code-DpIyjHqY.js} +1 -1
  58. package/dist/assets/{renderShortcut-DCEcfEd4.js → renderShortcut-X-CJUSM8.js} +1 -1
  59. package/dist/assets/{reveal-component-NBGQ6pCq.js → reveal-component-BuI7t0JO.js} +1 -1
  60. package/dist/assets/{run-page-TDSGBzJ2.js → run-page-DZNsqz9H.js} +1 -1
  61. package/dist/assets/{scratchpad-panel-smtKLHdh.js → scratchpad-panel-CARmXIqQ.js} +1 -1
  62. package/dist/assets/{secrets-panel-CWX3By7x.js → secrets-panel-D3BpDIgg.js} +1 -1
  63. package/dist/assets/{session-panel-BEpBnPuv.js → session-panel-CB5EvmZs.js} +1 -1
  64. package/dist/assets/{skeleton-BVafJh4G.js → skeleton-idYPIMw_.js} +1 -1
  65. package/dist/assets/{snippets-panel-D5Fdmjit.js → snippets-panel-D5yy7kj0.js} +1 -1
  66. package/dist/assets/{state-D2Q_Pzdk.js → state-B6AklCzD.js} +1 -1
  67. package/dist/assets/{state-CbrP-RfI.js → state-Di9TbuV9.js} +1 -1
  68. package/dist/assets/{switch-BzJYj8tz.js → switch-DjXr9Y9D.js} +1 -1
  69. package/dist/assets/{terminal-CWTEGSbX.js → terminal-CQAFGBfN.js} +1 -1
  70. package/dist/assets/{tracing-htk2wAUH.js → tracing-DNk3gmWv.js} +1 -1
  71. package/dist/assets/{tracing-panel-D_FtT8xh.js → tracing-panel-Yqazr6w_.js} +2 -2
  72. package/dist/assets/{tree-actions-CIKsGmOE.js → tree-actions-CG5g7aUQ.js} +1 -1
  73. package/dist/assets/{useAddCell-B9V9LPof.js → useAddCell-DdB_uBif.js} +1 -1
  74. package/dist/assets/{useBoolean-Dh6ALPsT.js → useBoolean-BXC4CItZ.js} +1 -1
  75. package/dist/assets/{useCellActionButton-Byz1dxEY.js → useCellActionButton-B31neI3m.js} +1 -1
  76. package/dist/assets/{useDebounce-Dulm61-t.js → useDebounce-DgpSQhrP.js} +1 -1
  77. package/dist/assets/{useDeleteCell-xIU8YxOm.js → useDeleteCell-Bz49_cFj.js} +1 -1
  78. package/dist/assets/{useDependencyPanelTab-CTX4KBOX.js → useDependencyPanelTab-C_ps0r--.js} +1 -1
  79. package/dist/assets/{useNotebookActions-CEWtEB4v.js → useNotebookActions-Br4AVVRg.js} +1 -1
  80. package/dist/assets/{useRunCells-DpLEou4d.js → useRunCells-CX7oW5zy.js} +1 -1
  81. package/dist/assets/{useSplitCell-DIMMMQzM.js → useSplitCell-Dy2_DmJR.js} +1 -1
  82. package/dist/assets/{useTheme-OSyXt1jZ.js → useTheme-C-ri8BtY.js} +1 -1
  83. package/dist/assets/utils-D5Ywm7-q.js +1 -0
  84. package/dist/assets/{vega-component-D23FlAOS.js → vega-component-C7TCzyaU.js} +1 -1
  85. package/dist/assets/{write-secret-modal-C2V1d4p-.js → write-secret-modal-D6zoZgLY.js} +1 -1
  86. package/dist/index.html +31 -31
  87. package/package.json +1 -1
  88. package/src/components/ai/ai-provider-icon.tsx +2 -2
  89. package/src/components/ai/ai-utils.ts +1 -1
  90. package/src/components/app-config/ai-config.tsx +33 -37
  91. package/src/components/app-config/user-config-form.tsx +1 -1
  92. package/src/components/data-table/renderers.tsx +2 -6
  93. package/src/components/editor/file-tree/__tests__/file-explorer-navigation.test.tsx +832 -0
  94. package/src/components/editor/file-tree/__tests__/file-viewer.test.tsx +27 -0
  95. package/src/components/editor/file-tree/__tests__/requesting-tree.test.ts +130 -0
  96. package/src/components/editor/file-tree/file-browser-row.tsx +82 -0
  97. package/src/components/editor/file-tree/file-explorer.tsx +377 -159
  98. package/src/components/editor/file-tree/file-operations.tsx +1 -1
  99. package/src/components/editor/file-tree/file-search-results.tsx +142 -0
  100. package/src/components/editor/file-tree/file-tree-row.tsx +40 -0
  101. package/src/components/editor/file-tree/file-viewer.tsx +2 -1
  102. package/src/components/editor/file-tree/requesting-tree.tsx +162 -32
  103. package/src/components/editor/file-tree/use-file-search.ts +142 -0
  104. package/src/components/editor/file-tree/use-hover-expand.ts +34 -0
  105. package/src/components/ui/links.tsx +2 -1
  106. package/src/core/ai/ids/__tests__/ids.test.ts +0 -8
  107. package/src/core/ai/ids/ids.ts +1 -1
  108. package/src/core/config/config-schema.ts +1 -0
  109. package/src/core/network/__tests__/requests-toasting.test.tsx +14 -0
  110. package/src/core/network/requests-toasting.tsx +1 -1
  111. package/dist/assets/ai-model-dropdown-BN9nyr0_.js +0 -3
  112. package/dist/assets/file-explorer-panel-DB17Zw1x.js +0 -1
  113. package/dist/assets/file-name-input-DsaNomb7.js +0 -3
  114. package/dist/assets/index-D1E_LKIW.css +0 -2
  115. package/dist/assets/utils-B-_Wem_I.js +0 -1
@@ -64,6 +64,33 @@ describe("FileViewer bounded previews", () => {
64
64
  store.set(requestClientAtom, null);
65
65
  });
66
66
 
67
+ it("uses fetched metadata to offer opening a notebook from a search result", async () => {
68
+ const notebook = {
69
+ ...file,
70
+ id: "/workspace/app.py",
71
+ path: "/workspace/app.py",
72
+ name: "app.py",
73
+ };
74
+ const onOpenNotebook = vi.fn();
75
+ const client = MockRequestClient.create({
76
+ sendFileDetails: vi.fn().mockResolvedValue({
77
+ file: { ...notebook, isMarimoFile: true },
78
+ contents: "import marimo",
79
+ mimeType: "text/plain",
80
+ isBase64: false,
81
+ isTooLarge: false,
82
+ }),
83
+ });
84
+ store.set(requestClientAtom, client);
85
+ render(<FileViewer file={notebook} onOpenNotebook={onOpenNotebook} />, {
86
+ wrapper,
87
+ });
88
+ fireEvent.click(
89
+ await screen.findByRole("button", { name: "Open notebook" }),
90
+ );
91
+ expect(onOpenNotebook).toHaveBeenCalledOnce();
92
+ });
93
+
67
94
  it("requests a bounded preview and shows oversized metadata", async () => {
68
95
  const client = renderViewer({
69
96
  file,
@@ -307,6 +307,109 @@ describe("RequestingTree", () => {
307
307
  expect(sendListFiles).toHaveBeenCalledWith({ path: "/external" });
308
308
  });
309
309
 
310
+ test("caches empty directories and shares concurrent expansion requests", async () => {
311
+ let finish!: (value: { files: never[] }) => void;
312
+ sendListFiles.mockImplementationOnce(
313
+ () =>
314
+ new Promise((resolve) => {
315
+ finish = resolve;
316
+ }),
317
+ );
318
+ const first = tree.expand(EXTERNAL_ROOT_ID);
319
+ const second = tree.expand(EXTERNAL_ROOT_ID);
320
+ expect(sendListFiles).toHaveBeenCalledOnce();
321
+ finish({ files: [] });
322
+ expect(await Promise.all([first, second])).toEqual([true, true]);
323
+ await tree.expand(EXTERNAL_ROOT_ID);
324
+ expect(sendListFiles).toHaveBeenCalledOnce();
325
+ });
326
+
327
+ test("refreshes a cached ancestor when revealing a newly created folder", async () => {
328
+ await tree.expand(PRIMARY_ROOT_ID);
329
+ const newFolder = {
330
+ ...PRIMARY_FILES[1],
331
+ id: "/root/new",
332
+ path: "/root/new",
333
+ name: "new",
334
+ };
335
+ const nested = {
336
+ ...newFolder,
337
+ id: "/root/new/nested",
338
+ path: "/root/new/nested",
339
+ name: "nested",
340
+ };
341
+ sendListFiles.mockImplementation(async ({ path }: { path: string }) => {
342
+ if (path === "/root") {
343
+ return { files: [...PRIMARY_FILES, newFolder] };
344
+ }
345
+ if (path === "/root/new") {
346
+ return { files: [nested] };
347
+ }
348
+ return { files: [] };
349
+ });
350
+ sendListFiles.mockClear();
351
+ expect(
352
+ await tree.reveal({
353
+ ...nested,
354
+ id: fileTreeNodeId("/root", nested.path),
355
+ rootPath: "/root",
356
+ isRoot: false,
357
+ isPrimaryRoot: true,
358
+ }),
359
+ ).toBe(true);
360
+ expect(sendListFiles.mock.calls).toEqual([
361
+ [{ path: "/root" }],
362
+ [{ path: "/root/new" }],
363
+ [{ path: "/root/new/nested" }],
364
+ ]);
365
+ });
366
+
367
+ test("reports a search folder that no longer exists after refreshing its parent", async () => {
368
+ await tree.expand(PRIMARY_ROOT_ID);
369
+ expect(
370
+ await tree.reveal({
371
+ ...PRIMARY_FILES[1],
372
+ id: fileTreeNodeId("/root", "/root/missing"),
373
+ path: "/root/missing",
374
+ rootPath: "/root",
375
+ isRoot: false,
376
+ isPrimaryRoot: true,
377
+ }),
378
+ ).toBe(false);
379
+ expect(toast).toHaveBeenCalledWith(
380
+ expect.objectContaining({ title: "Could not reveal folder" }),
381
+ );
382
+ });
383
+
384
+ test("retries a failed expansion", async () => {
385
+ sendListFiles.mockRejectedValueOnce(new Error("Offline"));
386
+ expect(await tree.expand(EXTERNAL_ROOT_ID)).toBe(false);
387
+ expect(await tree.expand(EXTERNAL_ROOT_ID)).toBe(true);
388
+ expect(sendListFiles).toHaveBeenCalledTimes(2);
389
+ });
390
+
391
+ test("preserves loaded descendants when refreshing their parent", async () => {
392
+ await tree.expand(PRIMARY_ROOT_ID);
393
+ const folderId = fileTreeNodeId("/root", "/root/folder1");
394
+ await tree.expand(folderId);
395
+ await tree.refreshPath("/root" as FilePath);
396
+ sendListFiles.mockClear();
397
+ await tree.expand(folderId);
398
+ expect(sendListFiles).not.toHaveBeenCalled();
399
+ });
400
+
401
+ test("reloads a collapsed cached folder after refreshing", async () => {
402
+ await tree.expand(PRIMARY_ROOT_ID);
403
+ const folderId = fileTreeNodeId("/root", "/root/folder1");
404
+ await tree.expand(folderId);
405
+ await tree.refreshAll([]);
406
+ sendListFiles.mockClear();
407
+ await tree.expand(folderId);
408
+ expect(sendListFiles).toHaveBeenCalledExactlyOnceWith({
409
+ path: "/root/folder1",
410
+ });
411
+ });
412
+
310
413
  test("refreshes supplied open folders in addition to configured roots", async () => {
311
414
  await tree.expand(PRIMARY_ROOT_ID);
312
415
  sendListFiles.mockClear();
@@ -318,6 +421,33 @@ describe("RequestingTree", () => {
318
421
  expect(sendListFiles).toHaveBeenCalledWith({ path: "/root/folder1" });
319
422
  });
320
423
 
424
+ test("invalidates collapsed copies independently for overlapping roots", async () => {
425
+ getRoots.mockResolvedValue({
426
+ roots: [
427
+ { path: "/root", name: "workspace", isPrimary: true },
428
+ { path: "/root/folder1", name: "nested root", isPrimary: false },
429
+ ],
430
+ });
431
+ const overlapping = new RequestingTree({
432
+ getRoots,
433
+ listFiles: sendListFiles,
434
+ createFileOrFolder: sendCreateFileOrFolder,
435
+ deleteFileOrFolder: sendDeleteFileOrFolder,
436
+ copyFileOrFolder: sendCopyFileOrFolder,
437
+ renameFileOrFolder: sendRenameFileOrFolder,
438
+ });
439
+ await overlapping.initialize(onChange);
440
+ await overlapping.expand(PRIMARY_ROOT_ID);
441
+ const collapsedCopy = fileTreeNodeId("/root", "/root/folder1");
442
+ await overlapping.expand(collapsedCopy);
443
+ await overlapping.refreshAll([]);
444
+ sendListFiles.mockClear();
445
+ await overlapping.expand(collapsedCopy);
446
+ expect(sendListFiles).toHaveBeenCalledExactlyOnceWith({
447
+ path: "/root/folder1",
448
+ });
449
+ });
450
+
321
451
  test("keeps existing children when a refresh fails", async () => {
322
452
  await tree.expand(PRIMARY_ROOT_ID);
323
453
  const beforeRefresh = onChange.mock.calls.at(-1)?.[0];
@@ -0,0 +1,82 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+
3
+ import { useEffect, useRef } from "react";
4
+ import type { RowRendererProps } from "react-arborist";
5
+ import { useDrop } from "react-dnd";
6
+ import { FileTreeRow } from "./file-tree-row";
7
+ import { useHoverExpand } from "./use-hover-expand";
8
+ import type { FileTreeNode } from "./requesting-tree";
9
+
10
+ export function FileBrowserRow(props: RowRendererProps<FileTreeNode>) {
11
+ return props.node.data.isDirectory ? (
12
+ <FolderDropRow {...props} />
13
+ ) : (
14
+ <FileTreeRow {...props} />
15
+ );
16
+ }
17
+
18
+ function FolderDropRow(props: RowRendererProps<FileTreeNode>) {
19
+ const { node } = props;
20
+ const tree = node.tree;
21
+ const rowRef = useRef<HTMLDivElement | null>(null);
22
+ const [{ isHovering }, dropRef] = useDrop(
23
+ () => ({
24
+ // Arborist's drag source uses NODE. Keep its validation and move handler,
25
+ // but treat the entire folder row as a destination, without reorder zones.
26
+ accept: "NODE",
27
+ canDrop: () => tree.canDrop(),
28
+ collect: (monitor) => ({
29
+ isHovering: monitor.isOver({ shallow: true }) && monitor.canDrop(),
30
+ }),
31
+ hover: () => {
32
+ if (
33
+ tree.state.dnd.parentId !== node.id ||
34
+ tree.state.dnd.index !== null
35
+ ) {
36
+ tree.dispatch({
37
+ type: "DND_HOVERING",
38
+ parentId: node.id,
39
+ index: null,
40
+ });
41
+ }
42
+ if (!tree.canDrop()) {
43
+ tree.hideCursor();
44
+ }
45
+ },
46
+ drop: () => {
47
+ if (!tree.canDrop()) {
48
+ return;
49
+ }
50
+ void tree.props.onMove?.({
51
+ dragIds: tree.state.dnd.dragIds,
52
+ dragNodes: tree.dragNodes,
53
+ parentId: node.id,
54
+ parentNode: node,
55
+ index: 0,
56
+ });
57
+ tree.open(node.id);
58
+ },
59
+ }),
60
+ [tree, node.id],
61
+ );
62
+
63
+ useHoverExpand(node, isHovering);
64
+
65
+ // Replacing innerRef avoids attaching Arborist's positional drop target.
66
+ // Retain its focus behavior for keyboard navigation and rename completion.
67
+ useEffect(() => {
68
+ if (node.isFocused && !node.isEditing) {
69
+ rowRef.current?.focus({ preventScroll: true });
70
+ }
71
+ }, [node.isFocused, node.isEditing]);
72
+
73
+ return (
74
+ <FileTreeRow
75
+ {...props}
76
+ innerRef={(element) => {
77
+ rowRef.current = element;
78
+ dropRef(element);
79
+ }}
80
+ />
81
+ );
82
+ }