@marimo-team/islands 0.20.5-dev4 → 0.20.5-dev40

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 (167) hide show
  1. package/dist/{Combination-Du-o_hC9.js → Combination-Dk6JxauT.js} +1 -1
  2. package/dist/{ConnectedDataExplorerComponent-DUS-zJoR.js → ConnectedDataExplorerComponent-B07FkeWC.js} +10 -10
  3. package/dist/{any-language-editor-BL9o7y0_.js → any-language-editor-BIj11a2e.js} +19 -19
  4. package/dist/{architectureDiagram-VXUJARFQ-DrJeyFHq.js → architectureDiagram-VXUJARFQ-IZt4NuSd.js} +5 -5
  5. package/dist/{blockDiagram-VD42YOAC-BJrP6qKc.js → blockDiagram-VD42YOAC-mhFHC3Ty.js} +5 -5
  6. package/dist/{button-KYalaJYu.js → button-DQpBib29.js} +24 -11
  7. package/dist/{c4Diagram-YG6GDRKO-Bo4gytQ5.js → c4Diagram-YG6GDRKO-BzStmvfT.js} +4 -4
  8. package/dist/{channel-IWLGkaBE.js → channel-CUFaIkTh.js} +1 -1
  9. package/dist/{check-C50jsehH.js → check-DpqPQmzz.js} +1 -1
  10. package/dist/{chunk-ABZYJK2D-CRwanrkd.js → chunk-ABZYJK2D-7QYXAAhe.js} +1 -1
  11. package/dist/{chunk-ATLVNIR6-CMMCMvOK.js → chunk-ATLVNIR6-pmHPAPSd.js} +1 -1
  12. package/dist/{chunk-B4BG7PRW-BNsHrGHG.js → chunk-B4BG7PRW-C9mfKT9i.js} +4 -4
  13. package/dist/{chunk-DI55MBZ5-DQeYbfMV.js → chunk-DI55MBZ5-IKrK49rX.js} +4 -4
  14. package/dist/{chunk-EXTU4WIE-CV_DQeaX.js → chunk-EXTU4WIE-BRFl4iNd.js} +1 -1
  15. package/dist/{chunk-JA3XYJ7Z-Cmt--e0q.js → chunk-JA3XYJ7Z-C9q_MXZQ.js} +2 -2
  16. package/dist/{chunk-JZLCHNYA-CkyMJnI9.js → chunk-JZLCHNYA-DVjoFib5.js} +4 -4
  17. package/dist/{chunk-N4CR4FBY-BJfHtJbD.js → chunk-N4CR4FBY-BYr5N5mX.js} +5 -5
  18. package/dist/{chunk-QN33PNHL-WOLIPUAJ.js → chunk-QN33PNHL-CXfJywHv.js} +1 -1
  19. package/dist/{chunk-QXUST7PY-DYuD50pU.js → chunk-QXUST7PY-YO0PM8b3.js} +5 -5
  20. package/dist/{chunk-S3R3BYOJ-CsnX6RKs.js → chunk-S3R3BYOJ-DgI4FlvW.js} +1 -1
  21. package/dist/{chunk-TZMSLE5B-B3eYTGCw.js → chunk-TZMSLE5B-DSfBOnzx.js} +1 -1
  22. package/dist/{classDiagram-2ON5EDUG-C7C-oefv.js → classDiagram-2ON5EDUG-CvpnTWzz.js} +10 -10
  23. package/dist/{classDiagram-v2-WZHVMYZB-UTw37Gg8.js → classDiagram-v2-WZHVMYZB-DEQrBHLI.js} +10 -10
  24. package/dist/{copy-oc-FcZzt.js → copy-BkBF0Xgk.js} +2 -2
  25. package/dist/{dagre-6UL2VRFP-BgsUhJrV.js → dagre-6UL2VRFP-DC-emrm5.js} +7 -7
  26. package/dist/{diagram-PSM6KHXK-BIUUOfKo.js → diagram-PSM6KHXK-BAgNlpL8.js} +6 -6
  27. package/dist/{diagram-QEK2KX5R-BFjolZQv.js → diagram-QEK2KX5R-BM7QE5WA.js} +4 -4
  28. package/dist/{diagram-S2PKOQOG-4jfkWoZw.js → diagram-S2PKOQOG-qs4mB1gW.js} +4 -4
  29. package/dist/dist-B4MxkKHf.js +8 -0
  30. package/dist/{dist-De9X_Des.js → dist-B9EjSb9T.js} +1 -1
  31. package/dist/{dist-IW_ARJ3S.js → dist-BFxYppVR.js} +4 -4
  32. package/dist/{dist-D7ZGWV_9.js → dist-BGZ7TWS9.js} +3 -3
  33. package/dist/{dist-CwtEWuFb.js → dist-BSfYc7vq.js} +2 -2
  34. package/dist/{dist-DMS81OrU.js → dist-BUrWeMEP.js} +1 -1
  35. package/dist/dist-BYghZv6b.js +5 -0
  36. package/dist/dist-Be-uQhz5.js +6 -0
  37. package/dist/{dist-Ch_JuCvc.js → dist-BpMlUdNO.js} +3 -3
  38. package/dist/{dist-C6z8U-ms.js → dist-Bq5eYK43.js} +2 -2
  39. package/dist/{dist-BFL9TlzD.js → dist-Bq9zYwJs.js} +5 -5
  40. package/dist/{dist-7ZF--V_D.js → dist-C4K7pumm.js} +2 -2
  41. package/dist/{dist-Qjf6pcqK.js → dist-CAKwXCWI.js} +2 -2
  42. package/dist/dist-CB_xf0ju.js +5 -0
  43. package/dist/{dist-BwQHkjA9.js → dist-CDHl2i1x.js} +4 -4
  44. package/dist/dist-CK0qFAbF.js +8 -0
  45. package/dist/{dist-C4XMUaob.js → dist-CPlGUbk-.js} +2 -2
  46. package/dist/{dist-BT6_J2eq.js → dist-CSEWGuDq.js} +7 -2
  47. package/dist/dist-CYEk-qrr.js +8 -0
  48. package/dist/{dist-CYo3w-nC.js → dist-Cl5iM8xL.js} +3 -3
  49. package/dist/dist-CmKoWpMk.js +5 -0
  50. package/dist/{dist-I8MQW60_.js → dist-CseYuPtL.js} +2 -2
  51. package/dist/dist-D1nf4IQl.js +5 -0
  52. package/dist/{dist-CsqiXw7J.js → dist-D4gcY469.js} +2 -2
  53. package/dist/{dist-DUxS2paD.js → dist-D5NMgbbv.js} +2 -2
  54. package/dist/{dist-UYm1IE5s.js → dist-DERtJN02.js} +2 -2
  55. package/dist/{dist-CFToYDWO.js → dist-DEj2X26M.js} +2 -2
  56. package/dist/{dist-BuapEdlD.js → dist-DOoqn-VL.js} +70 -67
  57. package/dist/{dist-BLThQiU4.js → dist-DUretbKK.js} +2 -2
  58. package/dist/{dist-DEFZ7dnD.js → dist-D_-CGmlh.js} +2 -2
  59. package/dist/dist-Df3AcKpt.js +6 -0
  60. package/dist/dist-DgaFHt_I.js +5 -0
  61. package/dist/dist-Dk10C3ui.js +5 -0
  62. package/dist/{dist-D0f6Yrrb.js → dist-DodLQWPg.js} +1 -1
  63. package/dist/dist-DtyPVMHR.js +5 -0
  64. package/dist/{dist-Cb3cLT39.js → dist-HoZO6brh.js} +2 -2
  65. package/dist/{dist-Cqpjy6bK.js → dist-RNGn_-uD.js} +1 -1
  66. package/dist/{dist-BBcqvpvP.js → dist-Ux6dL_VB.js} +1 -1
  67. package/dist/{dist-B8Y11RWn.js → dist-WIWVvdBh.js} +2 -2
  68. package/dist/{dist-CB6qhQ8K.js → dist-gc9KgJuA.js} +1 -1
  69. package/dist/{dist-ovDpXuSB.js → dist-i-ud9aCA.js} +1 -1
  70. package/dist/dist-ko7WnHAO.js +5 -0
  71. package/dist/{dist-BTQbjEKU.js → dist-lNe4i1Nm.js} +1 -1
  72. package/dist/dist-of7gLRFK.js +8 -0
  73. package/dist/{erDiagram-Q2GNP2WA-Cq5Bz5lG.js → erDiagram-Q2GNP2WA-Dh5nhgY3.js} +10 -10
  74. package/dist/{error-banner-D0tXnwl4.js → error-banner-BctofTCP.js} +2 -2
  75. package/dist/{esm-BxMbHo0y.js → esm-BBkPJL8N.js} +29 -27
  76. package/dist/{flowDiagram-NV44I4VS-6WPJVFl7.js → flowDiagram-NV44I4VS-ChR1Vbmj.js} +10 -10
  77. package/dist/{ganttDiagram-JELNMOA3-AfDhh9CI.js → ganttDiagram-JELNMOA3-sK0z-5KM.js} +3 -3
  78. package/dist/{gitGraphDiagram-V2S2FVAM-BRSwuj0Q.js → gitGraphDiagram-V2S2FVAM-9S1VqQrL.js} +3 -3
  79. package/dist/{glide-data-editor-ByPNTNVG.js → glide-data-editor-DI5VFwRB.js} +63 -63
  80. package/dist/{infoDiagram-HS3SLOUP-Cmxo6jKx.js → infoDiagram-HS3SLOUP-C5A8b-2O.js} +3 -3
  81. package/dist/{journeyDiagram-XKPGCS4Q-CKYr8cSR.js → journeyDiagram-XKPGCS4Q-D5BIjS4N.js} +3 -3
  82. package/dist/{kanban-definition-3W4ZIXB7-DVvAZzQD.js → kanban-definition-3W4ZIXB7-C1vZZabj.js} +7 -7
  83. package/dist/{label-CV0KYhtH.js → label-Cx28eo0O.js} +5 -5
  84. package/dist/{loader-eJCvvApN.js → loader-C62dRCuy.js} +1 -1
  85. package/dist/main.js +1570 -1095
  86. package/dist/{mermaid-COOB_abB.js → mermaid-BgeZPIms.js} +41 -41
  87. package/dist/{mindmap-definition-VGOIOE7T-1ExmnvYy.js → mindmap-definition-VGOIOE7T-Cn9_H_5f.js} +9 -9
  88. package/dist/{pieDiagram-ADFJNKIX-CJlIsdsU.js → pieDiagram-ADFJNKIX-iA0mvRW9.js} +4 -4
  89. package/dist/{purify.es-CyOIw8ru.js → purify.es-DGenX2XH.js} +67 -67
  90. package/dist/{quadrantDiagram-AYHSOK5B-BU78RiaH.js → quadrantDiagram-AYHSOK5B-CAcVWXc-.js} +2 -2
  91. package/dist/{requirementDiagram-UZGBJVZJ-DACHtrFr.js → requirementDiagram-UZGBJVZJ-1HxQ6I5Z.js} +9 -9
  92. package/dist/{sankeyDiagram-TZEHDZUN-Bzg7_UWs.js → sankeyDiagram-TZEHDZUN-BVJnR4_b.js} +2 -2
  93. package/dist/{sequenceDiagram-WL72ISMW-agybEe9J.js → sequenceDiagram-WL72ISMW-ByirOtHb.js} +4 -4
  94. package/dist/{slides-component-B0yK5GXP.js → slides-component-DwvL_HJi.js} +2 -2
  95. package/dist/{spec-Dq_reDGM.js → spec-B8V2Bcbi.js} +4 -4
  96. package/dist/{stateDiagram-FKZM4ZOC-DehQAt8g.js → stateDiagram-FKZM4ZOC-DrYNXdQr.js} +10 -10
  97. package/dist/{stateDiagram-v2-4FDKWEC3-8VzeREl9.js → stateDiagram-v2-4FDKWEC3-C9CFKCSr.js} +10 -10
  98. package/dist/style.css +1 -1
  99. package/dist/{timeline-definition-IT6M3QCI-CdCfdaCF.js → timeline-definition-IT6M3QCI-D8B3p7ID.js} +2 -2
  100. package/dist/{tooltip-CL8m4f9y.js → tooltip-SPkubVH3.js} +3 -3
  101. package/dist/{types-BwnzGcE4.js → types-DqrGPzsT.js} +517 -406
  102. package/dist/{useAsyncData-B4hMFGnF.js → useAsyncData-Ioeh75f8.js} +1 -1
  103. package/dist/{useDeepCompareMemoize-DuPhOXzr.js → useDeepCompareMemoize-DtbTAJq3.js} +4 -4
  104. package/dist/{useIframeCapabilities-CAt6D2EI.js → useIframeCapabilities-DFGZKWkO.js} +1 -1
  105. package/dist/{useTheme-BNYQnvu-.js → useTheme-OvBNH9t3.js} +2 -2
  106. package/dist/{vega-component-DouPy8AI.js → vega-component-B_4Lp3hK.js} +8 -8
  107. package/dist/{xychartDiagram-PRI3JC2R-rEm_SIsC.js → xychartDiagram-PRI3JC2R-KuxgQuK9.js} +5 -5
  108. package/package.json +9 -9
  109. package/src/__mocks__/requests.ts +1 -0
  110. package/src/components/app-config/ai-config.tsx +10 -0
  111. package/src/components/data-table/pagination.tsx +36 -30
  112. package/src/components/datasources/components.tsx +3 -6
  113. package/src/components/datasources/datasources.tsx +8 -21
  114. package/src/components/editor/actions/types.ts +6 -1
  115. package/src/components/editor/actions/useNotebookActions.tsx +50 -13
  116. package/src/components/editor/chrome/types.ts +17 -0
  117. package/src/components/editor/controls/command-palette.tsx +7 -0
  118. package/src/components/editor/controls/keyboard-shortcuts.tsx +3 -1
  119. package/src/components/editor/file-tree/file-explorer.tsx +48 -62
  120. package/src/components/editor/file-tree/file-icons.tsx +132 -0
  121. package/src/components/editor/file-tree/file-viewer.tsx +1 -1
  122. package/src/components/editor/file-tree/tree-actions.tsx +107 -0
  123. package/src/components/editor/file-tree/types.ts +2 -96
  124. package/src/components/editor/header/filename-input.tsx +4 -1
  125. package/src/components/icons/marimo-icons.tsx +2 -2
  126. package/src/components/pages/home-page.tsx +5 -5
  127. package/src/components/storage/components.tsx +0 -38
  128. package/src/components/storage/storage-file-viewer.tsx +1 -1
  129. package/src/components/storage/storage-inspector.tsx +28 -45
  130. package/src/components/ui/command.tsx +2 -0
  131. package/src/components/ui/links.tsx +1 -0
  132. package/src/core/ai/tools/__tests__/run-cells-tool.test.ts +206 -0
  133. package/src/core/ai/tools/run-cells-tool.ts +75 -40
  134. package/src/core/hotkeys/__tests__/hotkeys.test.ts +64 -1
  135. package/src/core/hotkeys/hotkeys.ts +29 -3
  136. package/src/core/islands/bridge.ts +1 -0
  137. package/src/core/network/__tests__/requests-network.test.ts +17 -0
  138. package/src/core/network/requests-lazy.ts +1 -0
  139. package/src/core/network/requests-network.ts +9 -0
  140. package/src/core/network/requests-static.ts +1 -0
  141. package/src/core/network/requests-toasting.tsx +1 -0
  142. package/src/core/network/types.ts +1 -0
  143. package/src/core/wasm/bridge.ts +1 -0
  144. package/src/plugins/impl/FileBrowserPlugin.tsx +4 -4
  145. package/src/plugins/impl/mpl-interactive/MplInteractivePlugin.tsx +309 -0
  146. package/src/plugins/impl/mpl-interactive/__tests__/mpl-websocket-shim.test.ts +110 -0
  147. package/src/plugins/impl/mpl-interactive/mpl-websocket-shim.ts +57 -0
  148. package/src/plugins/impl/plotly/PlotlyPlugin.tsx +8 -2
  149. package/src/plugins/plugins.ts +2 -0
  150. package/src/utils/__tests__/filenames.test.ts +7 -0
  151. package/src/utils/__tests__/smartMatch.test.ts +61 -0
  152. package/src/utils/filenames.ts +3 -0
  153. package/src/utils/smartMatch.ts +62 -0
  154. package/dist/dist-BAeGo2rp.js +0 -5
  155. package/dist/dist-BqwCMSEa.js +0 -5
  156. package/dist/dist-Bum8FwTO.js +0 -6
  157. package/dist/dist-C0YiOwt_.js +0 -5
  158. package/dist/dist-C2uPv4iU.js +0 -5
  159. package/dist/dist-C5hOLsJN.js +0 -8
  160. package/dist/dist-C9NIAKMs.js +0 -8
  161. package/dist/dist-CCrzTtvk.js +0 -5
  162. package/dist/dist-CFS9i1rS.js +0 -8
  163. package/dist/dist-CyHZuhPH.js +0 -5
  164. package/dist/dist-CzcjWdIk.js +0 -6
  165. package/dist/dist-DaYyUSNC.js +0 -5
  166. package/dist/dist-DpDcJYNh.js +0 -8
  167. package/dist/dist-U_BfxcPn.js +0 -5
@@ -3,6 +3,10 @@
3
3
  import { PopoverAnchor } from "@radix-ui/react-popover";
4
4
  import { FilePenIcon } from "lucide-react";
5
5
  import { type JSX, useEffect, useRef, useState } from "react";
6
+ import {
7
+ FILE_ICON as FILE_TYPE_ICONS,
8
+ guessFileIconType as guessFileType,
9
+ } from "@/components/editor/file-tree/file-icons";
6
10
  import type { FileInfo } from "@/core/network/types";
7
11
  import { useAsyncData } from "@/hooks/useAsyncData";
8
12
  import { Paths } from "@/utils/paths";
@@ -15,7 +19,6 @@ import {
15
19
  CommandList,
16
20
  } from "../../ui/command";
17
21
  import { Popover, PopoverContent } from "../../ui/popover";
18
- import { FILE_TYPE_ICONS, guessFileType } from "../file-tree/types";
19
22
 
20
23
  import "./filename-input.css";
21
24
  import { getFeatureFlag } from "@/core/config/feature-flag";
@@ -81,9 +81,9 @@ export const MarimoIcon = ({
81
81
  width={width ?? size}
82
82
  height={height ?? size}
83
83
  viewBox={viewBox}
84
+ aria-hidden={true}
84
85
  {...props}
85
86
  >
86
- <title>marimo icon</title>
87
87
  <MarimoCirclePaths
88
88
  fill={fill}
89
89
  stroke={stroke ?? "currentColor"}
@@ -119,9 +119,9 @@ const MarimoMultiIcon = ({
119
119
  width={width ?? size}
120
120
  height={height ?? size}
121
121
  viewBox={viewBox}
122
+ aria-hidden={true}
122
123
  {...props}
123
124
  >
124
- <title>marimo multi icon</title>
125
125
  <defs>
126
126
  <mask id={maskId}>
127
127
  <rect width="100%" height="100%" fill="white" />
@@ -23,6 +23,11 @@ import {
23
23
  } from "react-arborist";
24
24
  import { useLocale } from "react-aria";
25
25
  import { MarkdownIcon } from "@/components/editor/cell/code/icons";
26
+ import {
27
+ FILE_ICON as FILE_TYPE_ICONS,
28
+ type FileIconType as FileType,
29
+ guessFileIconType as guessFileType,
30
+ } from "@/components/editor/file-tree/file-icons";
26
31
  import { useImperativeModal } from "@/components/modal/ImperativeModal";
27
32
  import { AlertDialogDestructiveAction } from "@/components/ui/alert-dialog";
28
33
  import { Button } from "@/components/ui/button";
@@ -47,11 +52,6 @@ import { newNotebookURL } from "@/utils/urls";
47
52
  import { ConfigButton } from "../app-config/app-config-button";
48
53
  import { ErrorBoundary } from "../editor/boundary/ErrorBoundary";
49
54
  import { ShutdownButton } from "../editor/controls/shutdown-button";
50
- import {
51
- FILE_TYPE_ICONS,
52
- type FileType,
53
- guessFileType,
54
- } from "../editor/file-tree/types";
55
55
  import {
56
56
  Header,
57
57
  OpenTutorialDropDown,
@@ -8,15 +8,9 @@ import CoreweaveIcon from "@marimo-team/llm-info/icons/coreweave.svg?inline";
8
8
  import CoreweaveDarkIcon from "@marimo-team/llm-info/icons/coreweave-dark.svg?inline";
9
9
  import {
10
10
  DatabaseZapIcon,
11
- FileCodeIcon,
12
- FileIcon,
13
- FileSpreadsheetIcon,
14
- FileTextIcon,
15
- FileVideoIcon,
16
11
  GithubIcon,
17
12
  GlobeIcon,
18
13
  HardDriveIcon,
19
- ImageIcon,
20
14
  } from "lucide-react";
21
15
  import GoogleCloudIcon from "@/components/databases/icons/google-cloud-storage.svg?inline";
22
16
  import GoogleDriveIcon from "@/components/databases/icons/google-drive.svg?inline";
@@ -24,38 +18,6 @@ import type { KnownStorageProtocol } from "@/core/storage/types";
24
18
  import { useTheme } from "@/theme/useTheme";
25
19
  import { cn } from "@/utils/cn";
26
20
 
27
- export function renderFileIcon(name: string): React.ReactNode {
28
- const ext = name.split(".").pop()?.toLowerCase();
29
- switch (ext) {
30
- case "png":
31
- case "jpg":
32
- case "jpeg":
33
- case "gif":
34
- case "svg":
35
- case "webp":
36
- return <ImageIcon className="h-3.5 w-3.5 text-purple-500" />;
37
- case "csv":
38
- case "parquet":
39
- case "arrow":
40
- case "xlsx":
41
- return <FileSpreadsheetIcon className="h-3.5 w-3.5 text-green-500" />;
42
- case "py":
43
- case "js":
44
- case "ts":
45
- case "json":
46
- return <FileCodeIcon className="h-3.5 w-3.5 text-blue-500" />;
47
- case "mp4":
48
- case "mpeg":
49
- return <FileVideoIcon className="h-3.5 w-3.5 text-orange-500" />;
50
- case "txt":
51
- case "md":
52
- case "log":
53
- return <FileTextIcon className="h-3.5 w-3.5 text-muted-foreground" />;
54
- default:
55
- return <FileIcon className="h-3.5 w-3.5 text-muted-foreground" />;
56
- }
57
- }
58
-
59
21
  type IconEntry =
60
22
  | { src: string; dark?: string }
61
23
  | React.ComponentType<{ className?: string }>;
@@ -5,6 +5,7 @@ import type React from "react";
5
5
  import { useCallback } from "react";
6
6
  import { useLocale } from "react-aria";
7
7
  import { FilePreviewHeader } from "@/components/editor/file-tree/file-header";
8
+ import { renderFileIcon } from "@/components/editor/file-tree/file-icons";
8
9
  import {
9
10
  FileContentRenderer,
10
11
  isMediaMime,
@@ -18,7 +19,6 @@ import { formatBytes } from "@/utils/formatting";
18
19
  import { Logger } from "@/utils/Logger";
19
20
  import { CopyClipboardIcon } from "../icons/copy-icon";
20
21
  import { Button } from "../ui/button";
21
- import { renderFileIcon } from "./components";
22
22
 
23
23
  const MAX_MEDIA_PREVIEW_SIZE = 100 * 1024 * 1024; // 100 MB
24
24
 
@@ -2,16 +2,13 @@
2
2
 
3
3
  import { CommandList } from "cmdk";
4
4
  import {
5
- ChevronRightIcon,
6
5
  CopyIcon,
7
6
  DownloadIcon,
8
7
  FolderIcon,
9
8
  HardDriveIcon,
10
9
  HelpCircleIcon,
11
10
  LoaderCircle,
12
- MoreVerticalIcon,
13
11
  PlusIcon,
14
- RefreshCwIcon,
15
12
  ViewIcon,
16
13
  XIcon,
17
14
  } from "lucide-react";
@@ -20,6 +17,16 @@ import { useLocale } from "react-aria";
20
17
  import { EngineVariable } from "@/components/databases/engine-variable";
21
18
  import { PanelEmptyState } from "@/components/editor/chrome/panels/empty-state";
22
19
  import { AddConnectionDialog } from "@/components/editor/connections/add-connection-dialog";
20
+ import {
21
+ FILE_ICON_COLOR,
22
+ renderFileIcon,
23
+ } from "@/components/editor/file-tree/file-icons";
24
+ import {
25
+ MENU_ITEM_ICON_CLASS,
26
+ MoreActionsButton,
27
+ RefreshIconButton,
28
+ TreeChevron,
29
+ } from "@/components/editor/file-tree/tree-actions";
23
30
  import { Command, CommandInput, CommandItem } from "@/components/ui/command";
24
31
  import {
25
32
  DropdownMenu,
@@ -49,7 +56,7 @@ import { formatBytes } from "@/utils/formatting";
49
56
  import { Logger } from "@/utils/Logger";
50
57
  import { ErrorState } from "../datasources/components";
51
58
  import { Button } from "../ui/button";
52
- import { ProtocolIcon, renderFileIcon } from "./components";
59
+ import { ProtocolIcon } from "./components";
53
60
  import { StorageFileViewer } from "./storage-file-viewer";
54
61
 
55
62
  interface OpenFileInfo {
@@ -305,17 +312,14 @@ const StorageEntryRow: React.FC<{
305
312
  }}
306
313
  >
307
314
  {isDir ? (
308
- <ChevronRightIcon
309
- className={cn(
310
- "h-3 w-3 shrink-0 transition-transform",
311
- effectiveExpanded && "rotate-90",
312
- )}
313
- />
315
+ <TreeChevron isExpanded={effectiveExpanded} className="h-3 w-3" />
314
316
  ) : (
315
317
  <span className="w-3 shrink-0" />
316
318
  )}
317
319
  {isDir ? (
318
- <FolderIcon className="h-3.5 w-3.5 text-amber-500 shrink-0" />
320
+ <FolderIcon
321
+ className={cn("h-3.5 w-3.5 shrink-0", FILE_ICON_COLOR.directory)}
322
+ />
319
323
  ) : (
320
324
  renderFileIcon(name)
321
325
  )}
@@ -337,14 +341,10 @@ const StorageEntryRow: React.FC<{
337
341
  )}
338
342
  <DropdownMenu>
339
343
  <DropdownMenuTrigger asChild={true}>
340
- <Button
341
- variant="text"
342
- size="icon"
343
- className="opacity-0 group-hover:opacity-100 transition-opacity hover:shadow-none hover:text-link text-muted-foreground"
344
+ <MoreActionsButton
345
+ iconClassName="h-3 w-3"
344
346
  onClick={(e) => e.stopPropagation()}
345
- >
346
- <MoreVerticalIcon className="h-3 w-3" />
347
- </Button>
347
+ />
348
348
  </DropdownMenuTrigger>
349
349
  <DropdownMenuContent
350
350
  align="end"
@@ -355,7 +355,7 @@ const StorageEntryRow: React.FC<{
355
355
  <DropdownMenuItem
356
356
  onSelect={() => onOpenFile({ entry, namespace })}
357
357
  >
358
- <ViewIcon className="h-3.5 w-3.5 mr-2" />
358
+ <ViewIcon className={MENU_ITEM_ICON_CLASS} />
359
359
  View
360
360
  </DropdownMenuItem>
361
361
  )}
@@ -366,12 +366,12 @@ const StorageEntryRow: React.FC<{
366
366
  toast({ title: "Copied to clipboard" });
367
367
  }}
368
368
  >
369
- <CopyIcon className="h-3.5 w-3.5 mr-2" />
369
+ <CopyIcon className={MENU_ITEM_ICON_CLASS} />
370
370
  Copy URL
371
371
  </DropdownMenuItem>
372
372
  {!isDir && (
373
373
  <DropdownMenuItem onSelect={() => handleDownload()}>
374
- <DownloadIcon className="h-3.5 w-3.5 mr-2" />
374
+ <DownloadIcon className={MENU_ITEM_ICON_CLASS} />
375
375
  Download
376
376
  </DropdownMenuItem>
377
377
  )}
@@ -402,7 +402,6 @@ const StorageNamespaceSection: React.FC<{
402
402
  onOpenFile: (info: OpenFileInfo) => void;
403
403
  }> = ({ namespace, locale, searchValue, onOpenFile }) => {
404
404
  const [isExpanded, setIsExpanded] = useState(true);
405
- const [isSpinning, setIsSpinning] = useState(false);
406
405
  const { entriesByPath } = useStorage();
407
406
  const { clearNamespaceCache } = useStorageActions();
408
407
  const namespaceName = namespace.name ?? namespace.displayName;
@@ -417,10 +416,8 @@ const StorageNamespaceSection: React.FC<{
417
416
  const handleRefresh = useCallback(
418
417
  (e: React.MouseEvent) => {
419
418
  e.stopPropagation();
420
- setIsSpinning(true);
421
419
  clearNamespaceCache(namespaceName);
422
420
  refetch();
423
- setTimeout(() => setIsSpinning(false), 500);
424
421
  },
425
422
  [namespaceName, clearNamespaceCache, refetch],
426
423
  );
@@ -441,12 +438,7 @@ const StorageNamespaceSection: React.FC<{
441
438
  onSelect={() => setIsExpanded(!isExpanded)}
442
439
  className="flex flex-row font-semibold h-7 text-xs gap-1.5 bg-(--slate-2) text-muted-foreground rounded-none"
443
440
  >
444
- <ChevronRightIcon
445
- className={cn(
446
- "h-3 w-3 shrink-0 transition-transform",
447
- isExpanded && "rotate-90",
448
- )}
449
- />
441
+ <TreeChevron isExpanded={isExpanded} className="h-3 w-3" />
450
442
  <ProtocolIcon protocol={namespace.protocol} />
451
443
  <span>{namespace.displayName}</span>
452
444
  {namespace.name && (
@@ -454,21 +446,12 @@ const StorageNamespaceSection: React.FC<{
454
446
  (<EngineVariable variableName={namespace.name as VariableName} />)
455
447
  </span>
456
448
  )}
457
- <Tooltip content="Refresh storage connection">
458
- <Button
459
- variant="ghost"
460
- size="icon"
461
- className="hover:bg-transparent hover:shadow-none"
462
- onClick={handleRefresh}
463
- >
464
- <RefreshCwIcon
465
- className={cn(
466
- "h-3 w-3 text-muted-foreground hover:text-foreground",
467
- isSpinning && "animate-[spin_0.5s]",
468
- )}
469
- />
470
- </Button>
471
- </Tooltip>
449
+ <RefreshIconButton
450
+ onClick={handleRefresh}
451
+ tooltip="Refresh storage connection"
452
+ className="p-0"
453
+ iconClassName="h-3 w-3"
454
+ />
472
455
  <span className="text-[10px] text-muted-foreground font-normal tabular-nums ml-auto">
473
456
  {namespace.rootPath || "(root)"}
474
457
  </span>
@@ -7,6 +7,7 @@ import { Search } from "lucide-react";
7
7
  import * as React from "react";
8
8
  import { Dialog, DialogContent } from "@/components/ui/dialog";
9
9
  import { cn } from "@/utils/cn";
10
+ import { smartMatchFilter } from "@/utils/smartMatch";
10
11
  import { Strings } from "@/utils/strings";
11
12
  import {
12
13
  MENU_ITEM_DISABLED,
@@ -21,6 +22,7 @@ const Command = React.forwardRef<
21
22
  >(({ className, ...props }, ref) => (
22
23
  <CommandPrimitive
23
24
  ref={ref}
25
+ filter={smartMatchFilter}
24
26
  className={cn(
25
27
  "flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground",
26
28
  className,
@@ -8,6 +8,7 @@ export const ExternalLink = ({
8
8
  | `https://console.anthropic.com/${string}`
9
9
  | `https://aistudio.google.com/${string}`
10
10
  | `https://github.com/${string}`
11
+ | `https://docs.github.com/${string}`
11
12
  | `https://openrouter.ai/${string}`
12
13
  | `https://docs.marimo.io/${string}`
13
14
  | `https://docs.python.org/${string}`
@@ -411,6 +411,212 @@ describe("RunStaleCellsTool", () => {
411
411
  });
412
412
  });
413
413
 
414
+ describe("output truncation", () => {
415
+ it("should summarize text/html output instead of dumping raw content", async () => {
416
+ const notebook = MockNotebook.notebookState({
417
+ cellData: {
418
+ [cellId1]: { code: "fig.show()", edited: true },
419
+ },
420
+ });
421
+ store.set(notebookAtom, notebook);
422
+
423
+ vi.mocked(runCells).mockImplementation(async () => {
424
+ const updatedNotebook = store.get(notebookAtom);
425
+ updatedNotebook.cellRuntime[cellId1] = {
426
+ ...updatedNotebook.cellRuntime[cellId1],
427
+ status: "idle",
428
+ };
429
+ store.set(notebookAtom, updatedNotebook);
430
+ });
431
+
432
+ const largeHtml = `<div>${"x".repeat(2_000_000)}</div>`;
433
+ vi.mocked(getCellContextData).mockReturnValue({
434
+ cellOutput: {
435
+ outputType: "text",
436
+ processedContent: null,
437
+ imageUrl: null,
438
+ output: { mimetype: "text/html", data: largeHtml },
439
+ },
440
+ consoleOutputs: null,
441
+ cellName: "cell1",
442
+ } as never);
443
+
444
+ const result = await tool.handler({}, toolContext as never);
445
+
446
+ expect(result.status).toBe("success");
447
+ const output = result.cellsToOutput?.[cellId1]?.cellOutput ?? "";
448
+ expect(output).toContain("HTML Output:");
449
+ expect(output).toContain("text/html");
450
+ expect(output.length).toBeLessThan(200);
451
+ expect(output).not.toContain(largeHtml);
452
+ });
453
+
454
+ it("should truncate large text output to MAX_TEXT_OUTPUT_CHARS", async () => {
455
+ const notebook = MockNotebook.notebookState({
456
+ cellData: {
457
+ [cellId1]: { code: "print(big_string)", edited: true },
458
+ },
459
+ });
460
+ store.set(notebookAtom, notebook);
461
+
462
+ vi.mocked(runCells).mockImplementation(async () => {
463
+ const updatedNotebook = store.get(notebookAtom);
464
+ updatedNotebook.cellRuntime[cellId1] = {
465
+ ...updatedNotebook.cellRuntime[cellId1],
466
+ status: "idle",
467
+ };
468
+ store.set(notebookAtom, updatedNotebook);
469
+ });
470
+
471
+ const largeText = "a".repeat(10_000);
472
+ vi.mocked(getCellContextData).mockReturnValue({
473
+ cellOutput: {
474
+ outputType: "text",
475
+ processedContent: largeText,
476
+ imageUrl: null,
477
+ output: { mimetype: "text/plain", data: largeText },
478
+ },
479
+ consoleOutputs: null,
480
+ cellName: "cell1",
481
+ } as never);
482
+
483
+ const result = await tool.handler({}, toolContext as never);
484
+
485
+ const output = result.cellsToOutput?.[cellId1]?.cellOutput ?? "";
486
+ expect(output).toContain("[TRUNCATED:");
487
+ expect(output).toContain("Full output visible in the notebook UI.");
488
+ // Output should be capped (2000 chars content + "Output:\n" prefix + truncation message)
489
+ expect(output.length).toBeLessThan(2200);
490
+ });
491
+
492
+ it("should omit output for cells that exceed total output budget", async () => {
493
+ const cellIds = Array.from(
494
+ { length: 25 },
495
+ (_, i) => `budget-cell-${i}` as CellId,
496
+ );
497
+ const cellData: Record<string, { code: string; edited: boolean }> = {};
498
+ for (const id of cellIds) {
499
+ cellData[id] = { code: "x = 1", edited: true };
500
+ }
501
+
502
+ const notebook = MockNotebook.notebookState({ cellData });
503
+ store.set(notebookAtom, notebook);
504
+
505
+ vi.mocked(runCells).mockImplementation(async () => {
506
+ const updatedNotebook = store.get(notebookAtom);
507
+ for (const id of cellIds) {
508
+ updatedNotebook.cellRuntime[id] = {
509
+ ...updatedNotebook.cellRuntime[id],
510
+ status: "idle",
511
+ };
512
+ }
513
+ store.set(notebookAtom, updatedNotebook);
514
+ });
515
+
516
+ // Each cell produces ~2008 chars of formatted output ("Output:\n" + 2000 chars).
517
+ // After 20 cells the running total exceeds MAX_TOOL_OUTPUT_CHARS (40,000).
518
+ const content = "a".repeat(2000);
519
+ vi.mocked(getCellContextData).mockReturnValue({
520
+ cellOutput: {
521
+ outputType: "text",
522
+ processedContent: content,
523
+ imageUrl: null,
524
+ output: { mimetype: "text/plain", data: content },
525
+ },
526
+ consoleOutputs: null,
527
+ cellName: "cell",
528
+ } as never);
529
+
530
+ const result = await tool.handler({}, toolContext as never);
531
+
532
+ expect(result.cellsToOutput?.[cellIds[0]]?.cellOutput).toContain(
533
+ "Output:",
534
+ );
535
+ expect(result.cellsToOutput?.[cellIds[24]]?.cellOutput).toBe(
536
+ "Cell executed (output omitted due to context limits).",
537
+ );
538
+ });
539
+
540
+ it("should use higher truncation limit for error outputs", async () => {
541
+ const notebook = MockNotebook.notebookState({
542
+ cellData: {
543
+ [cellId1]: { code: "raise Exception()", edited: true },
544
+ },
545
+ });
546
+ store.set(notebookAtom, notebook);
547
+
548
+ vi.mocked(runCells).mockImplementation(async () => {
549
+ const updatedNotebook = store.get(notebookAtom);
550
+ updatedNotebook.cellRuntime[cellId1] = {
551
+ ...updatedNotebook.cellRuntime[cellId1],
552
+ status: "idle",
553
+ };
554
+ store.set(notebookAtom, updatedNotebook);
555
+ });
556
+
557
+ // 2500 chars sits between MAX_TEXT_OUTPUT_CHARS (2000) and MAX_ERROR_OUTPUT_CHARS (3000)
558
+ const errorContent = "E".repeat(2500);
559
+ vi.mocked(getCellContextData).mockReturnValue({
560
+ cellOutput: {
561
+ outputType: "text",
562
+ processedContent: errorContent,
563
+ imageUrl: null,
564
+ output: {
565
+ mimetype: "application/vnd.marimo+error",
566
+ data: errorContent,
567
+ },
568
+ },
569
+ consoleOutputs: null,
570
+ cellName: "cell1",
571
+ } as never);
572
+
573
+ const result = await tool.handler({}, toolContext as never);
574
+
575
+ const output = result.cellsToOutput?.[cellId1]?.cellOutput ?? "";
576
+ expect(output).not.toContain("[TRUNCATED:");
577
+ expect(output).toContain(errorContent);
578
+ });
579
+
580
+ it("should truncate large console output", async () => {
581
+ const notebook = MockNotebook.notebookState({
582
+ cellData: {
583
+ [cellId1]: { code: 'print("x" * 10000)', edited: true },
584
+ },
585
+ });
586
+ store.set(notebookAtom, notebook);
587
+
588
+ vi.mocked(runCells).mockImplementation(async () => {
589
+ const updatedNotebook = store.get(notebookAtom);
590
+ updatedNotebook.cellRuntime[cellId1] = {
591
+ ...updatedNotebook.cellRuntime[cellId1],
592
+ status: "idle",
593
+ };
594
+ store.set(notebookAtom, updatedNotebook);
595
+ });
596
+
597
+ const largeConsoleText = "x".repeat(10_000);
598
+ vi.mocked(getCellContextData).mockReturnValue({
599
+ cellOutput: null,
600
+ consoleOutputs: [
601
+ {
602
+ outputType: "text",
603
+ processedContent: largeConsoleText,
604
+ imageUrl: null,
605
+ output: { mimetype: "text/plain", data: largeConsoleText },
606
+ },
607
+ ],
608
+ cellName: "cell1",
609
+ } as never);
610
+
611
+ const result = await tool.handler({}, toolContext as never);
612
+
613
+ const consoleOutput =
614
+ result.cellsToOutput?.[cellId1]?.consoleOutput ?? "";
615
+ expect(consoleOutput).toContain("[TRUNCATED:");
616
+ expect(consoleOutput.length).toBeLessThan(2200);
617
+ });
618
+ });
619
+
414
620
  describe("cell execution completion", () => {
415
621
  it("should complete immediately if cells are already idle", async () => {
416
622
  const notebook = MockNotebook.notebookState({