@marimo-team/islands 0.20.5-dev9 → 0.20.5-dev91

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 (253) hide show
  1. package/dist/{Combination-Du-o_hC9.js → Combination-Dk6JxauT.js} +1 -1
  2. package/dist/{ConnectedDataExplorerComponent-DUS-zJoR.js → ConnectedDataExplorerComponent-Bh11efrC.js} +17 -17
  3. package/dist/{_baseIsEqual-5cAxzk6f.js → _baseIsEqual-CvgsjYoW.js} +38 -38
  4. package/dist/{_basePickBy-3JVb5wYm.js → _basePickBy-pTDW2_2A.js} +6 -6
  5. package/dist/{_baseUniq-DSSiFuIJ.js → _baseUniq-BUFhl85h.js} +1 -1
  6. package/dist/{any-language-editor-BL9o7y0_.js → any-language-editor-BIj11a2e.js} +19 -19
  7. package/dist/{architecture-7HQA4BMR-BxkNpYRp.js → architecture-7HQA4BMR-BmtmhGMc.js} +2 -2
  8. package/dist/{architectureDiagram-VXUJARFQ-DrJeyFHq.js → architectureDiagram-VXUJARFQ-Df0FNeBR.js} +14 -14
  9. package/dist/assets/__vite-browser-external-Us1ds95c.js +1 -0
  10. package/dist/assets/{worker-DUYMdbtA.js → worker-D10K3OOz.js} +2 -2
  11. package/dist/{blockDiagram-VD42YOAC-BJrP6qKc.js → blockDiagram-VD42YOAC-DszWqlLz.js} +7 -7
  12. package/dist/{button-KYalaJYu.js → button-DQpBib29.js} +24 -11
  13. package/dist/{c4Diagram-YG6GDRKO-Bo4gytQ5.js → c4Diagram-YG6GDRKO-Dyj8LoUX.js} +4 -4
  14. package/dist/{channel-IWLGkaBE.js → channel-CUFaIkTh.js} +1 -1
  15. package/dist/{check-C50jsehH.js → check-DpqPQmzz.js} +1 -1
  16. package/dist/{chunk-4F5CHEZ2-CxKDFd-t.js → chunk-4F5CHEZ2-CRwwZ2ED.js} +1 -1
  17. package/dist/{chunk-ABZYJK2D-CRwanrkd.js → chunk-ABZYJK2D-7QYXAAhe.js} +1 -1
  18. package/dist/{chunk-ATLVNIR6-CMMCMvOK.js → chunk-ATLVNIR6-pmHPAPSd.js} +1 -1
  19. package/dist/{chunk-B2363JML-e_W7KW1D.js → chunk-B2363JML-BuBMltZc.js} +1 -1
  20. package/dist/{chunk-B4BG7PRW-BNsHrGHG.js → chunk-B4BG7PRW-Dbta9cTX.js} +4 -4
  21. package/dist/{chunk-DI55MBZ5-DQeYbfMV.js → chunk-DI55MBZ5-DyKB35wC.js} +4 -4
  22. package/dist/{chunk-EXTU4WIE-CV_DQeaX.js → chunk-EXTU4WIE-BRFl4iNd.js} +1 -1
  23. package/dist/{chunk-FRFDVMJY-C7q09nvl.js → chunk-FRFDVMJY-Bk2LD5Te.js} +1 -1
  24. package/dist/{chunk-JA3XYJ7Z-Cmt--e0q.js → chunk-JA3XYJ7Z-BkrY9SdL.js} +2 -2
  25. package/dist/{chunk-JZLCHNYA-CkyMJnI9.js → chunk-JZLCHNYA-Bk_Lil-q.js} +4 -4
  26. package/dist/{chunk-N4CR4FBY-BJfHtJbD.js → chunk-N4CR4FBY-f5n6meOd.js} +5 -5
  27. package/dist/{chunk-PL6DKKU2-ChKBqnoD.js → chunk-PL6DKKU2-DiFkzMfM.js} +1 -1
  28. package/dist/{chunk-QN33PNHL-WOLIPUAJ.js → chunk-QN33PNHL-CXfJywHv.js} +1 -1
  29. package/dist/{chunk-QXUST7PY-DYuD50pU.js → chunk-QXUST7PY-D7-26sj3.js} +5 -5
  30. package/dist/{chunk-S3R3BYOJ-CsnX6RKs.js → chunk-S3R3BYOJ-BRT9vd1R.js} +3 -3
  31. package/dist/{chunk-SJTYNZTY-j6_1s5om.js → chunk-SJTYNZTY-BvVkbShU.js} +1 -1
  32. package/dist/{chunk-TCCFYFTB-DdLCbCTn.js → chunk-TCCFYFTB-DqxhgXG0.js} +31 -31
  33. package/dist/{chunk-TQ3KTPDO-CGsUIC73.js → chunk-TQ3KTPDO-CPkEruAA.js} +1 -1
  34. package/dist/{chunk-TZMSLE5B-B3eYTGCw.js → chunk-TZMSLE5B-DSfBOnzx.js} +1 -1
  35. package/dist/{chunk-UMXZTB3W--LdAK3Bv.js → chunk-UMXZTB3W-C4ypIY3V.js} +1 -1
  36. package/dist/{classDiagram-v2-WZHVMYZB-UTw37Gg8.js → classDiagram-2ON5EDUG-DphiMW3Y.js} +10 -10
  37. package/dist/{classDiagram-2ON5EDUG-C7C-oefv.js → classDiagram-v2-WZHVMYZB-BH1x5h4a.js} +10 -10
  38. package/dist/{clone-BJrS4PdE.js → clone-CEQ-pda1.js} +1 -1
  39. package/dist/{constants-D1Tbg_6B.js → constants-CytQ_3LM.js} +3 -3
  40. package/dist/{copy-oc-FcZzt.js → copy-BkBF0Xgk.js} +2 -2
  41. package/dist/{dagre-6UL2VRFP-BgsUhJrV.js → dagre-6UL2VRFP-DGEbtmgU.js} +12 -12
  42. package/dist/{dagre-CyZCGfV_.js → dagre-BVnNvbvD.js} +37 -37
  43. package/dist/{diagram-PSM6KHXK-BIUUOfKo.js → diagram-PSM6KHXK-CG_usglE.js} +15 -15
  44. package/dist/{diagram-QEK2KX5R-BFjolZQv.js → diagram-QEK2KX5R-CtGFEwzJ.js} +13 -13
  45. package/dist/{diagram-S2PKOQOG-4jfkWoZw.js → diagram-S2PKOQOG-ClKAGmbv.js} +13 -13
  46. package/dist/dist-B4MxkKHf.js +8 -0
  47. package/dist/{dist-De9X_Des.js → dist-B9EjSb9T.js} +1 -1
  48. package/dist/{dist-IW_ARJ3S.js → dist-BFxYppVR.js} +4 -4
  49. package/dist/{dist-D7ZGWV_9.js → dist-BGZ7TWS9.js} +3 -3
  50. package/dist/{dist-CwtEWuFb.js → dist-BSfYc7vq.js} +2 -2
  51. package/dist/{dist-DMS81OrU.js → dist-BUrWeMEP.js} +1 -1
  52. package/dist/dist-BYghZv6b.js +5 -0
  53. package/dist/dist-Be-uQhz5.js +6 -0
  54. package/dist/{dist-Ch_JuCvc.js → dist-BpMlUdNO.js} +3 -3
  55. package/dist/{dist-C6z8U-ms.js → dist-Bq5eYK43.js} +2 -2
  56. package/dist/{dist-BFL9TlzD.js → dist-Bq9zYwJs.js} +5 -5
  57. package/dist/{dist-7ZF--V_D.js → dist-C4K7pumm.js} +2 -2
  58. package/dist/{dist-Qjf6pcqK.js → dist-CAKwXCWI.js} +2 -2
  59. package/dist/dist-CB_xf0ju.js +5 -0
  60. package/dist/{dist-BwQHkjA9.js → dist-CDHl2i1x.js} +4 -4
  61. package/dist/dist-CK0qFAbF.js +8 -0
  62. package/dist/{dist-C4XMUaob.js → dist-CPlGUbk-.js} +2 -2
  63. package/dist/{dist-BT6_J2eq.js → dist-CSEWGuDq.js} +7 -2
  64. package/dist/dist-CYEk-qrr.js +8 -0
  65. package/dist/{dist-CYo3w-nC.js → dist-Cl5iM8xL.js} +3 -3
  66. package/dist/dist-CmKoWpMk.js +5 -0
  67. package/dist/{dist-I8MQW60_.js → dist-CseYuPtL.js} +2 -2
  68. package/dist/dist-D1nf4IQl.js +5 -0
  69. package/dist/{dist-CsqiXw7J.js → dist-D4gcY469.js} +2 -2
  70. package/dist/{dist-DUxS2paD.js → dist-D5NMgbbv.js} +2 -2
  71. package/dist/{dist-UYm1IE5s.js → dist-DERtJN02.js} +2 -2
  72. package/dist/{dist-CFToYDWO.js → dist-DEj2X26M.js} +2 -2
  73. package/dist/{dist-BuapEdlD.js → dist-DOoqn-VL.js} +70 -67
  74. package/dist/{dist-BLThQiU4.js → dist-DUretbKK.js} +2 -2
  75. package/dist/{dist-DEFZ7dnD.js → dist-D_-CGmlh.js} +2 -2
  76. package/dist/dist-Df3AcKpt.js +6 -0
  77. package/dist/dist-DgaFHt_I.js +5 -0
  78. package/dist/dist-Dk10C3ui.js +5 -0
  79. package/dist/{dist-D0f6Yrrb.js → dist-DodLQWPg.js} +1 -1
  80. package/dist/dist-DtyPVMHR.js +5 -0
  81. package/dist/{dist-Cb3cLT39.js → dist-HoZO6brh.js} +2 -2
  82. package/dist/{dist-Cqpjy6bK.js → dist-RNGn_-uD.js} +1 -1
  83. package/dist/{dist-BBcqvpvP.js → dist-Ux6dL_VB.js} +1 -1
  84. package/dist/{dist-B8Y11RWn.js → dist-WIWVvdBh.js} +2 -2
  85. package/dist/{dist-CB6qhQ8K.js → dist-gc9KgJuA.js} +1 -1
  86. package/dist/{dist-ovDpXuSB.js → dist-i-ud9aCA.js} +1 -1
  87. package/dist/dist-ko7WnHAO.js +5 -0
  88. package/dist/{dist-BTQbjEKU.js → dist-lNe4i1Nm.js} +1 -1
  89. package/dist/dist-of7gLRFK.js +8 -0
  90. package/dist/{erDiagram-Q2GNP2WA-Cq5Bz5lG.js → erDiagram-Q2GNP2WA-DPMseVVp.js} +10 -10
  91. package/dist/{error-banner-D0tXnwl4.js → error-banner-BctofTCP.js} +2 -2
  92. package/dist/{esm-BxMbHo0y.js → esm-BBkPJL8N.js} +29 -27
  93. package/dist/{flowDiagram-NV44I4VS-6WPJVFl7.js → flowDiagram-NV44I4VS-BpAIFwW7.js} +10 -10
  94. package/dist/{ganttDiagram-JELNMOA3-AfDhh9CI.js → ganttDiagram-JELNMOA3-DXYghZ9C.js} +3 -3
  95. package/dist/{gitGraph-G5XIXVHT-C0o6gecv.js → gitGraph-G5XIXVHT-ChHUSAop.js} +2 -2
  96. package/dist/{gitGraphDiagram-V2S2FVAM-BRSwuj0Q.js → gitGraphDiagram-V2S2FVAM-CBL-7g3_.js} +12 -12
  97. package/dist/{glide-data-editor-ByPNTNVG.js → glide-data-editor-DqxJOnJk.js} +63 -63
  98. package/dist/{graphlib-DZnBMcsX.js → graphlib-D18eZCT4.js} +10 -10
  99. package/dist/hasIn-B9AbGLj3.js +86 -0
  100. package/dist/{info-VBDWY6EO-Bzsods6X.js → info-VBDWY6EO-CwyXEo8E.js} +2 -2
  101. package/dist/{infoDiagram-HS3SLOUP-Cmxo6jKx.js → infoDiagram-HS3SLOUP-BXGbfBss.js} +12 -12
  102. package/dist/{isArrayLikeObject-Btu-i6_P.js → isArrayLikeObject-BrYl-ETg.js} +25 -26
  103. package/dist/{isEmpty-CZvUtYFp.js → isEmpty-C-xMag79.js} +2 -2
  104. package/dist/{isString-CBr7TEb7.js → isString-D-vNYDBA.js} +1 -1
  105. package/dist/{isSymbol-BuQsMXhk.js → isSymbol-Dyt2NSnN.js} +1 -1
  106. package/dist/{journeyDiagram-XKPGCS4Q-CKYr8cSR.js → journeyDiagram-XKPGCS4Q-D5BIjS4N.js} +3 -3
  107. package/dist/{kanban-definition-3W4ZIXB7-DVvAZzQD.js → kanban-definition-3W4ZIXB7-DhDkqxFB.js} +7 -7
  108. package/dist/{label-CV0KYhtH.js → label-BLDcDYdI.js} +6 -6
  109. package/dist/{loader-eJCvvApN.js → loader-DsE3MiYo.js} +2 -2
  110. package/dist/main.js +1673 -1163
  111. package/dist/{memoize-P1T1IGb9.js → memoize-Cs8aS5RW.js} +1 -1
  112. package/dist/merge-NuyC7LN7.js +51 -0
  113. package/dist/{mermaid-COOB_abB.js → mermaid-DkdSmFY8.js} +42 -42
  114. package/dist/{mermaid-parser.core-Cd-wu4tE.js → mermaid-parser.core-OkWZ8nr-.js} +8 -8
  115. package/dist/{min-CMDDtXJP.js → min-ECVRnCdn.js} +30 -30
  116. package/dist/{mindmap-definition-VGOIOE7T-1ExmnvYy.js → mindmap-definition-VGOIOE7T-BxQi78Vl.js} +9 -9
  117. package/dist/{now-BxlRp0OQ.js → now-BC2mX0ZT.js} +1 -1
  118. package/dist/{packet-DYOGHKS2-Bf1CvFco.js → packet-DYOGHKS2-C62XQjZh.js} +2 -2
  119. package/dist/{pie-VRWISCQL-LY_wbqji.js → pie-VRWISCQL-nfAKQJw3.js} +2 -2
  120. package/dist/{pieDiagram-ADFJNKIX-CJlIsdsU.js → pieDiagram-ADFJNKIX-DfSJXUHa.js} +13 -13
  121. package/dist/{purify.es-CyOIw8ru.js → purify.es-DGenX2XH.js} +67 -67
  122. package/dist/{quadrantDiagram-AYHSOK5B-BU78RiaH.js → quadrantDiagram-AYHSOK5B-CAcVWXc-.js} +2 -2
  123. package/dist/{radar-ZZBFDIW7-Ro3iXZCk.js → radar-ZZBFDIW7-lopS8_4j.js} +2 -2
  124. package/dist/{range-Dh0_-r8P.js → range-BKaWvVUE.js} +8 -8
  125. package/dist/reduce-CqQo8ppc.js +275 -0
  126. package/dist/{requirementDiagram-UZGBJVZJ-DACHtrFr.js → requirementDiagram-UZGBJVZJ-BU7dwzFM.js} +9 -9
  127. package/dist/{sankeyDiagram-TZEHDZUN-Bzg7_UWs.js → sankeyDiagram-TZEHDZUN-BVJnR4_b.js} +2 -2
  128. package/dist/{sequenceDiagram-WL72ISMW-agybEe9J.js → sequenceDiagram-WL72ISMW-CQcFQTwX.js} +4 -4
  129. package/dist/{slides-component-B0yK5GXP.js → slides-component-DwvL_HJi.js} +2 -2
  130. package/dist/{spec-Dq_reDGM.js → spec-CbYkiXG3.js} +5 -5
  131. package/dist/{stateDiagram-FKZM4ZOC-DehQAt8g.js → stateDiagram-FKZM4ZOC-Dx9AIGDe.js} +12 -12
  132. package/dist/{stateDiagram-v2-4FDKWEC3-8VzeREl9.js → stateDiagram-v2-4FDKWEC3-BIeUs-Ed.js} +10 -10
  133. package/dist/style.css +1 -1
  134. package/dist/{timeline-definition-IT6M3QCI-CdCfdaCF.js → timeline-definition-IT6M3QCI-D8B3p7ID.js} +2 -2
  135. package/dist/{toNumber-By7s5JC_.js → toNumber-CbZ70FdN.js} +2 -2
  136. package/dist/{toString-Ckpb50uw.js → toString-DbIAWQpF.js} +2 -2
  137. package/dist/{tooltip-CL8m4f9y.js → tooltip-SPkubVH3.js} +3 -3
  138. package/dist/{treemap-GDKQZRPO-DRxfDG65.js → treemap-GDKQZRPO-CkR-5ai2.js} +2 -2
  139. package/dist/{types-BwnzGcE4.js → types-0FB-N7AA.js} +519 -408
  140. package/dist/{uniq-cCc07Q8K.js → uniq-H2E5nMLq.js} +1 -1
  141. package/dist/{useAsyncData-B4hMFGnF.js → useAsyncData-D7-oahg5.js} +1 -1
  142. package/dist/{useDeepCompareMemoize-DuPhOXzr.js → useDeepCompareMemoize-DLS-bHHT.js} +5 -5
  143. package/dist/{useIframeCapabilities-CAt6D2EI.js → useIframeCapabilities-DFGZKWkO.js} +1 -1
  144. package/dist/{useTheme-BNYQnvu-.js → useTheme-D0rdoMBF.js} +6 -5
  145. package/dist/{vega-component-DouPy8AI.js → vega-component-CnO3mkFC.js} +11 -11
  146. package/dist/{xychartDiagram-PRI3JC2R-rEm_SIsC.js → xychartDiagram-PRI3JC2R-XO8FiQjU.js} +5 -5
  147. package/package.json +9 -9
  148. package/src/__mocks__/common.ts +41 -8
  149. package/src/__mocks__/requests.ts +1 -0
  150. package/src/components/chat/__tests__/useFileState.test.tsx +2 -3
  151. package/src/components/chat/acp/__tests__/context-utils.test.ts +2 -6
  152. package/src/components/datasources/components.tsx +3 -6
  153. package/src/components/datasources/datasources.tsx +8 -21
  154. package/src/components/editor/__tests__/data-attributes.test.tsx +2 -11
  155. package/src/components/editor/actions/types.ts +6 -1
  156. package/src/components/editor/actions/useNotebookActions.tsx +50 -13
  157. package/src/components/editor/cell/cell-context-menu.tsx +2 -6
  158. package/src/components/editor/chrome/types.ts +17 -0
  159. package/src/components/editor/connections/add-connection-dialog.tsx +27 -2
  160. package/src/components/editor/connections/database/__tests__/__snapshots__/as-code.test.ts.snap +105 -6
  161. package/src/components/editor/connections/database/__tests__/as-code.test.ts +101 -8
  162. package/src/components/editor/connections/database/as-code.ts +115 -25
  163. package/src/components/editor/connections/database/schemas.ts +49 -2
  164. package/src/components/editor/connections/storage/as-code.ts +1 -1
  165. package/src/components/editor/controls/command-palette.tsx +7 -0
  166. package/src/components/editor/controls/keyboard-shortcuts.tsx +3 -1
  167. package/src/components/editor/file-tree/__tests__/requesting-tree.test.ts +2 -3
  168. package/src/components/editor/file-tree/file-explorer.tsx +48 -62
  169. package/src/components/editor/file-tree/file-icons.tsx +132 -0
  170. package/src/components/editor/file-tree/file-viewer.tsx +1 -1
  171. package/src/components/editor/file-tree/tree-actions.tsx +107 -0
  172. package/src/components/editor/file-tree/types.ts +2 -96
  173. package/src/components/editor/header/filename-input.tsx +4 -1
  174. package/src/components/editor/navigation/__tests__/clipboard.test.ts +2 -4
  175. package/src/components/editor/output/console/ConsoleOutput.tsx +51 -2
  176. package/src/components/editor/output/console/__tests__/ConsoleOutput.test.tsx +97 -16
  177. package/src/components/editor/package-alert.tsx +4 -0
  178. package/src/components/icons/marimo-icons.tsx +2 -2
  179. package/src/components/pages/home-page.tsx +5 -5
  180. package/src/components/storage/__tests__/storage-snippets.test.ts +253 -0
  181. package/src/components/storage/components.tsx +0 -38
  182. package/src/components/storage/storage-file-viewer.tsx +1 -1
  183. package/src/components/storage/storage-inspector.tsx +66 -51
  184. package/src/components/storage/storage-snippets.ts +67 -0
  185. package/src/components/ui/command.tsx +2 -0
  186. package/src/core/ai/tools/__tests__/run-cells-tool.test.ts +206 -0
  187. package/src/core/ai/tools/run-cells-tool.ts +75 -40
  188. package/src/core/alerts/state.ts +1 -0
  189. package/src/core/cells/__tests__/cells.test.ts +62 -0
  190. package/src/core/cells/__tests__/session.test.ts +2 -7
  191. package/src/core/cells/cells.ts +25 -3
  192. package/src/core/cells/ids.ts +2 -1
  193. package/src/core/codemirror/compat/__tests__/jupyter.test.ts +2 -3
  194. package/src/core/codemirror/keymaps/vim.ts +32 -3
  195. package/src/core/codemirror/markdown/__tests__/commands.test.ts +2 -3
  196. package/src/core/config/__tests__/config-schema.test.ts +6 -2
  197. package/src/core/config/config-schema.ts +1 -0
  198. package/src/core/config/feature-flag.tsx +1 -1
  199. package/src/core/dom/ui-element-constants.ts +15 -0
  200. package/src/core/dom/ui-element.ts +3 -2
  201. package/src/core/export/__tests__/hooks.test.ts +3 -10
  202. package/src/core/hotkeys/__tests__/hotkeys.test.ts +64 -1
  203. package/src/core/hotkeys/hotkeys.ts +29 -3
  204. package/src/core/islands/bridge.ts +1 -0
  205. package/src/core/islands/components/web-components.tsx +2 -1
  206. package/src/core/network/__tests__/requests-network.test.ts +17 -0
  207. package/src/core/network/requests-lazy.ts +1 -0
  208. package/src/core/network/requests-network.ts +9 -0
  209. package/src/core/network/requests-static.ts +1 -0
  210. package/src/core/network/requests-toasting.tsx +1 -0
  211. package/src/core/network/types.ts +1 -0
  212. package/src/core/runtime/__tests__/runtime.test.ts +2 -8
  213. package/src/core/storage/__tests__/state.test.ts +1 -0
  214. package/src/core/wasm/bridge.ts +1 -0
  215. package/src/core/websocket/useMarimoKernelConnection.tsx +2 -0
  216. package/src/plugins/impl/DataTablePlugin.tsx +53 -3
  217. package/src/plugins/impl/FileBrowserPlugin.tsx +8 -5
  218. package/src/plugins/impl/__tests__/DataTablePlugin.test.tsx +2 -11
  219. package/src/plugins/impl/__tests__/DropdownPlugin.test.tsx +2 -11
  220. package/src/plugins/impl/anywidget/AnyWidgetPlugin.tsx +4 -1
  221. package/src/plugins/impl/data-explorer/ConnectedDataExplorerComponent.tsx +8 -1
  222. package/src/plugins/impl/mpl-interactive/MplInteractivePlugin.tsx +309 -0
  223. package/src/plugins/impl/mpl-interactive/__tests__/mpl-websocket-shim.test.ts +110 -0
  224. package/src/plugins/impl/mpl-interactive/mpl-websocket-shim.ts +57 -0
  225. package/src/plugins/impl/plotly/PlotlyPlugin.tsx +8 -2
  226. package/src/plugins/impl/vega/vega-component.tsx +7 -1
  227. package/src/plugins/impl/vega/vega.css +4 -11
  228. package/src/plugins/plugins.ts +2 -0
  229. package/src/utils/__tests__/copy.test.ts +129 -0
  230. package/src/utils/__tests__/download.test.tsx +12 -14
  231. package/src/utils/__tests__/filenames.test.ts +7 -0
  232. package/src/utils/__tests__/smartMatch.test.ts +61 -0
  233. package/src/utils/copy.ts +43 -0
  234. package/src/utils/filenames.ts +3 -0
  235. package/src/utils/smartMatch.ts +62 -0
  236. package/dist/_baseProperty-D1nWkRMz.js +0 -93
  237. package/dist/assets/__vite-browser-external-WSlCcXn_.js +0 -1
  238. package/dist/dist-BAeGo2rp.js +0 -5
  239. package/dist/dist-BqwCMSEa.js +0 -5
  240. package/dist/dist-Bum8FwTO.js +0 -6
  241. package/dist/dist-C0YiOwt_.js +0 -5
  242. package/dist/dist-C2uPv4iU.js +0 -5
  243. package/dist/dist-C5hOLsJN.js +0 -8
  244. package/dist/dist-C9NIAKMs.js +0 -8
  245. package/dist/dist-CCrzTtvk.js +0 -5
  246. package/dist/dist-CFS9i1rS.js +0 -8
  247. package/dist/dist-CyHZuhPH.js +0 -5
  248. package/dist/dist-CzcjWdIk.js +0 -6
  249. package/dist/dist-DaYyUSNC.js +0 -5
  250. package/dist/dist-DpDcJYNh.js +0 -8
  251. package/dist/dist-U_BfxcPn.js +0 -5
  252. package/dist/merge-CGQkMGzr.js +0 -51
  253. package/dist/reduce-BXFHs7IQ.js +0 -268
@@ -24,6 +24,11 @@ import type { CopilotMode } from "./registry";
24
24
  const POST_EXECUTION_DELAY = 200;
25
25
  const WAIT_FOR_CELLS_TIMEOUT = 30_000;
26
26
 
27
+ // Output size limits to prevent exceeding LLM token limits.
28
+ const MAX_TEXT_OUTPUT_CHARS = 2000;
29
+ const MAX_ERROR_OUTPUT_CHARS = 3000;
30
+ const MAX_TOOL_OUTPUT_CHARS = 40_000;
31
+
27
32
  interface CellOutput {
28
33
  consoleOutput?: string;
29
34
  cellOutput?: string;
@@ -92,7 +97,7 @@ export class RunStaleCellsTool
92
97
 
93
98
  await runCells({
94
99
  cellIds: staleCells,
95
- sendRun: sendRun,
100
+ sendRun,
96
101
  prepareForRun,
97
102
  notebook,
98
103
  });
@@ -116,44 +121,59 @@ export class RunStaleCellsTool
116
121
  const updatedNotebook = store.get(notebookAtom);
117
122
 
118
123
  const cellsToOutput = new Map<CellId, CellOutput | null>();
119
- let resultMessage = "";
120
124
  let outputHasErrors = false;
125
+ let hasAnyConsoleOutput = false;
126
+ let totalOutputChars = 0;
121
127
 
122
128
  for (const cellId of staleCells) {
123
129
  const cellContextData = getCellContextData(cellId, updatedNotebook, {
124
130
  includeConsoleOutput: true,
125
131
  });
126
132
 
127
- let cellOutputString: string | undefined;
128
- let consoleOutputString: string | undefined;
129
-
130
133
  const cellOutput = cellContextData.cellOutput;
131
134
  const consoleOutputs = cellContextData.consoleOutputs;
132
135
  const hasConsoleOutput = consoleOutputs && consoleOutputs.length > 0;
133
136
 
137
+ // Track errors regardless of budget
138
+ if (
139
+ (cellOutput && this.outputHasErrors(cellOutput)) ||
140
+ (hasConsoleOutput &&
141
+ consoleOutputs.some((output) => this.outputHasErrors(output)))
142
+ ) {
143
+ outputHasErrors = true;
144
+ }
145
+
134
146
  if (!cellOutput && !hasConsoleOutput) {
135
- // Set null to show no output
136
147
  cellsToOutput.set(cellId, null);
137
148
  continue;
138
149
  }
139
150
 
151
+ // If total budget exceeded, summarize remaining cells
152
+ if (totalOutputChars >= MAX_TOOL_OUTPUT_CHARS) {
153
+ cellsToOutput.set(cellId, {
154
+ cellOutput: "Cell executed (output omitted due to context limits).",
155
+ });
156
+ continue;
157
+ }
158
+
159
+ let cellOutputString: string | undefined;
160
+ let consoleOutputString: string | undefined;
161
+
140
162
  if (cellOutput) {
141
163
  cellOutputString = this.formatOutputString(cellOutput);
142
- if (this.outputHasErrors(cellOutput)) {
143
- outputHasErrors = true;
144
- }
164
+ totalOutputChars += cellOutputString.length;
145
165
  }
146
166
 
147
167
  if (hasConsoleOutput) {
168
+ hasAnyConsoleOutput = true;
148
169
  consoleOutputString = consoleOutputs
149
170
  .map((output) => this.formatOutputString(output))
150
171
  .join("\n");
151
- resultMessage +=
152
- "Console output represents the stdout or stderr of the cell (eg. print statements).";
153
-
154
- if (consoleOutputs.some((output) => this.outputHasErrors(output))) {
155
- outputHasErrors = true;
156
- }
172
+ consoleOutputString = this.truncateString(
173
+ consoleOutputString,
174
+ MAX_TEXT_OUTPUT_CHARS,
175
+ );
176
+ totalOutputChars += consoleOutputString.length;
157
177
  }
158
178
 
159
179
  cellsToOutput.set(cellId, {
@@ -179,43 +199,58 @@ export class RunStaleCellsTool
179
199
  return {
180
200
  status: "success",
181
201
  cellsToOutput: Object.fromEntries(cellsToOutput),
182
- message: resultMessage === "" ? undefined : resultMessage,
202
+ message: hasAnyConsoleOutput
203
+ ? "Console output represents the stdout or stderr of the cell (eg. print statements)."
204
+ : undefined,
183
205
  next_steps: nextSteps,
184
206
  };
185
207
  };
186
208
 
187
209
  private outputHasErrors(cellOutput: BaseOutput): boolean {
188
- const { output } = cellOutput;
189
- if (
190
- output.mimetype === "application/vnd.marimo+error" ||
191
- output.mimetype === "application/vnd.marimo+traceback"
192
- ) {
193
- return true;
194
- }
195
- return false;
210
+ return (
211
+ cellOutput.output.mimetype === "application/vnd.marimo+error" ||
212
+ cellOutput.output.mimetype === "application/vnd.marimo+traceback"
213
+ );
196
214
  }
197
215
 
198
216
  private formatOutputString(cellOutput: BaseOutput): string {
199
- let outputString = "";
200
217
  const { outputType, processedContent, imageUrl, output } = cellOutput;
201
- if (outputType === "text") {
202
- outputString += "Output:\n";
203
- if (processedContent) {
204
- outputString += processedContent;
205
- } else if (typeof output.data === "string") {
206
- outputString += output.data;
207
- } else {
208
- outputString += JSON.stringify(output.data);
209
- }
210
- } else if (outputType === "media") {
211
- outputString += `Media Output: Contains ${output.mimetype} content`;
212
- if (imageUrl) {
213
- outputString += `\nImage URL: ${imageUrl}`;
214
- }
218
+
219
+ if (outputType === "media") {
220
+ const base = `Media Output: Contains ${output.mimetype} content`;
221
+ return imageUrl ? `${base}\nImage URL: ${imageUrl}` : base;
215
222
  }
216
- return outputString;
223
+
224
+ if (output.mimetype === "text/html") {
225
+ // text/html (e.g. plotly figures, rich dataframes) can be millions of
226
+ // chars and is not interpretable by LLMs — summarize instead
227
+ const dataLength =
228
+ typeof output.data === "string"
229
+ ? output.data.length
230
+ : JSON.stringify(output.data).length;
231
+ return `HTML Output: text/html content (${dataLength.toLocaleString()} chars). Full output visible in notebook UI.`;
232
+ }
233
+
234
+ const maxChars = this.outputHasErrors(cellOutput)
235
+ ? MAX_ERROR_OUTPUT_CHARS
236
+ : MAX_TEXT_OUTPUT_CHARS;
237
+
238
+ let content = processedContent;
239
+ if (!content) {
240
+ content =
241
+ typeof output.data === "string"
242
+ ? output.data
243
+ : JSON.stringify(output.data);
244
+ }
245
+ return `Output:\n${this.truncateString(content, maxChars)}`;
217
246
  }
218
247
 
248
+ private truncateString(str: string, maxLength: number): string {
249
+ if (str.length <= maxLength) {
250
+ return str;
251
+ }
252
+ return `${str.slice(0, maxLength)}\n\n[TRUNCATED: ${str.length.toLocaleString()} → ${maxLength.toLocaleString()} chars. Full output visible in the notebook UI.]`;
253
+ }
219
254
  /**
220
255
  * Wait for cells to finish executing (status becomes "idle")
221
256
  * Returns true if all cells finished executing, false if the timeout was reached
@@ -11,6 +11,7 @@ export interface MissingPackageAlert {
11
11
  kind: "missing";
12
12
  packages: string[];
13
13
  isolated: boolean;
14
+ source?: "kernel" | "server";
14
15
  }
15
16
 
16
17
  export interface InstallingPackageAlert {
@@ -1439,6 +1439,68 @@ describe("cell reducer", () => {
1439
1439
  expect(state.cellData["5" as CellId]).not.toBeUndefined();
1440
1440
  });
1441
1441
 
1442
+ it("can set cell codes with names and configs", () => {
1443
+ const newIds = ["3", "4"] as CellId[];
1444
+ actions.setCellIds({ cellIds: newIds });
1445
+ actions.setCellCodes({
1446
+ codes: ["code1", "code2"],
1447
+ ids: newIds,
1448
+ codeIsStale: false,
1449
+ names: ["setup_cell", "analysis"],
1450
+ configs: [
1451
+ { hide_code: true, disabled: false, column: null },
1452
+ { hide_code: false, disabled: true, column: null },
1453
+ ],
1454
+ });
1455
+
1456
+ expect(state.cellData["3" as CellId].name).toBe("setup_cell");
1457
+ expect(state.cellData["3" as CellId].config.hide_code).toBe(true);
1458
+ expect(state.cellData["3" as CellId].config.disabled).toBe(false);
1459
+
1460
+ expect(state.cellData["4" as CellId].name).toBe("analysis");
1461
+ expect(state.cellData["4" as CellId].config.hide_code).toBe(false);
1462
+ expect(state.cellData["4" as CellId].config.disabled).toBe(true);
1463
+ });
1464
+
1465
+ it("can set cell codes without names/configs (backward compat)", () => {
1466
+ const newIds = ["3"] as CellId[];
1467
+ actions.setCellIds({ cellIds: newIds });
1468
+ actions.setCellCodes({
1469
+ codes: ["code1"],
1470
+ ids: newIds,
1471
+ codeIsStale: false,
1472
+ });
1473
+
1474
+ // Should use defaults when names/configs not provided
1475
+ expect(state.cellData["3" as CellId].code).toBe("code1");
1476
+ expect(state.cellData["3" as CellId].config.hide_code).toBe(false);
1477
+ expect(state.cellData["3" as CellId].config.disabled).toBe(false);
1478
+ });
1479
+
1480
+ it("can update names and configs on existing cells via setCellCodes", () => {
1481
+ // Set initial state
1482
+ actions.setCellCodes({
1483
+ codes: ["x = 1"],
1484
+ ids: [firstCellId],
1485
+ codeIsStale: false,
1486
+ names: ["old_name"],
1487
+ configs: [{ hide_code: false, disabled: false, column: null }],
1488
+ });
1489
+ expect(state.cellData[firstCellId].name).toBe("old_name");
1490
+ expect(state.cellData[firstCellId].config.hide_code).toBe(false);
1491
+
1492
+ // Update with new name and config (same code)
1493
+ actions.setCellCodes({
1494
+ codes: ["x = 1"],
1495
+ ids: [firstCellId],
1496
+ codeIsStale: true,
1497
+ names: ["new_name"],
1498
+ configs: [{ hide_code: true, disabled: false, column: null }],
1499
+ });
1500
+ expect(state.cellData[firstCellId].name).toBe("new_name");
1501
+ expect(state.cellData[firstCellId].config.hide_code).toBe(true);
1502
+ });
1503
+
1442
1504
  it("can fold and unfold all cells", () => {
1443
1505
  actions.foldAll();
1444
1506
  expect(foldAllBulk).toHaveBeenCalled();
@@ -3,6 +3,7 @@
3
3
  import type * as api from "@marimo-team/marimo-api";
4
4
  /* eslint-disable @typescript-eslint/no-explicit-any */
5
5
  import { beforeEach, describe, expect, it, vi } from "vitest";
6
+ import { Mocks } from "@/__mocks__/common";
6
7
  import { parseOutline } from "@/core/dom/outline";
7
8
  import { MultiColumn, visibleForTesting } from "@/utils/id-tree";
8
9
  import { invariant } from "@/utils/invariant";
@@ -11,13 +12,7 @@ import { type CellId, SETUP_CELL_ID } from "../ids";
11
12
  import { notebookStateFromSession } from "../session";
12
13
 
13
14
  // Mock dependencies
14
- vi.mock("@/utils/Logger", () => ({
15
- Logger: {
16
- error: vi.fn(),
17
- warn: vi.fn(),
18
- debug: vi.fn(),
19
- },
20
- }));
15
+ vi.mock("@/utils/Logger", () => ({ Logger: Mocks.quietLogger() }));
21
16
 
22
17
  vi.mock("@/core/dom/outline", () => ({
23
18
  parseOutline: vi.fn(),
@@ -3,7 +3,7 @@
3
3
  import { historyField } from "@codemirror/commands";
4
4
  import { type Atom, atom, useAtom, useAtomValue } from "jotai";
5
5
  import { atomFamily, selectAtom, splitAtom } from "jotai/utils";
6
- import { isEqual, zip } from "lodash-es";
6
+ import { isEqual } from "lodash-es";
7
7
  import { createRef, type ReducerWithoutAction } from "react";
8
8
  import type { CellHandle } from "@/components/editor/notebook-cell";
9
9
  import {
@@ -801,7 +801,13 @@ const {
801
801
  },
802
802
  setCellCodes: (
803
803
  state,
804
- action: { codes: string[]; ids: CellId[]; codeIsStale: boolean },
804
+ action: {
805
+ codes: string[];
806
+ ids: CellId[];
807
+ codeIsStale: boolean;
808
+ names?: string[];
809
+ configs?: CellConfig[];
810
+ },
805
811
  ) => {
806
812
  invariant(
807
813
  action.codes.length === action.ids.length,
@@ -814,15 +820,21 @@ const {
814
820
  cell,
815
821
  code,
816
822
  cellId,
823
+ name,
824
+ config,
817
825
  }: {
818
826
  cell: CellData | undefined;
819
827
  code: string;
820
828
  cellId: CellId;
829
+ name?: string;
830
+ config?: CellConfig;
821
831
  }) => {
822
832
  if (!cell) {
823
833
  return createCell({
824
834
  id: cellId,
825
835
  code,
836
+ name: name,
837
+ config: config,
826
838
  lastCodeRun: action.codeIsStale ? null : code,
827
839
  edited: action.codeIsStale && code.trim().length > 0,
828
840
  });
@@ -843,6 +855,8 @@ const {
843
855
  code: code,
844
856
  edited,
845
857
  lastCodeRun,
858
+ ...(name !== undefined && { name }),
859
+ ...(config !== undefined && { config }),
846
860
  };
847
861
  }
848
862
 
@@ -860,13 +874,19 @@ const {
860
874
  code: code,
861
875
  edited,
862
876
  lastCodeRun,
877
+ ...(name !== undefined && { name }),
878
+ ...(config !== undefined && { config }),
863
879
  };
864
880
  };
865
881
 
866
- for (const [cellId, code] of zip(action.ids, action.codes)) {
882
+ for (let i = 0; i < action.ids.length; i++) {
883
+ const cellId = action.ids[i];
884
+ const code = action.codes[i];
867
885
  if (cellId === undefined || code === undefined) {
868
886
  continue;
869
887
  }
888
+ const name = action.names?.[i];
889
+ const config = action.configs?.[i];
870
890
  nextState = {
871
891
  ...nextState,
872
892
  cellData: {
@@ -875,6 +895,8 @@ const {
875
895
  cell: nextState.cellData[cellId],
876
896
  code,
877
897
  cellId,
898
+ name,
899
+ config,
878
900
  }),
879
901
  },
880
902
  };
@@ -1,6 +1,7 @@
1
1
  /* Copyright 2026 Marimo. All rights reserved. */
2
2
  /* eslint-disable @typescript-eslint/no-redeclare */
3
3
 
4
+ import { OBJECT_ID_ATTR } from "@/core/dom/ui-element-constants";
4
5
  import { invariant } from "@/utils/invariant";
5
6
  import type { TypedString } from "../../utils/typed";
6
7
 
@@ -110,7 +111,7 @@ export function findCellId(element: HTMLElement): CellId | null {
110
111
  export type UIElementId = `${CellId}-${string}`;
111
112
  export const UIElementId = {
112
113
  parse(element: Element): UIElementId | null {
113
- return element.getAttribute("object-id") as UIElementId | null;
114
+ return element.getAttribute(OBJECT_ID_ATTR) as UIElementId | null;
114
115
  },
115
116
  parseOrThrow(element: Element): UIElementId {
116
117
  const id = UIElementId.parse(element);
@@ -3,6 +3,7 @@
3
3
  import { EditorState } from "@codemirror/state";
4
4
  import { EditorView } from "@codemirror/view";
5
5
  import { beforeEach, describe, expect, it, vi } from "vitest";
6
+ import { MockModules } from "@/__mocks__/common";
6
7
  import { MockRequestClient } from "@/__mocks__/requests";
7
8
  import { toast } from "@/components/ui/use-toast";
8
9
  import { store } from "@/core/state/jotai";
@@ -16,9 +17,7 @@ vi.mock("@/core/state/jotai", () => ({
16
17
  },
17
18
  }));
18
19
 
19
- vi.mock("@/components/ui/use-toast", () => ({
20
- toast: vi.fn(),
21
- }));
20
+ vi.mock("@/components/ui/use-toast", () => MockModules.toast());
22
21
 
23
22
  // Mock the helper to get request client
24
23
  const mockRequestClient = MockRequestClient.create();
@@ -247,6 +247,32 @@ function applyVimCommands(vimCommands: VimCommand[]) {
247
247
  }
248
248
  }
249
249
 
250
+ interface ExtendedVim {
251
+ getVimGlobalState_: () => {
252
+ macroModeState?: {
253
+ isRecording: boolean;
254
+ isPlaying: boolean;
255
+ };
256
+ };
257
+ }
258
+
259
+ function isExtendedVim(vim: typeof Vim): vim is typeof Vim & ExtendedVim {
260
+ return (
261
+ "getVimGlobalState_" in vim && typeof vim.getVimGlobalState_ === "function"
262
+ );
263
+ }
264
+
265
+ function isMacroActive() {
266
+ if (!isExtendedVim(Vim)) {
267
+ return false;
268
+ }
269
+ const macroModeState = Vim.getVimGlobalState_()?.macroModeState;
270
+ if (!macroModeState) {
271
+ return false;
272
+ }
273
+ return Boolean(macroModeState.isRecording || macroModeState.isPlaying);
274
+ }
275
+
250
276
  class CodeMirrorVimSync {
251
277
  private instances = new Set<EditorView>();
252
278
  private isBroadcasting = false;
@@ -275,10 +301,13 @@ class CodeMirrorVimSync {
275
301
  return;
276
302
  }
277
303
  invariant("mode" in e, 'Expected event to have a "mode" property');
304
+ const skipBroadcast = isMacroActive();
278
305
  this.isBroadcasting = true;
279
306
  // We use onIdle to keep the focused editor snappy
280
307
  onIdle(() => {
281
- this.broadcastModeChange(instance, e.mode, e.subMode);
308
+ if (!skipBroadcast) {
309
+ this.broadcastModeChange(instance, e.mode, e.subMode);
310
+ }
282
311
  this.isBroadcasting = false;
283
312
  });
284
313
  });
@@ -338,9 +367,9 @@ class CodeMirrorVimSync {
338
367
  }
339
368
  break;
340
369
  case "insert":
341
- // Only enter insert mode if we're not already in it
370
+ // only enter insert mode if we're not already in it
342
371
  if (!vim.insertMode) {
343
- Vim.handleKey(cm, "i", "");
372
+ Vim.handleKey(cm, "i", "mapping");
344
373
  }
345
374
  break;
346
375
  case "visual":
@@ -9,6 +9,7 @@ import {
9
9
  test,
10
10
  vi,
11
11
  } from "vitest";
12
+ import { MockModules } from "@/__mocks__/common";
12
13
  import { MockRequestClient } from "@/__mocks__/requests";
13
14
  import { requestClientAtom } from "@/core/network/requests";
14
15
  import { filenameAtom } from "@/core/saving/file-state";
@@ -25,9 +26,7 @@ import {
25
26
  insertUL,
26
27
  } from "../commands";
27
28
 
28
- vi.mock("@/components/ui/use-toast", () => ({
29
- toast: vi.fn(),
30
- }));
29
+ vi.mock("@/components/ui/use-toast", () => MockModules.toast());
31
30
 
32
31
  import { toast } from "@/components/ui/use-toast";
33
32
 
@@ -70,7 +70,9 @@ test("default UserConfig - empty", () => {
70
70
  "reference_highlighting": true,
71
71
  "theme": "light",
72
72
  },
73
- "experimental": {},
73
+ "experimental": {
74
+ "storage_inspector": true,
75
+ },
74
76
  "formatting": {
75
77
  "line_length": 79,
76
78
  },
@@ -140,7 +142,9 @@ test("default UserConfig - one level", () => {
140
142
  "reference_highlighting": true,
141
143
  "theme": "light",
142
144
  },
143
- "experimental": {},
145
+ "experimental": {
146
+ "storage_inspector": true,
147
+ },
144
148
  "formatting": {
145
149
  "line_length": 79,
146
150
  },
@@ -186,6 +186,7 @@ export const UserConfigSchema = z
186
186
  .looseObject({
187
187
  markdown: z.boolean().optional(),
188
188
  rtc: z.boolean().optional(),
189
+ storage_inspector: z.boolean().prefault(true),
189
190
  // Add new experimental features here
190
191
  })
191
192
  // Pass through so that we don't remove any extra keys that the user has added.
@@ -21,7 +21,7 @@ const defaultValues: ExperimentalFeatures = {
21
21
  rtc_v2: false,
22
22
  cache_panel: false,
23
23
  external_agents: import.meta.env.DEV,
24
- storage_inspector: false,
24
+ storage_inspector: true,
25
25
  };
26
26
 
27
27
  export function getFeatureFlag<T extends keyof ExperimentalFeatures>(
@@ -0,0 +1,15 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+
3
+ /**
4
+ * Stable identifier for a UI element, deterministic across re-executions
5
+ * of the same cell (based on cell ID + creation order).
6
+ * Used to synchronize multiple instances and kernel state.
7
+ */
8
+ export const OBJECT_ID_ATTR = "object-id";
9
+
10
+ /**
11
+ * Random token that changes every time a UI element is constructed
12
+ * (i.e., every cell execution). Used to detect stale elements and
13
+ * force re-renders when a cell re-runs.
14
+ */
15
+ export const RANDOM_ID_ATTR = "random-id";
@@ -8,6 +8,7 @@ import {
8
8
  MarimoValueInputEvent,
9
9
  type MarimoValueInputEventType,
10
10
  } from "./events";
11
+ import { RANDOM_ID_ATTR } from "./ui-element-constants";
11
12
  import { UI_ELEMENT_REGISTRY } from "./uiregistry";
12
13
 
13
14
  import "./ui-element.css";
@@ -189,7 +190,7 @@ export function initializeUIElement() {
189
190
  // used like a React key. If the random-id changes, we need to unmount and
190
191
  // remount its child.
191
192
  static get observedAttributes() {
192
- return ["random-id"];
193
+ return [RANDOM_ID_ATTR];
193
194
  }
194
195
 
195
196
  attributeChangedCallback(
@@ -199,7 +200,7 @@ export function initializeUIElement() {
199
200
  ) {
200
201
  if (this.initialized) {
201
202
  const hasChanged = oldValue !== newValue;
202
- if (name === "random-id" && hasChanged) {
203
+ if (name === RANDOM_ID_ATTR && hasChanged) {
203
204
  // deregister/clean-up this instance
204
205
  this.disconnectedCallback();
205
206
  // remove and re-add its child to force it to re-render; note that
@@ -5,6 +5,7 @@ import { createStore, Provider } from "jotai";
5
5
  import type { ReactNode } from "react";
6
6
  import * as React from "react";
7
7
  import { beforeEach, describe, expect, it, vi } from "vitest";
8
+ import { MockModules, Mocks } from "@/__mocks__/common";
8
9
  import type { CellId } from "@/core/cells/ids";
9
10
  import { CellOutputId } from "@/core/cells/ids";
10
11
  import type { CellRuntimeState } from "@/core/cells/types";
@@ -20,17 +21,9 @@ vi.mock("html-to-image", () => ({
20
21
  toPng: vi.fn(),
21
22
  }));
22
23
 
23
- // Mock Logger
24
- vi.mock("@/utils/Logger", () => ({
25
- Logger: {
26
- error: vi.fn(),
27
- },
28
- }));
24
+ vi.mock("@/utils/Logger", () => ({ Logger: Mocks.quietLogger() }));
29
25
 
30
- // Mock toast
31
- vi.mock("@/components/ui/use-toast", () => ({
32
- toast: vi.fn(),
33
- }));
26
+ vi.mock("@/components/ui/use-toast", () => MockModules.toast());
34
27
 
35
28
  // Mock cellsRuntimeAtom - must be defined inline in the factory function
36
29
  vi.mock("@/core/cells/cells", async () => {
@@ -1,6 +1,12 @@
1
1
  /* Copyright 2026 Marimo. All rights reserved. */
2
2
  import { describe, expect, it } from "vitest";
3
- import { type Hotkey, type HotkeyAction, HotkeyProvider } from "../hotkeys";
3
+ import {
4
+ type Hotkey,
5
+ type HotkeyAction,
6
+ HotkeyProvider,
7
+ normalizeKeyString,
8
+ OverridingHotkeyProvider,
9
+ } from "../hotkeys";
4
10
 
5
11
  /**
6
12
  * Just a helper.
@@ -72,3 +78,60 @@ describe("HotkeyProvider platform separation", () => {
72
78
  expect(linux.getHotkey("cell.format").key).toBe("Ctrl-Shift-L");
73
79
  });
74
80
  });
81
+
82
+ describe("normalizeKeyString", () => {
83
+ it("should capitalize multi-character base key names", () => {
84
+ expect(normalizeKeyString("Shift-enter")).toBe("Shift-Enter");
85
+ expect(normalizeKeyString("Cmd-enter")).toBe("Cmd-Enter");
86
+ expect(normalizeKeyString("Ctrl-backspace")).toBe("Ctrl-Backspace");
87
+ expect(normalizeKeyString("Alt-tab")).toBe("Alt-Tab");
88
+ expect(normalizeKeyString("Cmd-Shift-arrowUp")).toBe("Cmd-Shift-ArrowUp");
89
+ });
90
+
91
+ it("should leave already-correct key names unchanged", () => {
92
+ expect(normalizeKeyString("Shift-Enter")).toBe("Shift-Enter");
93
+ expect(normalizeKeyString("Cmd-Enter")).toBe("Cmd-Enter");
94
+ expect(normalizeKeyString("Mod-Shift-Enter")).toBe("Mod-Shift-Enter");
95
+ });
96
+
97
+ it("should leave single-character keys unchanged", () => {
98
+ expect(normalizeKeyString("Cmd-a")).toBe("Cmd-a");
99
+ expect(normalizeKeyString("Ctrl-Shift-z")).toBe("Ctrl-Shift-z");
100
+ expect(normalizeKeyString("a")).toBe("a");
101
+ });
102
+
103
+ it("should handle keys without modifiers", () => {
104
+ expect(normalizeKeyString("enter")).toBe("Enter");
105
+ expect(normalizeKeyString("Escape")).toBe("Escape");
106
+ expect(normalizeKeyString("F12")).toBe("F12");
107
+ });
108
+ });
109
+
110
+ describe("OverridingHotkeyProvider", () => {
111
+ it("should normalize lowercase key overrides", () => {
112
+ const provider = new OverridingHotkeyProvider(
113
+ {
114
+ "cell.run": "Shift-enter",
115
+ "cell.runAndNewBelow": "Cmd-enter",
116
+ },
117
+ { platform: "mac" },
118
+ );
119
+
120
+ expect(provider.getHotkey("cell.run").key).toBe("Shift-Enter");
121
+ expect(provider.getHotkey("cell.runAndNewBelow").key).toBe("Cmd-Enter");
122
+ });
123
+
124
+ it("should return defaults when no override is set", () => {
125
+ const provider = new OverridingHotkeyProvider({}, { platform: "mac" });
126
+ expect(provider.getHotkey("cell.run").key).toBe("Cmd-Enter");
127
+ expect(provider.getHotkey("cell.runAndNewBelow").key).toBe("Shift-Enter");
128
+ });
129
+
130
+ it("should pass through correctly-cased overrides unchanged", () => {
131
+ const provider = new OverridingHotkeyProvider(
132
+ { "cell.run": "Shift-Enter" },
133
+ { platform: "mac" },
134
+ );
135
+ expect(provider.getHotkey("cell.run").key).toBe("Shift-Enter");
136
+ });
137
+ });