@marimo-team/islands 0.25.3-dev13 → 0.25.3-dev135

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 (440) hide show
  1. package/dist/{ConnectedDataExplorerComponent-CNMliVzw.js → ConnectedDataExplorerComponent-BAiSVaHj.js} +17 -15
  2. package/dist/{ErrorBoundary-BWYjMPq8.js → ErrorBoundary-C1PyX4Kv.js} +16 -16
  3. package/dist/{abnfDiagram-VCTEODGH-D-Ez91gV.js → abnfDiagram-VCTEODGH-BBHN92-F.js} +2 -2
  4. package/dist/any-language-editor-3E24SnCV.js +75 -0
  5. package/dist/{architectureDiagram-5GKGNRK7-CTpcNtO8.js → architectureDiagram-5GKGNRK7-C0Rd64qh.js} +1 -1
  6. package/dist/{arrays-CN1djC4k.js → arrays-R6JsImiT.js} +8 -0
  7. package/dist/assets/__vite-browser-external-SaDYcg7x.js +1 -0
  8. package/dist/assets/{worker-HPY9fbhz.js → worker-CfME3uyi.js} +3 -2
  9. package/dist/{button-CvjkwdSk.js → button-BQRdqQi1.js} +199 -192
  10. package/dist/{capabilities-CQ5IjhzL.js → capabilities-CI4xUPsp.js} +2 -2
  11. package/dist/{chat-ui-DaHynk8g.js → chat-ui-DqcauBr6.js} +1142 -1141
  12. package/dist/{check-EDwSwP89.js → check-SpOMoCLP.js} +1 -1
  13. package/dist/{chunk-IMKFNOWR-DGxi-zeo.js → chunk-IMKFNOWR-hJXey5p2.js} +1 -1
  14. package/dist/{chunk-SHT3W25Y-CV9J3rAB.js → chunk-SHT3W25Y-JMzTmmVW.js} +1 -1
  15. package/dist/{chunk-SVP7TREG-BL-9Fqgg.js → chunk-SVP7TREG-BIyoHTXi.js} +1 -1
  16. package/dist/{chunk-TICWLB2K-Cu_XMjDM.js → chunk-TICWLB2K-CCzH2Uwa.js} +1 -1
  17. package/dist/{chunk-YOKDWASO-DPZk8vhS.js → chunk-YOKDWASO-DKplTrBY.js} +20 -20
  18. package/dist/{classDiagram-ZZMXUADV-BXXV13Hq.js → classDiagram-ZZMXUADV-DB1kOPS6.js} +2 -2
  19. package/dist/{classDiagram-v2-VYDZK3BY-B7FXJ1Cp.js → classDiagram-v2-VYDZK3BY-CPaFL0OB.js} +2 -2
  20. package/dist/{code-visibility-O0uJkFyz.js → code-visibility-BS43kDO4.js} +6 -6
  21. package/dist/{common-PAvTWnXO.js → common-Bi8KemMn.js} +6762 -6586
  22. package/dist/{copy-icon-Ft7t9bqz.js → copy-icon-C1trdvVt.js} +3 -3
  23. package/dist/{cynefinDiagram-5FMLGOSQ-CH96rwba.js → cynefinDiagram-5FMLGOSQ-CF93uliH.js} +1 -1
  24. package/dist/{dagre-GXQ25YYZ-BD3PmpFY.js → dagre-GXQ25YYZ-BBB-_p9D.js} +1 -1
  25. package/dist/{data-grid-overlay-editor-DeWySy0K.js → data-grid-overlay-editor-Cxje4LxB.js} +5 -5
  26. package/dist/{data-grid-types-BH_r7g7l.js → data-grid-types-0PJ-9N5n.js} +1 -1
  27. package/dist/{diagram-S7CK7UJ4-BsSJJldB.js → diagram-S7CK7UJ4-BynCnbyt.js} +1 -1
  28. package/dist/{diagram-UQ7AKVKN-muQtNZc4.js → diagram-UQ7AKVKN-svOxikMa.js} +1 -1
  29. package/dist/{diagram-VX7I27RA-CdhuZAqR.js → diagram-VX7I27RA-BVQSnPx0.js} +1 -1
  30. package/dist/{diagram-Z3DM3KII-BD8mXjOj.js → diagram-Z3DM3KII-nR1J0BH5.js} +1 -1
  31. package/dist/{dialog-CEBFZNyc.js → dialog-DgeqyrEZ.js} +3 -3
  32. package/dist/{dist-DpBpA_n1.js → dist-Lm-D-Vr6.js} +3 -3
  33. package/dist/{dropdown-menu-CP_qCYHC.js → dropdown-menu-CgL3fm8P.js} +1262 -1229
  34. package/dist/{ebnfDiagram-PWID7BFC-BIN1sweE.js → ebnfDiagram-PWID7BFC-C9tq9-Qo.js} +2 -2
  35. package/dist/{erDiagram-RLTQ6QDP-Ctp5cfoN.js → erDiagram-RLTQ6QDP-BiaeB7Fi.js} +1 -1
  36. package/dist/{error-banner-D-WCIiMt.js → error-banner-D40pmfQQ.js} +83 -83
  37. package/dist/{error-entries-Bv6bZPF3.js → error-entries-CmFDQ9CA.js} +1018 -977
  38. package/dist/{feedback-button-WbN_sdUE.js → feedback-button-n0-eDhBU.js} +23 -23
  39. package/dist/{flowDiagram-HODETNUW-3EfLrlFb.js → flowDiagram-HODETNUW-f-sLm87k.js} +2 -2
  40. package/dist/{formats-27CW_UP4.js → formats-CCxaLPNs.js} +3 -3
  41. package/dist/{ganttDiagram-EL5Y4UJY-BQT16JSc.js → ganttDiagram-EL5Y4UJY-Dh_4cBJZ.js} +2 -2
  42. package/dist/{glide-data-editor-_oc1WE9x.js → glide-data-editor-BQBctTSS.js} +370 -368
  43. package/dist/{glide-utils-DJyqh0EM.js → glide-utils-Dz_F4pAY.js} +16 -16
  44. package/dist/{globals-C-fUiztk.js → globals-C9s-sVIk.js} +3 -3
  45. package/dist/{highlighted-body-KPVGNVTW-zwjq21tf.js → highlighted-body-KPVGNVTW-3Ziwfz-V.js} +2 -2
  46. package/dist/{html-to-image-Bf8QMGi4.js → html-to-image-CfgsWObF.js} +630 -593
  47. package/dist/{infoDiagram-27XIBGKW-DipQoHxl.js → infoDiagram-27XIBGKW-BHrWywBe.js} +1 -1
  48. package/dist/{ishikawaDiagram-5VMMS53U-DgFMjSGz.js → ishikawaDiagram-5VMMS53U-Da4s0Reg.js} +1 -1
  49. package/dist/{kanban-definition-UXKFOSKX-w1ACVjgl.js → kanban-definition-UXKFOSKX-DDf1GVLj.js} +1 -1
  50. package/dist/{label-BDKF3Afc.js → label-D9_-zSg-.js} +3 -3
  51. package/dist/{linear-Cnaq3Tfb.js → linear-DWERh7eI.js} +121 -73
  52. package/dist/{loader-BQOfjTIJ.js → loader-DOVB0Syl.js} +12 -12
  53. package/dist/main.js +7441 -7115
  54. package/dist/{mermaid-CHo49Yv-.js → mermaid-ClwTt7OD.js} +38 -38
  55. package/dist/mermaid-HWGCJPDP-Du3-HPQA.js +2 -0
  56. package/dist/{mindmap-definition-YA3MSWOX-CrT0KSfX.js → mindmap-definition-YA3MSWOX-D60oMZtT.js} +1 -1
  57. package/dist/{number-overlay-editor-DdPkN3h1.js → number-overlay-editor-Bp1CMtol.js} +3 -3
  58. package/dist/{objects-CzUgoMIF.js → objects-DCmMvZtn.js} +1 -3
  59. package/dist/{once-BtE-wKRL.js → once-eG8Vmvjd.js} +1 -1
  60. package/dist/{pegDiagram-XKGWAZYB-BqvywNJA.js → pegDiagram-XKGWAZYB-B9wFpMga.js} +2 -2
  61. package/dist/{pieDiagram-E7YTZNPT-BfxJ_XCc.js → pieDiagram-E7YTZNPT-BZwvMuBT.js} +1 -1
  62. package/dist/precisionRound-DhAzeRcm.js +49 -0
  63. package/dist/{process-output-BZg56CCb.js → process-output-B64ZYWuI.js} +5 -5
  64. package/dist/{quadrantDiagram-AXDQQJYC-Ccw0xMjN.js → quadrantDiagram-AXDQQJYC-Blrm7V7_.js} +1 -1
  65. package/dist/{railroadDiagram-O6MQD6OU-8vFLVTPu.js → railroadDiagram-O6MQD6OU-DqhZGsN2.js} +2 -2
  66. package/dist/{react-vega-BED0DjiB.js → react-vega-C4T9Rb_w.js} +12284 -12270
  67. package/dist/{requirementDiagram-BXWQKSXE-a_MxGvxe.js → requirementDiagram-BXWQKSXE-CL5dYARV.js} +1 -1
  68. package/dist/{reveal-component-Bs9X_N3n.js → reveal-component-C2qjH1P6.js} +14 -14
  69. package/dist/{select-BButFi-d.js → select-CkF_GBxO.js} +537 -458
  70. package/dist/{sequenceDiagram-WJ2MYXX4-CXFMnxGh.js → sequenceDiagram-WJ2MYXX4-BSUCRd2x.js} +1 -1
  71. package/dist/{spec-BwarBpUJ.js → spec-DiSh_qJC.js} +41 -11
  72. package/dist/{stateDiagram-D77RDMKH-DQeA_CwI.js → stateDiagram-D77RDMKH-B-1Hvyb0.js} +2 -2
  73. package/dist/{stateDiagram-v2-MP3YSRHH-BxxQucm1.js → stateDiagram-v2-MP3YSRHH--HtiR4yB.js} +2 -2
  74. package/dist/style.css +1 -1
  75. package/dist/{swimlanes-42K2YHIH-Do6L-cYg.js → swimlanes-42K2YHIH-Bdfhrd-2.js} +1 -1
  76. package/dist/{swimlanesDiagram-VR7AAH4N-DUzPvSVU.js → swimlanesDiagram-VR7AAH4N-BJIfxSz0.js} +2 -2
  77. package/dist/{swiper-component-DKntyn8p.js → swiper-component-BkCm-kLg.js} +2 -2
  78. package/dist/{time-BieKwdYS.js → time-BaMbJs4z.js} +18 -18
  79. package/dist/{timeline-definition-24CTP7MA-D3c46WM5.js → timeline-definition-24CTP7MA-Cs4EfFD3.js} +1 -1
  80. package/dist/{toDate-UtiwmnH2.js → toDate-DGvRHwTk.js} +19 -18
  81. package/dist/{toast-B9UMEi8u.js → toast-Btbve3Lm.js} +231 -213
  82. package/dist/{tooltip-BO1REbqs.js → tooltip-CJ4Rqasp.js} +327 -303
  83. package/dist/{use-restore-focus-BEM7sFWl.js → use-restore-focus-CmGwRNId.js} +1482 -1433
  84. package/dist/{useAsyncData-Deh5kszT.js → useAsyncData-BmVgnMP5.js} +15 -14
  85. package/dist/{useDateFormatter-D8XruNGG.js → useDateFormatter-eCL_SdeG.js} +2 -2
  86. package/dist/{useDeepCompareMemoize-DXwzRINI.js → useDeepCompareMemoize-Ch_AeGu6.js} +1 -1
  87. package/dist/{useIframeCapabilities-DBETvvPl.js → useIframeCapabilities-CSdWn6NP.js} +1 -1
  88. package/dist/{useLifecycle-j7D9uD1o.js → useLifecycle-B5BX921H.js} +3 -3
  89. package/dist/{useTheme-CRnjkku0.js → useTheme-Bq9pi_vz.js} +6 -6
  90. package/dist/{vega-component-BeF9dfyO.js → vega-component-BgyZ-COt.js} +147 -147
  91. package/dist/{vega-embed-container-BNHE18G9.js → vega-embed-container--LYZqguO.js} +2 -2
  92. package/dist/{vega-loader.browser-DNST6uVf.js → vega-loader.browser-B9Pz0EGP.js} +298 -507
  93. package/dist/{vennDiagram-4TSXK5OY-BSZD8JcZ.js → vennDiagram-4TSXK5OY-DRu0vRjO.js} +1 -1
  94. package/dist/{wardleyDiagram-VM6X3IG4-48cUWkfA.js → wardleyDiagram-VM6X3IG4-DzbDxwDY.js} +1 -1
  95. package/dist/{xychartDiagram-S5SC5T6Z-CswYgYeu.js → xychartDiagram-S5SC5T6Z-CLWj6ZFQ.js} +2 -2
  96. package/package.json +14 -14
  97. package/src/__tests__/host-imports.test.ts +1 -0
  98. package/src/__tests__/module-entry.test.ts +32 -0
  99. package/src/__tests__/mount.test.ts +0 -20
  100. package/src/components/ai/__tests__/ai-model-dropdown.test.tsx +63 -0
  101. package/src/components/ai/ai-model-dropdown.tsx +11 -6
  102. package/src/components/app-config/ai-config.tsx +7 -2
  103. package/src/components/chat/__tests__/message-queue.test.tsx +17 -0
  104. package/src/components/chat/acp/__tests__/session-tabs.test.tsx +43 -0
  105. package/src/components/chat/acp/__tests__/state.test.ts +2 -2
  106. package/src/components/chat/acp/blocks.tsx +3 -2
  107. package/src/components/chat/acp/session-tabs.tsx +8 -3
  108. package/src/components/chat/chat-components.tsx +2 -0
  109. package/src/components/chat/chat-panel.tsx +9 -8
  110. package/src/components/chat/chat-utils.ts +4 -8
  111. package/src/components/data-table/TableTopBar.tsx +2 -0
  112. package/src/components/data-table/__tests__/chart-spec-model.test.ts +195 -195
  113. package/src/components/data-table/__tests__/column-explorer.test.tsx +27 -1
  114. package/src/components/data-table/__tests__/columns.test.tsx +105 -0
  115. package/src/components/data-table/__tests__/context-menu-interaction.test.tsx +130 -0
  116. package/src/components/data-table/__tests__/data-table.test.tsx +1 -0
  117. package/src/components/data-table/__tests__/date-popover.test.tsx +72 -0
  118. package/src/components/data-table/__tests__/export-actions.test.tsx +105 -0
  119. package/src/components/data-table/__tests__/keyboard-selection.test.tsx +188 -0
  120. package/src/components/data-table/__tests__/search-highlight.test.tsx +109 -0
  121. package/src/components/data-table/__tests__/url-detector.test.tsx +74 -0
  122. package/src/components/data-table/cell-selection/feature.ts +2 -2
  123. package/src/components/data-table/cell-selection/types.ts +2 -2
  124. package/src/components/data-table/charts/__tests__/chart-panel.test.tsx +74 -1
  125. package/src/components/data-table/charts/__tests__/spec-snapshot.test.ts +20 -20
  126. package/src/components/data-table/charts/__tests__/spec.test.ts +78 -52
  127. package/src/components/data-table/charts/__tests__/storage.test.ts +4 -1
  128. package/src/components/data-table/charts/chart-spec/spec.ts +68 -43
  129. package/src/components/data-table/charts/charts.tsx +40 -30
  130. package/src/components/data-table/charts/storage.ts +16 -5
  131. package/src/components/data-table/column-explorer-panel/column-explorer.tsx +1 -1
  132. package/src/components/data-table/column-summary/chart-spec-model.tsx +23 -16
  133. package/src/components/data-table/column-summary/column-summary.tsx +2 -4
  134. package/src/components/data-table/column-visibility-dropdown.tsx +8 -13
  135. package/src/components/data-table/columns.tsx +45 -46
  136. package/src/components/data-table/context-menu.tsx +36 -20
  137. package/src/components/data-table/data-table.tsx +13 -1
  138. package/src/components/data-table/date-popover.tsx +6 -4
  139. package/src/components/data-table/export-actions.tsx +76 -48
  140. package/src/components/data-table/range-focus/atoms.ts +4 -6
  141. package/src/components/data-table/range-focus/use-cell-range-selection.ts +65 -38
  142. package/src/components/data-table/range-focus/use-scroll-into-view.ts +21 -2
  143. package/src/components/data-table/renderers.tsx +16 -7
  144. package/src/components/data-table/row-viewer-panel/__tests__/row-viewer.test.tsx +24 -1
  145. package/src/components/data-table/row-viewer-panel/row-viewer.tsx +4 -4
  146. package/src/components/data-table/search-highlight.tsx +79 -0
  147. package/src/components/data-table/types.ts +1 -0
  148. package/src/components/data-table/url-detector.tsx +49 -9
  149. package/src/components/datasources/__tests__/column-actions.test.tsx +97 -0
  150. package/src/components/datasources/__tests__/column-preview.test.tsx +98 -5
  151. package/src/components/datasources/__tests__/datasources-interactions.test.tsx +181 -0
  152. package/src/components/datasources/__tests__/table-list.test.tsx +118 -0
  153. package/src/components/datasources/__tests__/utils.test.ts +57 -6
  154. package/src/components/datasources/column-preview.tsx +13 -4
  155. package/src/components/datasources/datasources.tsx +51 -39
  156. package/src/components/datasources/utils.ts +32 -23
  157. package/src/components/debugger/debugger-code.tsx +5 -0
  158. package/src/components/dependency-graph/dependency-graph-tree.tsx +8 -8
  159. package/src/components/dependency-graph/elements.ts +24 -21
  160. package/src/components/dependency-graph/panels.tsx +2 -1
  161. package/src/components/dependency-graph/utils/__tests__/layout.test.ts +18 -11
  162. package/src/components/editor/SortableCell.tsx +1 -3
  163. package/src/components/editor/__tests__/dynamic-favicon.test.tsx +111 -18
  164. package/src/components/editor/__tests__/package-alert.test.tsx +22 -1
  165. package/src/components/editor/actions/__tests__/pair-with-agent-modal.test.tsx +24 -0
  166. package/src/components/editor/actions/pair-with-agent-modal.tsx +18 -15
  167. package/src/components/editor/ai/add-cell-with-ai.tsx +3 -3
  168. package/src/components/editor/ai/ai-completion-editor.tsx +1 -0
  169. package/src/components/editor/alerts/startup-logs-alert.tsx +1 -0
  170. package/src/components/editor/cell/CreateCellButton.tsx +8 -10
  171. package/src/components/editor/cell/DeleteButton.tsx +1 -0
  172. package/src/components/editor/cell/__tests__/useShouldShowInterrupt.test.ts +39 -0
  173. package/src/components/editor/cell/code/__tests__/language-toggle.test.tsx +54 -0
  174. package/src/components/editor/cell/code/cell-editor.tsx +1 -1
  175. package/src/components/editor/cell/code/language-toggle.tsx +8 -7
  176. package/src/components/editor/cell/collapse.tsx +5 -3
  177. package/src/components/editor/cell/useShouldShowInterrupt.ts +2 -12
  178. package/src/components/editor/chrome/panels/__tests__/packages-panel.test.tsx +30 -1
  179. package/src/components/editor/chrome/panels/cache-panel.tsx +1 -0
  180. package/src/components/editor/chrome/panels/outline/__tests__/outline-keyboard.test.tsx +84 -0
  181. package/src/components/editor/chrome/panels/outline/floating-outline.tsx +39 -14
  182. package/src/components/editor/chrome/panels/outline/useActiveOutline.tsx +3 -2
  183. package/src/components/editor/chrome/panels/packages-panel.tsx +4 -7
  184. package/src/components/editor/chrome/panels/secrets-panel.tsx +1 -0
  185. package/src/components/editor/chrome/panels/snippets-panel.tsx +5 -1
  186. package/src/components/editor/chrome/wrapper/__tests__/attachment-status.test.tsx +12 -18
  187. package/src/components/editor/chrome/wrapper/__tests__/pending-ai-cells.test.tsx +58 -1
  188. package/src/components/editor/chrome/wrapper/footer-items/runtime-settings.tsx +1 -7
  189. package/src/components/editor/chrome/wrapper/pending-ai-cells.tsx +12 -2
  190. package/src/components/editor/chrome/wrapper/storage.ts +1 -1
  191. package/src/components/editor/connections/components.tsx +1 -1
  192. package/src/components/editor/controls/__tests__/command-palette.test.tsx +53 -0
  193. package/src/components/editor/controls/__tests__/keyboard-shortcuts.test.tsx +61 -0
  194. package/src/components/editor/controls/command-palette.tsx +22 -10
  195. package/src/components/editor/controls/keyboard-shortcuts.tsx +14 -5
  196. package/src/components/editor/dynamic-favicon.tsx +21 -40
  197. package/src/components/editor/errors/__tests__/send-error-report-button.test.tsx +12 -23
  198. package/src/components/editor/file-tree/__tests__/file-explorer-interactions.test.tsx +6 -0
  199. package/src/components/editor/file-tree/__tests__/file-explorer-navigation.test.tsx +47 -0
  200. package/src/components/editor/file-tree/__tests__/use-file-search.test.tsx +91 -0
  201. package/src/components/editor/file-tree/file-explorer.tsx +45 -8
  202. package/src/components/editor/file-tree/file-name-input.tsx +1 -0
  203. package/src/components/editor/file-tree/file-viewer.tsx +4 -2
  204. package/src/components/editor/file-tree/use-file-search.ts +35 -21
  205. package/src/components/editor/header/status.tsx +28 -5
  206. package/src/components/editor/links/__tests__/cell-link.test.tsx +146 -0
  207. package/src/components/editor/links/cell-link.tsx +14 -5
  208. package/src/components/editor/navigation/focus-utils.ts +4 -4
  209. package/src/components/editor/notebook-banner.tsx +1 -0
  210. package/src/components/editor/notebook-cell.tsx +11 -2
  211. package/src/components/editor/output/MarimoTracebackOutput.tsx +19 -17
  212. package/src/components/editor/output/__tests__/traceback.test.tsx +27 -1
  213. package/src/components/editor/output/ansi-reduce.ts +33 -20
  214. package/src/components/editor/output/console/ConsoleOutput.tsx +19 -25
  215. package/src/components/editor/package-alert.tsx +14 -7
  216. package/src/components/editor/renderers/grid-layout/grid-layout.tsx +12 -6
  217. package/src/components/editor/renderers/vertical-layout/sidebar/toggle.tsx +7 -1
  218. package/src/components/home/components.tsx +17 -31
  219. package/src/components/icons/__tests__/large-spinner.test.tsx +39 -0
  220. package/src/components/icons/large-spinner.tsx +3 -14
  221. package/src/components/icons/spinner.tsx +1 -1
  222. package/src/components/mcp/mcp-status-indicator.tsx +1 -1
  223. package/src/components/pages/__tests__/home-page.test.tsx +116 -0
  224. package/src/components/pages/home-page.tsx +164 -91
  225. package/src/components/scratchpad/scratchpad.test.tsx +95 -0
  226. package/src/components/scratchpad/scratchpad.tsx +16 -3
  227. package/src/components/slides/reveal-component.tsx +4 -2
  228. package/src/components/slides/slide-cell-view.tsx +1 -1
  229. package/src/components/slides/slide-form.tsx +1 -0
  230. package/src/components/tracing/tracing-keyboard.test.tsx +85 -0
  231. package/src/components/tracing/tracing.tsx +45 -41
  232. package/src/components/ui/__tests__/draggable-popover.test.tsx +24 -0
  233. package/src/components/ui/alert-dialog.tsx +1 -1
  234. package/src/components/ui/draggable-popover.tsx +24 -2
  235. package/src/core/ai/context/providers/datasource.ts +6 -2
  236. package/src/core/ai/tools/base.ts +16 -7
  237. package/src/core/ai/tools/edit-notebook-tool.ts +47 -45
  238. package/src/core/ai/tools/registry.ts +13 -8
  239. package/src/core/ai/tools/sample-tool.ts +6 -7
  240. package/src/core/attachments/state.ts +5 -8
  241. package/src/core/cells/__tests__/__snapshots__/cells.test.ts.snap +4 -0
  242. package/src/core/cells/__tests__/cell.test.ts +39 -0
  243. package/src/core/cells/__tests__/cells.test.ts +3 -1
  244. package/src/core/cells/__tests__/collapseConsoleOutputs.test.ts +54 -0
  245. package/src/core/cells/__tests__/notebook-outline.test.ts +113 -0
  246. package/src/core/cells/__tests__/session.test.ts +117 -36
  247. package/src/core/cells/cell.ts +14 -3
  248. package/src/core/cells/cells.ts +3 -1
  249. package/src/core/cells/pending-delete-service.ts +2 -6
  250. package/src/core/cells/session.ts +1 -1
  251. package/src/core/cells/types.ts +10 -1
  252. package/src/core/codemirror/__tests__/setup.test.ts +29 -29
  253. package/src/core/codemirror/cells/__tests__/extensions.test.ts +15 -0
  254. package/src/core/codemirror/cells/extensions.ts +7 -3
  255. package/src/core/codemirror/keymaps/vim.ts +2 -6
  256. package/src/core/codemirror/language/__tests__/sql.test.ts +22 -20
  257. package/src/core/codemirror/language/extension.ts +8 -8
  258. package/src/core/codemirror/language/languages/markdown.ts +8 -6
  259. package/src/core/codemirror/language/languages/python.ts +16 -10
  260. package/src/core/codemirror/language/languages/sql/sql.ts +9 -7
  261. package/src/core/codemirror/language/panel/panel.tsx +1 -0
  262. package/src/core/codemirror/language/types.ts +7 -7
  263. package/src/core/codemirror/rtc/extension.ts +24 -12
  264. package/src/core/codemirror/rtc/loro/awareness.ts +34 -19
  265. package/src/core/codemirror/rtc/loro/sync.ts +1 -0
  266. package/src/core/codemirror/utils.ts +1 -0
  267. package/src/core/datasets/__tests__/data-source-discovery.test.ts +4 -4
  268. package/src/core/datasets/data-source-discovery.ts +4 -6
  269. package/src/core/document/__tests__/codemirror.test.ts +485 -0
  270. package/src/core/document/__tests__/draft.test.ts +322 -0
  271. package/src/core/document/__tests__/fixtures.ts +92 -0
  272. package/src/core/document/__tests__/hooks.test.tsx +386 -0
  273. package/src/core/document/__tests__/lifecycle-scenarios.test.ts +61 -0
  274. package/src/core/document/__tests__/model.test.ts +191 -0
  275. package/src/core/document/__tests__/protocol-fixtures.ts +38 -0
  276. package/src/core/document/__tests__/recovery-scenarios.test.ts +230 -0
  277. package/src/core/document/__tests__/scenario-fixture.ts +43 -0
  278. package/src/core/document/__tests__/scenarios.test.ts +205 -0
  279. package/src/core/document/__tests__/state.test.ts +438 -0
  280. package/src/core/document/__tests__/submission.test.ts +203 -0
  281. package/src/core/document/__tests__/test-host.test.ts +92 -0
  282. package/src/core/document/__tests__/test-host.ts +247 -0
  283. package/src/core/document/__tests__/test-notebook.ts +149 -0
  284. package/src/core/document/__tests__/user.ts +61 -0
  285. package/src/core/document/codemirror.ts +311 -0
  286. package/src/core/document/draft.ts +472 -0
  287. package/src/core/document/hooks.tsx +118 -0
  288. package/src/core/document/index.ts +15 -0
  289. package/src/core/document/model.ts +55 -0
  290. package/src/core/document/select.ts +41 -0
  291. package/src/core/document/state.ts +374 -0
  292. package/src/core/document/sync.ts +369 -0
  293. package/src/core/host/AGENTS.md +8 -2
  294. package/src/core/host/__tests__/attachments.test.ts +56 -0
  295. package/src/core/host/__tests__/document.test.ts +126 -0
  296. package/src/core/host/__tests__/fixtures.ts +71 -0
  297. package/src/core/host/__tests__/replica.test.ts +550 -0
  298. package/src/core/host/__tests__/static.test.ts +169 -0
  299. package/src/core/host/__tests__/wasm.test.ts +162 -0
  300. package/src/core/host/backend.ts +19 -0
  301. package/src/core/host/connection.ts +1 -40
  302. package/src/core/host/index.ts +14 -0
  303. package/src/core/host/replica.ts +286 -0
  304. package/src/core/host/resources/attachments.ts +22 -0
  305. package/src/core/host/resources/document.ts +101 -0
  306. package/src/core/host/static.ts +72 -0
  307. package/src/core/host/wasm.ts +102 -0
  308. package/src/core/hotkeys/actions.ts +2 -4
  309. package/src/core/islands/__tests__/bootstrap.test.ts +0 -26
  310. package/src/core/islands/bootstrap.ts +0 -1
  311. package/src/core/islands/components/__tests__/IslandControls.test.tsx +7 -5
  312. package/src/core/kernel/__tests__/RuntimeState.test.ts +1 -1
  313. package/src/core/network/connection.ts +4 -2
  314. package/src/core/network/useConnectionNotice.ts +8 -2
  315. package/src/core/runtime/__tests__/createWsUrl.test.ts +77 -7
  316. package/src/core/runtime/__tests__/runtime.test.ts +32 -58
  317. package/src/core/runtime/runtime.ts +38 -42
  318. package/src/core/runtime/types.ts +0 -2
  319. package/src/core/state/__tests__/jotai.test.tsx +108 -0
  320. package/src/core/state/jotai.ts +1 -1
  321. package/src/core/static/__tests__/files.test.ts +38 -10
  322. package/src/core/static/files.ts +2 -0
  323. package/src/core/wasm/bridge.ts +38 -10
  324. package/src/core/wasm/store.ts +3 -3
  325. package/src/core/wasm/worker/bootstrap.ts +2 -1
  326. package/src/core/wasm/worker/getMarimoWheel.ts +1 -1
  327. package/src/core/wasm/worker/types.ts +11 -0
  328. package/src/core/wasm/worker/worker.ts +48 -0
  329. package/src/core/websocket/__tests__/backend.test.ts +200 -0
  330. package/src/core/websocket/__tests__/connection-state.test.ts +0 -12
  331. package/src/core/websocket/__tests__/useNotebookReplica.hook.test.tsx +141 -0
  332. package/src/core/websocket/__tests__/useNotebookSession.hook.test.tsx +154 -428
  333. package/src/core/websocket/backend.ts +106 -0
  334. package/src/core/websocket/connection-state.ts +1 -11
  335. package/src/core/websocket/state.ts +7 -0
  336. package/src/core/websocket/transports/__tests__/notebook.test.ts +445 -0
  337. package/src/core/websocket/transports/notebook.ts +190 -0
  338. package/src/core/websocket/types.ts +2 -2
  339. package/src/core/websocket/useNotebookReplica.ts +69 -0
  340. package/src/core/websocket/useNotebookSession.tsx +10 -22
  341. package/src/css/globals.css +5 -0
  342. package/src/hooks/__tests__/useAsyncData.test.tsx +148 -0
  343. package/src/hooks/__tests__/useDebounce.test.tsx +51 -0
  344. package/src/hooks/__tests__/useDeepCompareMemoize.test.tsx +39 -0
  345. package/src/hooks/__tests__/useDelayElapsed.test.tsx +28 -0
  346. package/src/hooks/__tests__/useInternalStateWithSync.test.tsx +20 -0
  347. package/src/hooks/__tests__/useScript.test.tsx +77 -0
  348. package/src/hooks/__tests__/useStateForKey.test.tsx +257 -0
  349. package/src/hooks/useAsyncData.ts +3 -3
  350. package/src/hooks/useDataSourceDiscovery.ts +6 -4
  351. package/src/hooks/useDebounce.ts +6 -10
  352. package/src/hooks/useDeepCompareMemoize.ts +1 -1
  353. package/src/hooks/useDelayElapsed.ts +14 -10
  354. package/src/hooks/useElapsedTime.ts +3 -3
  355. package/src/hooks/useEventListener.ts +1 -2
  356. package/src/hooks/useInputHistory.ts +4 -7
  357. package/src/hooks/useInternalStateWithSync.ts +6 -6
  358. package/src/hooks/useResizeObserver.ts +4 -7
  359. package/src/hooks/useScript.ts +67 -70
  360. package/src/hooks/useStateForKey.ts +93 -0
  361. package/src/mount.tsx +0 -4
  362. package/src/plugins/__tests__/plugin-object-schemas.test.ts +104 -0
  363. package/src/plugins/core/registerReactComponent.tsx +1 -1
  364. package/src/plugins/impl/CodeEditorPlugin.tsx +1 -1
  365. package/src/plugins/impl/DataEditorPlugin.tsx +1 -1
  366. package/src/plugins/impl/DataTablePlugin.tsx +29 -33
  367. package/src/plugins/impl/DropdownPlugin.tsx +2 -6
  368. package/src/plugins/impl/FileBrowserPlugin.tsx +4 -16
  369. package/src/plugins/impl/FormPlugin.tsx +1 -1
  370. package/src/plugins/impl/RangeSliderPlugin.tsx +0 -1
  371. package/src/plugins/impl/RefreshPlugin.tsx +1 -0
  372. package/src/plugins/impl/SliderPlugin.tsx +6 -8
  373. package/src/plugins/impl/__tests__/DataTablePlugin.test.tsx +117 -1
  374. package/src/plugins/impl/__tests__/FileBrowserPlugin.test.tsx +45 -0
  375. package/src/plugins/impl/__tests__/SliderPlugin.test.tsx +24 -3
  376. package/src/plugins/impl/anywidget/__tests__/registry.test.ts +2 -0
  377. package/src/plugins/impl/anywidget/__tests__/widget-binding.test.ts +241 -2
  378. package/src/plugins/impl/anywidget/runtime.ts +21 -9
  379. package/src/plugins/impl/anywidget/widget-binding.ts +160 -82
  380. package/src/plugins/impl/chat/__tests__/chat-prompts.test.tsx +93 -0
  381. package/src/plugins/impl/chat/chat-ui.tsx +14 -11
  382. package/src/plugins/impl/code/any-language-editor.tsx +2 -2
  383. package/src/plugins/impl/data-editor/glide-data-editor.tsx +8 -5
  384. package/src/plugins/impl/data-editor/glide-utils.ts +3 -1
  385. package/src/plugins/impl/data-explorer/ConnectedDataExplorerComponent.tsx +1 -1
  386. package/src/plugins/impl/data-explorer/components/column-summary.tsx +4 -2
  387. package/src/plugins/impl/data-explorer/state/reducer.ts +1 -1
  388. package/src/plugins/impl/data-frames/DataFramePlugin.tsx +1 -2
  389. package/src/plugins/impl/data-frames/__tests__/sidebar.test.tsx +160 -0
  390. package/src/plugins/impl/data-frames/forms/__tests__/__snapshots__/form.test.tsx.snap +66 -66
  391. package/src/plugins/impl/data-frames/panel.tsx +43 -16
  392. package/src/plugins/impl/mpl-interactive/MplInteractivePlugin.tsx +5 -3
  393. package/src/plugins/impl/panel/PanelPlugin.tsx +16 -17
  394. package/src/plugins/impl/panel/__tests__/utils.test.ts +39 -1
  395. package/src/plugins/impl/panel/utils.ts +17 -0
  396. package/src/plugins/impl/plotly/PlotlyPlugin.tsx +2 -5
  397. package/src/plugins/impl/plotly/selection.ts +25 -19
  398. package/src/plugins/impl/vega/VegaPlugin.tsx +2 -3
  399. package/src/plugins/impl/vega/make-selectable.ts +4 -7
  400. package/src/plugins/layout/RoutesPlugin.tsx +1 -1
  401. package/src/stories/__tests__/editor-story.test.tsx +33 -0
  402. package/src/stories/button.stories.tsx +1 -0
  403. package/src/stories/editor.stories.tsx +24 -17
  404. package/src/stories/form-wrapper.stories.tsx +2 -2
  405. package/src/stories/select.stories.tsx +2 -2
  406. package/src/stories/switchable-multi-select.stories.tsx +1 -1
  407. package/src/utils/__tests__/arrays.test.ts +35 -0
  408. package/src/utils/__tests__/batch-requests.test.ts +3 -3
  409. package/src/utils/__tests__/numbers.test.ts +48 -1
  410. package/src/utils/__tests__/objects.test.ts +21 -0
  411. package/src/utils/arrays.ts +10 -0
  412. package/src/utils/blob.ts +1 -1
  413. package/src/utils/numbers.ts +28 -17
  414. package/src/utils/objects.ts +3 -7
  415. package/src/utils/once.ts +19 -19
  416. package/src/utils/tracer.ts +9 -5
  417. package/src/utils/zod-utils.ts +1 -3
  418. package/dist/any-language-editor-DZ_YTVus.js +0 -76
  419. package/dist/assets/__vite-browser-external-BXYkiFsk.js +0 -1
  420. package/dist/mermaid-HWGCJPDP-ByCsZnYF.js +0 -2
  421. package/dist/precisionRound-DMIVQrt2.js +0 -97
  422. package/src/core/host/__tests__/events.test.ts +0 -46
  423. package/src/core/host/__tests__/notebook-client.test.ts +0 -191
  424. package/src/core/host/__tests__/protocol.test.ts +0 -214
  425. package/src/core/host/events.ts +0 -60
  426. package/src/core/host/notebook-client.ts +0 -226
  427. package/src/core/host/protocol.ts +0 -1943
  428. package/src/core/state/__tests__/jotai.test.ts +0 -25
  429. package/src/core/websocket/__tests__/createConnectionTransport.test.ts +0 -49
  430. package/src/core/websocket/__tests__/useNotebookClient.hook.test.tsx +0 -140
  431. package/src/core/websocket/transports/__tests__/basic.test.ts +0 -275
  432. package/src/core/websocket/transports/__tests__/sse.test.ts +0 -335
  433. package/src/core/websocket/transports/__tests__/transport.test.ts +0 -308
  434. package/src/core/websocket/transports/__tests__/ws.test.ts +0 -125
  435. package/src/core/websocket/transports/basic.ts +0 -102
  436. package/src/core/websocket/transports/create.ts +0 -31
  437. package/src/core/websocket/transports/sse.ts +0 -308
  438. package/src/core/websocket/transports/transport.ts +0 -50
  439. package/src/core/websocket/transports/ws.ts +0 -98
  440. package/src/core/websocket/useNotebookClient.ts +0 -80
@@ -17,6 +17,8 @@ export const FloatingOutline: React.FC = () => {
17
17
  findOutlineElements(items),
18
18
  );
19
19
  const [isHovered, setIsHovered] = React.useState(false);
20
+ const [isFocused, setIsFocused] = React.useState(false);
21
+ const isVisible = isHovered || isFocused;
20
22
 
21
23
  // Hide if < 2 items
22
24
  // It's kinda useless to have an outline with only one item
@@ -29,6 +31,12 @@ export const FloatingOutline: React.FC = () => {
29
31
  <div
30
32
  onMouseEnter={() => setIsHovered(true)}
31
33
  onMouseLeave={() => setIsHovered(false)}
34
+ onFocus={() => setIsFocused(true)}
35
+ onBlur={(e) => {
36
+ if (!e.currentTarget.contains(e.relatedTarget)) {
37
+ setIsFocused(false);
38
+ }
39
+ }}
32
40
  className={cn(
33
41
  "fixed top-[25vh] right-8 z-10000 print:hidden",
34
42
  // Hide on small screens
@@ -36,9 +44,10 @@ export const FloatingOutline: React.FC = () => {
36
44
  )}
37
45
  >
38
46
  <OutlineList
47
+ inert={!isVisible}
39
48
  className={cn(
40
49
  "-top-4 max-h-[70vh] bg-background rounded-lg shadow-lg absolute overflow-auto transition-all duration-300 w-[300px] border",
41
- isHovered ? "left-[-280px] opacity-100" : "left-[300px] opacity-0",
50
+ isVisible ? "left-[-280px] opacity-100" : "left-[300px] opacity-0",
42
51
  )}
43
52
  items={items}
44
53
  activeHeaderId={activeHeaderId}
@@ -62,17 +71,20 @@ export const MiniMap: React.FC<{
62
71
  const seen = new Map<string, number>();
63
72
  return (
64
73
  <div className="flex flex-col gap-4 items-end max-h-[70vh] overflow-hidden">
65
- {items.map((item, idx) => {
74
+ {items.map((item) => {
66
75
  const identifier = "id" in item.by ? item.by.id : item.by.path;
76
+ const selector = JSON.stringify(item.by);
67
77
  // Keep track of how many times we've seen this selector
68
- const occurrences = seen.get(identifier) ?? 0;
69
- seen.set(identifier, occurrences + 1);
78
+ const occurrences = seen.get(selector) ?? 0;
79
+ seen.set(selector, occurrences + 1);
70
80
 
71
81
  return (
72
- <div
73
- key={`${identifier}-${idx}`}
82
+ <button
83
+ type="button"
84
+ aria-label={item.name}
85
+ key={`${selector}-${occurrences}`}
74
86
  className={cn(
75
- "h-[2px] bg-muted-foreground/60",
87
+ "h-[2px] bg-muted-foreground/60 focus-visible:outline focus-visible:outline-ring",
76
88
  item.level === 1 && "w-5",
77
89
  item.level === 2 && "w-4",
78
90
  item.level === 3 && "w-3",
@@ -91,24 +103,31 @@ export const MiniMap: React.FC<{
91
103
 
92
104
  export const OutlineList: React.FC<{
93
105
  className?: string;
106
+ inert?: boolean;
94
107
  items: OutlineItem[];
95
108
  activeHeaderId: string | undefined;
96
109
  activeOccurrences: number | undefined;
97
- }> = ({ items, activeHeaderId, activeOccurrences, className }) => {
110
+ }> = ({ items, activeHeaderId, activeOccurrences, className, inert }) => {
98
111
  // Map of selector to its occurrences
99
112
  const seen = new Map<string, number>();
100
113
  return (
101
- <div className={cn("flex flex-col overflow-auto py-4 pl-2", className)}>
102
- {items.map((item, idx) => {
114
+ <div
115
+ inert={inert}
116
+ className={cn("flex flex-col overflow-auto py-4 pl-2", className)}
117
+ >
118
+ {items.map((item) => {
103
119
  const identifier = "id" in item.by ? item.by.id : item.by.path;
120
+ const selector = JSON.stringify(item.by);
104
121
  // Keep track of how many times we've seen this selector
105
- const occurrences = seen.get(identifier) ?? 0;
106
- seen.set(identifier, occurrences + 1);
122
+ const occurrences = seen.get(selector) ?? 0;
123
+ seen.set(selector, occurrences + 1);
107
124
 
108
- const key = `${identifier}-${idx}`;
125
+ const key = `${selector}-${occurrences}`;
109
126
  const sharedProps = {
127
+ role: "link",
128
+ tabIndex: 0,
110
129
  className: cn(
111
- "px-2 py-1 cursor-pointer hover:bg-accent/50 hover:text-accent-foreground rounded-l",
130
+ "px-2 py-1 cursor-pointer hover:bg-accent/50 hover:text-accent-foreground rounded-l focus-visible:outline focus-visible:outline-ring",
112
131
  item.level === 1 && "font-semibold",
113
132
  item.level === 2 && "ml-3",
114
133
  item.level === 3 && "ml-6",
@@ -118,6 +137,12 @@ export const OutlineList: React.FC<{
118
137
  "text-accent-foreground",
119
138
  ),
120
139
  onClick: () => scrollToOutlineItem(item, occurrences),
140
+ onKeyDown: (e: React.KeyboardEvent) => {
141
+ if (e.target === e.currentTarget && e.key === "Enter") {
142
+ e.preventDefault();
143
+ scrollToOutlineItem(item, occurrences);
144
+ }
145
+ },
121
146
  };
122
147
 
123
148
  if (item.html) {
@@ -113,9 +113,10 @@ export function findOutlineElements(items: OutlineItem[]) {
113
113
  return items
114
114
  .map((item) => {
115
115
  const identifier = "id" in item.by ? item.by.id : item.by.path;
116
+ const selector = JSON.stringify(item.by);
116
117
  // Keep track of how many times we've seen this selector
117
- const occurrences = seen.get(identifier) ?? 0;
118
- seen.set(identifier, occurrences + 1);
118
+ const occurrences = seen.get(selector) ?? 0;
119
+ seen.set(selector, occurrences + 1);
119
120
 
120
121
  const el = findOutlineItem(item, occurrences);
121
122
  if (!el) {
@@ -31,6 +31,7 @@ import { usePackageAction } from "@/core/packages/usePackageAction";
31
31
  import { usePackageDependencies } from "@/core/packages/usePackageDependencies";
32
32
  import { useInstallPackages } from "@/core/packages/useInstallPackage";
33
33
  import { isWasm } from "@/core/wasm/utils";
34
+ import { useStateForKey } from "@/hooks/useStateForKey";
34
35
  import { ErrorBanner } from "@/plugins/impl/common/error-banner";
35
36
  import { cn } from "@/utils/cn";
36
37
  import { copyToClipboard } from "@/utils/copy";
@@ -440,15 +441,11 @@ const DependencyTree: React.FC<{
440
441
  error?: Error | null;
441
442
  sandboxBackend: "uv" | "pixi" | null;
442
443
  }> = ({ tree, error, sandboxBackend }) => {
443
- const [expandedNodes, setExpandedNodes] = React.useState<Set<string>>(
444
- new Set(),
444
+ const [expandedNodes, setExpandedNodes] = useStateForKey(
445
+ tree,
446
+ () => new Set<string>(),
445
447
  );
446
448
 
447
- // Reset tree to collapsed state when tree data changes (including refetches)
448
- React.useEffect(() => {
449
- setExpandedNodes(new Set());
450
- }, [tree]);
451
-
452
449
  if (error) {
453
450
  return <ErrorBanner error={error} />;
454
451
  }
@@ -66,6 +66,7 @@ const SecretsPanel: React.FC = () => {
66
66
  <div className="flex justify-start h-8 border-b">
67
67
  <button
68
68
  type="button"
69
+ aria-label="Add environment variable"
69
70
  className="border-r px-2 m-0 h-full hover:bg-accent hover:text-accent-foreground"
70
71
  onClick={() =>
71
72
  openModal(
@@ -71,7 +71,11 @@ const SnippetsPanel: React.FC = () => {
71
71
  rootClassName="flex-1 border-r"
72
72
  />
73
73
  <ContributeSnippetButton>
74
- <button className="float-right px-2 m-0 h-full hover:bg-accent hover:text-accent-foreground">
74
+ <button
75
+ type="button"
76
+ aria-label="Contribute snippet"
77
+ className="float-right px-2 m-0 h-full hover:bg-accent hover:text-accent-foreground"
78
+ >
75
79
  <PlusIcon className="h-4 w-4" />
76
80
  </button>
77
81
  </ContributeSnippetButton>
@@ -8,27 +8,31 @@ import {
8
8
  screen,
9
9
  waitFor,
10
10
  } from "@testing-library/react";
11
- import { createStore, Provider } from "jotai";
11
+ import { atom, createStore, Provider } from "jotai";
12
12
  import { userEvent } from "storybook/test";
13
13
  import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
14
14
  import { PAIR_CONFIG } from "@/__tests__/fixtures/pair-config";
15
15
  import { TooltipProvider } from "@/components/ui/tooltip";
16
16
  import { toast } from "@/components/ui/use-toast";
17
- import { type Attachment, attachmentsAtom } from "@/core/attachments/state";
17
+ import type { Attachment } from "@/core/attachments/state";
18
18
  import { pairConfigAtom } from "@/core/config/pair";
19
+ import { createNotebookReplica } from "@/core/host/replica";
19
20
  import {
20
21
  type StableSessionId,
21
22
  stableSessionIdAtom,
22
23
  } from "@/core/kernel/session";
23
24
  import { API } from "@/core/network/api";
24
25
  import { connectionAtom } from "@/core/network/connection";
25
- import { WebSocketClosedReason, ConnectionState } from "@/core/websocket/types";
26
+ import { notebookReplicaAtom } from "@/core/websocket/state";
27
+ import { ConnectionState } from "@/core/websocket/types";
26
28
  import { Deferred } from "@/utils/Deferred";
27
29
  import { AttachmentStatus } from "../footer-items/attachment-status";
28
30
 
29
31
  vi.mock("@/components/ui/use-toast", () => ({ toast: vi.fn() }));
30
32
  vi.mock("@/core/network/api", () => ({ API: { post: vi.fn() } }));
31
33
 
34
+ const attachmentsAtom = atom<Attachment[]>([]);
35
+
32
36
  const AGENT: Attachment = {
33
37
  id: "a1",
34
38
  kind: "agent",
@@ -41,6 +45,11 @@ function renderStatus(attachments: Attachment[] = [AGENT], preview = true) {
41
45
  const store = createStore();
42
46
  store.set(connectionAtom, { state: ConnectionState.OPEN });
43
47
  store.set(pairConfigAtom, { ...PAIR_CONFIG, hostEnabled: preview });
48
+ store.set(notebookReplicaAtom, {
49
+ ...createNotebookReplica(store),
50
+ connectionAtom: atom({ state: ConnectionState.OPEN }),
51
+ attachmentsAtom,
52
+ });
44
53
  store.set(attachmentsAtom, attachments);
45
54
  store.set(stableSessionIdAtom, "stable/test" as StableSessionId);
46
55
  return {
@@ -342,21 +351,6 @@ describe("AttachmentStatus", () => {
342
351
  ).toBeDisabled();
343
352
  });
344
353
 
345
- it.each([
346
- { state: ConnectionState.CONNECTING },
347
- { state: ConnectionState.CLOSING },
348
- { state: ConnectionState.NOT_STARTED },
349
- {
350
- state: ConnectionState.CLOSED,
351
- code: WebSocketClosedReason.KERNEL_DISCONNECTED,
352
- reason: "offline",
353
- },
354
- ])("hides connected claims while the browser is %j", (connection) => {
355
- const { store, container } = renderStatus();
356
- act(() => store.set(connectionAtom, connection));
357
- expect(container).toBeEmptyDOMElement();
358
- });
359
-
360
354
  it("removes detached agents when the list is replaced", () => {
361
355
  const { store, container } = renderStatus([
362
356
  AGENT,
@@ -1,11 +1,12 @@
1
1
  /* Copyright 2026 Marimo. All rights reserved. */
2
2
 
3
- import { act, render, screen, within } from "@testing-library/react";
3
+ import { act, fireEvent, render, screen, within } from "@testing-library/react";
4
4
  import { createStore, Provider } from "jotai";
5
5
  import { describe, expect, it, vi } from "vitest";
6
6
  import { cellId } from "@/__tests__/branded";
7
7
  import { TooltipProvider } from "@/components/ui/tooltip";
8
8
  import { stagedAICellsAtom, visibleForTesting } from "@/core/ai/staged-cells";
9
+ import { scrollAndHighlightCell } from "../../../links/cell-link";
9
10
  import { PendingAICells } from "../pending-ai-cells";
10
11
 
11
12
  vi.mock("@/components/editor/cell/useDeleteCell", () => ({
@@ -29,6 +30,62 @@ vi.mock("@/components/editor/links/cell-link", () => ({
29
30
  }));
30
31
 
31
32
  describe("PendingAICells", () => {
33
+ it("navigates between pending cells through named controls", () => {
34
+ const store = createStore();
35
+ const first = cellId("first-pending-cell");
36
+ const second = cellId("second-pending-cell");
37
+ const third = cellId("third-pending-cell");
38
+ store.set(
39
+ stagedAICellsAtom,
40
+ new Map([
41
+ [first, { type: "add_cell" }],
42
+ [second, { type: "add_cell" }],
43
+ [third, { type: "add_cell" }],
44
+ ]),
45
+ );
46
+
47
+ render(
48
+ <Provider store={store}>
49
+ <TooltipProvider>
50
+ <PendingAICells />
51
+ </TooltipProvider>
52
+ </Provider>,
53
+ );
54
+
55
+ const next = screen.getByRole("button", { name: "Next pending AI cell" });
56
+ const previous = screen.getByRole("button", {
57
+ name: "Previous pending AI cell",
58
+ });
59
+
60
+ fireEvent.click(next);
61
+ expect(scrollAndHighlightCell).toHaveBeenLastCalledWith(first, "focus");
62
+ expect(screen.getByText("1 / 3")).toBeInTheDocument();
63
+
64
+ fireEvent.click(next);
65
+ expect(scrollAndHighlightCell).toHaveBeenLastCalledWith(second, "focus");
66
+ expect(screen.getByText("2 / 3")).toBeInTheDocument();
67
+
68
+ fireEvent.click(previous);
69
+ expect(scrollAndHighlightCell).toHaveBeenLastCalledWith(first, "focus");
70
+ expect(screen.getByText("1 / 3")).toBeInTheDocument();
71
+
72
+ fireEvent.click(next);
73
+ expect(scrollAndHighlightCell).toHaveBeenLastCalledWith(second, "focus");
74
+ expect(screen.getByText("2 / 3")).toBeInTheDocument();
75
+
76
+ fireEvent.click(next);
77
+ expect(scrollAndHighlightCell).toHaveBeenLastCalledWith(third, "focus");
78
+ expect(screen.getByText("3 / 3")).toBeInTheDocument();
79
+
80
+ fireEvent.click(next);
81
+ expect(scrollAndHighlightCell).toHaveBeenLastCalledWith(first, "focus");
82
+ expect(screen.getByText("1 / 3")).toBeInTheDocument();
83
+
84
+ fireEvent.click(previous);
85
+ expect(scrollAndHighlightCell).toHaveBeenLastCalledWith(third, "focus");
86
+ expect(screen.getByText("3 / 3")).toBeInTheDocument();
87
+ });
88
+
32
89
  it("disables bulk actions while cells are still generating", () => {
33
90
  const store = createStore();
34
91
  const generationId = Symbol("staged-cell-generation");
@@ -26,13 +26,7 @@ import { isWasm } from "@/core/wasm/utils";
26
26
  import { cn } from "@/utils/cn";
27
27
  import { FooterItem } from "../footer-item";
28
28
 
29
- interface RuntimeSettingsProps {
30
- className?: string;
31
- }
32
-
33
- export const RuntimeSettings: React.FC<RuntimeSettingsProps> = ({
34
- className,
35
- }) => {
29
+ export const RuntimeSettings: React.FC = () => {
36
30
  const { saveUserConfig } = useRequestClient();
37
31
  const [config, setUserConfig] = useResolvedMarimoConfig();
38
32
 
@@ -79,7 +79,12 @@ export const PendingAICells: React.FC = () => {
79
79
  <SparklesIcon className="h-4 w-4 text-primary" />
80
80
 
81
81
  <div className="flex items-center">
82
- <Button variant="ghost" size="icon" onClick={() => clickNext("up")}>
82
+ <Button
83
+ aria-label="Previous pending AI cell"
84
+ variant="ghost"
85
+ size="icon"
86
+ onClick={() => clickNext("down")}
87
+ >
83
88
  <ChevronUp className="h-3.5 w-3.5" />
84
89
  </Button>
85
90
  <span className="text-xs font-mono min-w-14 text-center">
@@ -87,7 +92,12 @@ export const PendingAICells: React.FC = () => {
87
92
  ? `${listStagedCells.length} pending`
88
93
  : `${currentIndex + 1} / ${listStagedCells.length}`}
89
94
  </span>
90
- <Button variant="ghost" size="icon" onClick={() => clickNext("down")}>
95
+ <Button
96
+ aria-label="Next pending AI cell"
97
+ variant="ghost"
98
+ size="icon"
99
+ onClick={() => clickNext("up")}
100
+ >
91
101
  <ChevronDown className="h-3.5 w-3.5" />
92
102
  </Button>
93
103
  </div>
@@ -15,7 +15,7 @@ let storedValue: string | null = null;
15
15
  */
16
16
  export function createStorage(location: "left" | "bottom"): PanelGroupStorage {
17
17
  return {
18
- getItem(name) {
18
+ getItem(_name) {
19
19
  if (!storedValue) {
20
20
  return storedValue ?? null;
21
21
  }
@@ -48,7 +48,7 @@ export const SelectorButton: React.FC<{
48
48
  }> = ({ name, color, icon, onSelect }) => (
49
49
  <button
50
50
  type="button"
51
- className="py-3 flex flex-col items-center justify-center gap-1 transition-all hover:scale-105 hover:brightness-110 rounded shadow-sm-solid hover:shadow-md-solid"
51
+ className="py-3 flex flex-col items-center justify-center gap-1 transition-[scale,filter,box-shadow] hover:scale-105 hover:brightness-110 rounded shadow-sm-solid hover:shadow-md-solid"
52
52
  style={{ backgroundColor: color }}
53
53
  onClick={onSelect}
54
54
  >
@@ -0,0 +1,53 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+
3
+ import { fireEvent, render, screen, waitFor } from "@testing-library/react";
4
+ import { createStore, Provider } from "jotai";
5
+ import { expect, it, vi } from "vitest";
6
+ import { useNotebookActions } from "../../actions/useNotebookActions";
7
+ import CommandPalette from "../command-palette";
8
+
9
+ vi.mock("../../actions/useNotebookActions", () => ({
10
+ useNotebookActions: vi.fn(),
11
+ }));
12
+ vi.mock("../../actions/useCellActionButton", () => ({
13
+ useCellActionButtons: () => [],
14
+ }));
15
+ vi.mock("../../actions/useConfigActions", () => ({
16
+ useConfigActions: () => [],
17
+ }));
18
+
19
+ it("builds current actions only while open and keeps the opening shortcut active", async () => {
20
+ HTMLElement.prototype.scrollIntoView = vi.fn();
21
+ const firstAction = vi.fn();
22
+ const nextAction = vi.fn();
23
+ vi.mocked(useNotebookActions).mockReturnValue([
24
+ { label: "First action", handle: firstAction },
25
+ ]);
26
+ render(
27
+ <Provider store={createStore()}>
28
+ <CommandPalette />
29
+ </Provider>,
30
+ );
31
+ expect(useNotebookActions).not.toHaveBeenCalled();
32
+
33
+ fireEvent.keyDown(document, { key: "k", ctrlKey: true });
34
+ await screen.findByPlaceholderText("Type to search...");
35
+ fireEvent.click(screen.getByText("First action"));
36
+ await waitFor(() => expect(firstAction).toHaveBeenCalledOnce());
37
+ await waitFor(() =>
38
+ expect(screen.queryByPlaceholderText("Type to search...")).toBeNull(),
39
+ );
40
+
41
+ vi.mocked(useNotebookActions)
42
+ .mockClear()
43
+ .mockReturnValue([{ label: "Updated action", handle: nextAction }]);
44
+ fireEvent.keyDown(document, { key: "x" });
45
+ expect(useNotebookActions).not.toHaveBeenCalled();
46
+
47
+ fireEvent.keyDown(document, { key: "k", ctrlKey: true });
48
+ await screen.findByPlaceholderText("Type to search...");
49
+ expect(screen.queryByText("First action")).toBeNull();
50
+ fireEvent.click(screen.getByText("Updated action"));
51
+ await waitFor(() => expect(nextAction).toHaveBeenCalledOnce());
52
+ expect(firstAction).toHaveBeenCalledOnce();
53
+ });
@@ -0,0 +1,61 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+
3
+ import { act, fireEvent, render, screen } from "@testing-library/react";
4
+ import { createStore, Provider } from "jotai";
5
+ import { beforeEach, describe, expect, it, vi } from "vitest";
6
+ import { TooltipProvider } from "@/components/ui/tooltip";
7
+ import { userConfigAtom } from "@/core/config/config";
8
+ import { defaultUserConfig } from "@/core/config/config-schema";
9
+ import { getDefaultHotkey } from "@/core/hotkeys/hotkeys";
10
+ import {
11
+ KeyboardShortcuts,
12
+ keyboardShortcutsAtom,
13
+ } from "../keyboard-shortcuts";
14
+
15
+ const saveUserConfig = vi.hoisted(() => vi.fn().mockResolvedValue(undefined));
16
+ vi.mock("@/core/network/requests", () => ({
17
+ useRequestClient: () => ({ saveUserConfig }),
18
+ }));
19
+
20
+ describe("shortcut recorder keyboard navigation", () => {
21
+ beforeEach(() => vi.clearAllMocks());
22
+
23
+ it("allows Tab navigation and Reset without saving a shortcut", async () => {
24
+ const store = createStore();
25
+ const config = defaultUserConfig();
26
+ store.set(userConfigAtom, {
27
+ ...config,
28
+ keymap: { ...config.keymap, overrides: { "cell.run": "Ctrl-9" } },
29
+ });
30
+ store.set(keyboardShortcutsAtom, true);
31
+ render(
32
+ <Provider store={store}>
33
+ <TooltipProvider>
34
+ <KeyboardShortcuts />
35
+ </TooltipProvider>
36
+ </Provider>,
37
+ );
38
+ const edit = screen.getByRole("button", {
39
+ name: `Edit shortcut: ${getDefaultHotkey("cell.run").name}`,
40
+ });
41
+ act(() => edit.focus());
42
+ fireEvent.click(edit, { detail: 0 });
43
+ const recorder = screen.getByPlaceholderText(
44
+ getDefaultHotkey("cell.run").name,
45
+ );
46
+ expect(recorder).toHaveFocus();
47
+ expect(fireEvent.keyDown(recorder, { key: "Tab" })).toBe(true);
48
+ expect(fireEvent.keyDown(recorder, { key: "Tab", shiftKey: true })).toBe(
49
+ true,
50
+ );
51
+ expect(recorder).toBeInTheDocument();
52
+ expect(saveUserConfig).not.toHaveBeenCalled();
53
+ const reset = screen.getByRole("button", { name: /^Reset to default:/ });
54
+ act(() => reset.focus());
55
+ expect(reset).toHaveFocus();
56
+ await act(async () => fireEvent.click(reset, { detail: 0 }));
57
+ expect(saveUserConfig).toHaveBeenCalledExactlyOnceWith({
58
+ config: { keymap: { ...config.keymap, overrides: {} } },
59
+ });
60
+ });
61
+ });
@@ -1,6 +1,6 @@
1
1
  /* Copyright 2026 Marimo. All rights reserved. */
2
2
 
3
- import { useAtom, useAtomValue } from "jotai";
3
+ import { useAtom, useAtomValue, useSetAtom } from "jotai";
4
4
  import {
5
5
  CommandDialog,
6
6
  CommandEmpty,
@@ -32,6 +32,25 @@ import { commandPaletteAtom } from "./state";
32
32
 
33
33
  const CommandPalette = () => {
34
34
  const [open, setOpen] = useAtom(commandPaletteAtom);
35
+ const hotkeys = useAtomValue(hotkeysAtom);
36
+
37
+ useEventListener(document, "keydown", (e) => {
38
+ if (parseShortcut(hotkeys.getHotkey("global.commandPalette").key)(e)) {
39
+ e.preventDefault();
40
+ setOpen((open) => !open);
41
+ }
42
+ });
43
+
44
+ return (
45
+ <CommandDialog open={open} onOpenChange={setOpen}>
46
+ <CommandPaletteContent />
47
+ </CommandDialog>
48
+ );
49
+ };
50
+
51
+ // The dialog mounts these subscriptions only while its content is present.
52
+ const CommandPaletteContent = () => {
53
+ const setOpen = useSetAtom(commandPaletteAtom);
35
54
  const registeredActions = useRegisteredActions();
36
55
  const lastFocusedCell = useAtomValue(lastFocusedCellAtom);
37
56
  const hotkeys = useAtomValue(hotkeysAtom);
@@ -55,13 +74,6 @@ const CommandPalette = () => {
55
74
  const { recentCommands, addRecentCommand } = useRecentCommands();
56
75
  const recentCommandsSet = new Set(recentCommands);
57
76
 
58
- useEventListener(document, "keydown", (e) => {
59
- if (parseShortcut(hotkeys.getHotkey("global.commandPalette").key)(e)) {
60
- e.preventDefault();
61
- setOpen((open) => !open);
62
- }
63
- });
64
-
65
77
  const renderShortcutCommandItem = (
66
78
  shortcut: HotkeyAction,
67
79
  props: {
@@ -142,7 +154,7 @@ const CommandPalette = () => {
142
154
  };
143
155
 
144
156
  return (
145
- <CommandDialog open={open} onOpenChange={setOpen}>
157
+ <>
146
158
  <CommandInput placeholder="Type to search..." />
147
159
  <CommandList>
148
160
  <CommandEmpty>No results found.</CommandEmpty>
@@ -211,7 +223,7 @@ const CommandPalette = () => {
211
223
  })}
212
224
  </CommandGroup>
213
225
  </CommandList>
214
- </CommandDialog>
226
+ </>
215
227
  );
216
228
  };
217
229
 
@@ -134,6 +134,9 @@ export const KeyboardShortcuts: React.FC = () => {
134
134
  defaultValue={newShortcut.join("+")}
135
135
  placeholder={hotkey.name}
136
136
  onKeyDown={(e) => {
137
+ if (e.key === "Tab" && !e.metaKey && !e.ctrlKey && !e.altKey) {
138
+ return;
139
+ }
137
140
  e.preventDefault();
138
141
  const next: string[] = [];
139
142
 
@@ -189,6 +192,7 @@ export const KeyboardShortcuts: React.FC = () => {
189
192
  autoFocus={true}
190
193
  endAdornment={
191
194
  <Button
195
+ aria-label="Cancel shortcut recording"
192
196
  variant="text"
193
197
  size="xs"
194
198
  className="mb-0"
@@ -206,13 +210,14 @@ export const KeyboardShortcuts: React.FC = () => {
206
210
  Press a key combination
207
211
  </span>
208
212
  {defaultHotkey.key !== hotkey.key && (
209
- <span
213
+ <button
214
+ type="button"
210
215
  className="text-xs cursor-pointer text-primary"
211
216
  onClick={handleResetShortcut}
212
217
  >
213
218
  Reset to default:{" "}
214
219
  <span className="font-mono">{defaultHotkey.key}</span>
215
- </span>
220
+ </button>
216
221
  )}
217
222
  </div>
218
223
  </div>
@@ -228,13 +233,17 @@ export const KeyboardShortcuts: React.FC = () => {
228
233
  className="grid grid-cols-[auto_2fr_3fr] gap-2 items-center"
229
234
  >
230
235
  {hotkeys.isEditable(action) ? (
231
- <EditIcon
232
- className="cursor-pointer opacity-60 hover:opacity-100 text-muted-foreground w-3 h-3"
236
+ <button
237
+ type="button"
238
+ aria-label={`Edit shortcut: ${hotkey.name}`}
239
+ className="cursor-pointer opacity-60 hover:opacity-100 text-muted-foreground w-3 h-3 focus-visible:outline focus-visible:outline-ring"
233
240
  onClick={() => {
234
241
  setNewShortcut([]);
235
242
  setEditingShortcut(action);
236
243
  }}
237
- />
244
+ >
245
+ <EditIcon className="w-3 h-3" />
246
+ </button>
238
247
  ) : (
239
248
  <div className="w-3 h-3" />
240
249
  )}