@marimo-team/islands 0.23.14-dev9 → 0.23.14

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 (160) hide show
  1. package/dist/{ConnectedDataExplorerComponent-DXBx_nQg.js → ConnectedDataExplorerComponent-CU4fZJzG.js} +4 -4
  2. package/dist/assets/__vite-browser-external-BQCLNwri.js +1 -0
  3. package/dist/assets/{worker-DEDLIQQV.js → worker-DAWRHcPq.js} +2 -2
  4. package/dist/{chat-ui-DYBcNEdd.js → chat-ui-DinOvbWu.js} +3256 -3052
  5. package/dist/{click-outside-container-BDd67_1U.js → click-outside-container-BLPjMamz.js} +141 -113
  6. package/dist/{code-visibility-BZArWgy6.js → code-visibility-CjfiSvwL.js} +905 -889
  7. package/dist/data-grid-overlay-editor-C6lxUJp-.js +136 -0
  8. package/dist/{dist-D_bzzWBm.js → dist-Bf9f8MuT.js} +3 -3
  9. package/dist/{formats-WsOgyW_K.js → formats-Dzx4J_z1.js} +1 -1
  10. package/dist/{glide-data-editor-qpmKyAPn.js → glide-data-editor-CjTu7ukN.js} +2084 -1889
  11. package/dist/{html-to-image-MqcD07Bw.js → html-to-image-_wGfk8V-.js} +2389 -2323
  12. package/dist/{input-BSdZp5Ng.js → input-DtsN7xm-.js} +1 -1
  13. package/dist/main.js +3667 -1838
  14. package/dist/{mermaid-D-HYBMEV.js → mermaid-BYqXy_NE.js} +2 -2
  15. package/dist/{number-overlay-editor-D-a0qCT8.js → number-overlay-editor-BLJXvX9c.js} +1 -1
  16. package/dist/{process-output-Dt3icftd.js → process-output-Mh4UrjwM.js} +1 -1
  17. package/dist/{reveal-component-CKomb1B5.js → reveal-component-DtOb1y5B.js} +600 -596
  18. package/dist/{spec-CnTgI25l.js → spec-Cz-Bj1JI.js} +1 -1
  19. package/dist/style.css +1 -1
  20. package/dist/{toDate-D1Z7ZXWh.js → toDate-CWNNlFEX.js} +15 -7
  21. package/dist/{useAsyncData-BMc8itk2.js → useAsyncData-KfHB8wQR.js} +1 -1
  22. package/dist/{useDeepCompareMemoize-ZwmDBRDY.js → useDeepCompareMemoize-nJMtxhm4.js} +1 -1
  23. package/dist/{useLifecycle-CxffarYV.js → useLifecycle-DegSo0lV.js} +1 -1
  24. package/dist/{useTheme-yGsGEk82.js → useTheme-6eZ3GOTS.js} +5 -2
  25. package/dist/{vega-component-BFJTyykA.js → vega-component-DzyyM9fc.js} +5 -5
  26. package/package.json +4 -4
  27. package/src/__mocks__/requests.ts +1 -0
  28. package/src/__tests__/CellStatus.test.tsx +1 -2
  29. package/src/components/app-config/user-config-form.tsx +52 -0
  30. package/src/components/data-table/__tests__/column-explorer.test.tsx +25 -0
  31. package/src/components/data-table/__tests__/column-visibility-dropdown.test.tsx +60 -3
  32. package/src/components/data-table/charts/__tests__/altair-generator.test.ts +24 -0
  33. package/src/components/data-table/charts/__tests__/merge-index-fields.test.ts +33 -0
  34. package/src/components/data-table/charts/chart-spec/altair-generator.ts +4 -1
  35. package/src/components/data-table/charts/charts.tsx +23 -1
  36. package/src/components/data-table/column-explorer-panel/column-explorer.tsx +33 -12
  37. package/src/components/data-table/column-visibility-dropdown.tsx +15 -0
  38. package/src/components/debugger/debugger-code.tsx +7 -2
  39. package/src/components/editor/cell/CellStatus.tsx +1 -20
  40. package/src/components/editor/cell/PendingDeleteConfirmation.tsx +1 -1
  41. package/src/components/editor/cell/cell-context-menu.tsx +2 -2
  42. package/src/components/editor/chrome/panels/snippets-panel.tsx +3 -3
  43. package/src/components/editor/chrome/wrapper/footer-items/pyodide-status.tsx +6 -36
  44. package/src/components/editor/code/__tests__/readonly-python-code.test.tsx +79 -0
  45. package/src/components/editor/code/readonly-python-code.tsx +60 -33
  46. package/src/components/editor/errors/__tests__/auto-fix.test.ts +11 -107
  47. package/src/components/editor/errors/auto-fix.tsx +11 -31
  48. package/src/components/editor/notebook-cell.tsx +7 -0
  49. package/src/components/editor/output/ImageOutput.tsx +20 -6
  50. package/src/components/editor/output/MarimoTracebackOutput.tsx +20 -3
  51. package/src/components/editor/output/__tests__/ImageOutput.test.tsx +53 -0
  52. package/src/components/editor/renderers/grid-layout/grid-layout.tsx +7 -2
  53. package/src/components/editor/renderers/vertical-layout/vertical-layout.tsx +7 -3
  54. package/src/components/lifecycle/ProgressiveBoundary.tsx +42 -0
  55. package/src/components/lifecycle/RuntimeStatusBadge.tsx +59 -0
  56. package/src/components/lifecycle/__tests__/ProgressiveBoundary.test.tsx +147 -0
  57. package/src/components/lifecycle/__tests__/RuntimeStatusBadge.test.tsx +72 -0
  58. package/src/components/slides/__tests__/slide.test.tsx +33 -0
  59. package/src/components/slides/minimap.tsx +7 -1
  60. package/src/components/slides/reveal-component.tsx +3 -0
  61. package/src/components/slides/slide-cell-view.tsx +10 -6
  62. package/src/components/slides/slide.tsx +16 -13
  63. package/src/components/tracing/tracing.tsx +2 -1
  64. package/src/core/ai/context/__tests__/registry.test.ts +133 -3
  65. package/src/core/ai/context/providers/__tests__/datasource.test.ts +3 -1
  66. package/src/core/ai/context/providers/__tests__/error.test.ts +200 -15
  67. package/src/core/ai/context/providers/datasource.ts +27 -2
  68. package/src/core/ai/context/providers/error.ts +226 -36
  69. package/src/core/ai/context/registry.ts +77 -43
  70. package/src/core/cells/__tests__/cell.test.ts +39 -0
  71. package/src/core/cells/__tests__/readonly-code-display.test.ts +46 -0
  72. package/src/core/cells/cell.ts +5 -3
  73. package/src/core/cells/readonly-code-display.ts +35 -0
  74. package/src/core/codemirror/__tests__/setup.test.ts +33 -1
  75. package/src/core/codemirror/ai/resources.ts +15 -10
  76. package/src/core/codemirror/cells/__tests__/debugger-decorations.test.ts +185 -0
  77. package/src/core/codemirror/cells/__tests__/debugger-state.test.ts +224 -0
  78. package/src/core/codemirror/cells/__tests__/line-timing-decorations.test.ts +126 -0
  79. package/src/core/codemirror/cells/debugger-decorations.ts +188 -0
  80. package/src/core/codemirror/cells/debugger-state.ts +133 -0
  81. package/src/core/codemirror/cells/extensions.ts +32 -4
  82. package/src/core/codemirror/cells/line-timing-decorations.ts +165 -0
  83. package/src/core/codemirror/go-to-definition/__tests__/utils.test.ts +75 -3
  84. package/src/core/codemirror/go-to-definition/extension.ts +3 -3
  85. package/src/core/codemirror/go-to-definition/underline.ts +5 -14
  86. package/src/core/codemirror/go-to-definition/utils.ts +49 -1
  87. package/src/core/config/__tests__/config-schema.test.ts +17 -0
  88. package/src/core/config/config-schema.ts +1 -0
  89. package/src/core/config/config.ts +10 -0
  90. package/src/core/config/feature-flag.tsx +4 -0
  91. package/src/core/edit-app.tsx +8 -5
  92. package/src/core/errors/errors.ts +2 -1
  93. package/src/core/islands/bootstrap.ts +4 -3
  94. package/src/core/islands/bridge.ts +1 -0
  95. package/src/core/lifecycle/__tests__/render-policy.test.ts +247 -0
  96. package/src/core/lifecycle/render-policy.ts +125 -0
  97. package/src/core/mime.ts +11 -4
  98. package/src/core/network/__tests__/api.test.ts +17 -0
  99. package/src/core/network/__tests__/requests-toasting.test.tsx +46 -0
  100. package/src/core/network/api.ts +17 -6
  101. package/src/core/network/requests-lazy.ts +1 -0
  102. package/src/core/network/requests-network.ts +8 -0
  103. package/src/core/network/requests-static.ts +1 -0
  104. package/src/core/network/requests-toasting.tsx +10 -1
  105. package/src/core/network/types.ts +2 -0
  106. package/src/core/run-app.tsx +27 -23
  107. package/src/core/runtime/__tests__/adapter.test.ts +160 -0
  108. package/src/core/runtime/__tests__/runtime.test.ts +45 -0
  109. package/src/core/runtime/adapter.ts +182 -0
  110. package/src/core/runtime/runtime.ts +30 -16
  111. package/src/core/wasm/PyodideLoader.tsx +20 -62
  112. package/src/core/wasm/bridge.ts +13 -4
  113. package/src/core/wasm/state.ts +8 -19
  114. package/src/core/websocket/__tests__/useMarimoKernelConnection.test.ts +4 -0
  115. package/src/core/websocket/__tests__/useWebSocket.test.ts +49 -0
  116. package/src/core/websocket/transports/__tests__/sse.test.ts +338 -0
  117. package/src/core/websocket/transports/sse.ts +308 -0
  118. package/src/core/websocket/useMarimoKernelConnection.tsx +58 -7
  119. package/src/core/websocket/useWebSocket.tsx +12 -2
  120. package/src/css/app/codemirror.css +14 -0
  121. package/src/hooks/__tests__/useDelayElapsed.test.tsx +55 -0
  122. package/src/hooks/useDelayElapsed.ts +27 -0
  123. package/src/mount.tsx +3 -3
  124. package/src/plugins/core/RenderHTML.tsx +35 -1
  125. package/src/plugins/core/__test__/RenderHTML.test.ts +44 -0
  126. package/src/plugins/impl/DataTablePlugin.tsx +1 -0
  127. package/src/plugins/impl/anywidget/AnyWidgetPlugin.tsx +33 -254
  128. package/src/plugins/impl/anywidget/__tests__/AnyWidgetPlugin.test.tsx +116 -174
  129. package/src/plugins/impl/anywidget/__tests__/host.test.ts +313 -0
  130. package/src/plugins/impl/anywidget/__tests__/model-proxy.test.ts +158 -0
  131. package/src/plugins/impl/anywidget/__tests__/model.test.ts +8 -177
  132. package/src/plugins/impl/anywidget/__tests__/registry.test.ts +708 -0
  133. package/src/plugins/impl/anywidget/__tests__/resolve-widget.test.ts +132 -0
  134. package/src/plugins/impl/anywidget/__tests__/widget-binding.test.ts +305 -133
  135. package/src/plugins/impl/anywidget/__tests__/widget-ref.test.ts +28 -0
  136. package/src/plugins/impl/anywidget/host.ts +54 -0
  137. package/src/plugins/impl/anywidget/model-proxy.ts +70 -0
  138. package/src/plugins/impl/anywidget/model.ts +59 -239
  139. package/src/plugins/impl/anywidget/registry.ts +268 -0
  140. package/src/plugins/impl/anywidget/resolve-widget.ts +138 -0
  141. package/src/plugins/impl/anywidget/runtime.ts +422 -0
  142. package/src/plugins/impl/anywidget/types.ts +14 -0
  143. package/src/plugins/impl/anywidget/widget-binding.ts +280 -119
  144. package/src/plugins/impl/anywidget/widget-ref.ts +29 -0
  145. package/src/plugins/impl/data-editor/__tests__/glide-data-editor.test.tsx +187 -0
  146. package/src/plugins/impl/data-editor/glide-data-editor.tsx +6 -0
  147. package/src/plugins/impl/data-editor/glide-portal.tsx +73 -0
  148. package/src/plugins/impl/mpl-interactive/MplInteractivePlugin.tsx +3 -2
  149. package/src/plugins/impl/mpl-interactive/__tests__/MplInteractivePlugin.test.tsx +16 -13
  150. package/src/plugins/impl/mpl-interactive/mpl-websocket-shim.ts +1 -1
  151. package/src/utils/errors.ts +15 -0
  152. package/src/utils/time.ts +20 -0
  153. package/dist/assets/__vite-browser-external-DAm_YW43.js +0 -1
  154. package/dist/data-grid-overlay-editor-mfEJ5475.js +0 -128
  155. package/src/components/editor/chrome/panels/__tests__/snippet-display.test.ts +0 -22
  156. package/src/components/editor/chrome/panels/snippet-display.ts +0 -27
  157. package/src/core/ai/context/providers/__tests__/__snapshots__/error.test.ts.snap +0 -3
  158. package/src/core/wasm/__tests__/PyodideLoader.test.ts +0 -72
  159. package/src/core/wasm/__tests__/state.test.ts +0 -124
  160. package/src/plugins/impl/anywidget/schemas.ts +0 -32
@@ -6,7 +6,7 @@ import { _ as Logger } from "./button-BacYv-bE.js";
6
6
  import { t as require_compiler_runtime } from "./compiler-runtime-CEbnTgxf.js";
7
7
  import { u as createLucideIcon } from "./dist--2Bqjvs0.js";
8
8
  import { r as KnownQueryParams } from "./constants-T20xxyNf.js";
9
- import { S as atom, g as isIslands, h as waitFor, p as store } from "./useTheme-yGsGEk82.js";
9
+ import { S as atom, g as isIslands, h as waitFor, p as store } from "./useTheme-6eZ3GOTS.js";
10
10
  import { t as invariant } from "./invariant-wRzNXIsJ.js";
11
11
  var CircleQuestionMark = createLucideIcon("circle-question-mark", [
12
12
  ["circle", {
@@ -529,17 +529,25 @@ var RuntimeManager = class {
529
529
  });
530
530
  return !this.isSameOrigin && this.config.authToken && x.searchParams.set(KnownQueryParams.accessToken, this.config.authToken), asWsUrl(x.toString());
531
531
  }
532
- getWsURL(e) {
532
+ getSessionSearchParams(e) {
533
533
  let b = new URL(this.config.url), x = new URLSearchParams(b.search);
534
534
  return new URLSearchParams(window.location.search).forEach((e2, b2) => {
535
535
  x.has(b2) || x.set(b2, e2);
536
- }), x.set(KnownQueryParams.sessionId, e), this.formatWsURL("/ws", x);
536
+ }), x.set(KnownQueryParams.sessionId, e), x;
537
+ }
538
+ getWsURL(e) {
539
+ return this.formatWsURL("/ws", this.getSessionSearchParams(e));
540
+ }
541
+ getSseURL(e) {
542
+ let b = this.formatHttpURL({
543
+ path: "/sse",
544
+ searchParams: this.getSessionSearchParams(e),
545
+ restrictToKnownQueryParams: false
546
+ });
547
+ return b.searchParams.delete(KnownQueryParams.accessToken), b;
537
548
  }
538
549
  getWsSyncURL(e) {
539
- let b = new URL(this.config.url), x = new URLSearchParams(b.search);
540
- return new URLSearchParams(window.location.search).forEach((e2, b2) => {
541
- x.has(b2) || x.set(b2, e2);
542
- }), x.set(KnownQueryParams.sessionId, e), this.formatWsURL("/ws_sync", x);
550
+ return this.formatWsURL("/ws_sync", this.getSessionSearchParams(e));
543
551
  }
544
552
  getTerminalWsURL() {
545
553
  return this.formatWsURL("/terminal/ws");
@@ -1,7 +1,7 @@
1
1
  import { s as __toESM } from "./chunk-BNovOVIE.js";
2
2
  import { t as require_react } from "./react-DA-nE2FX.js";
3
3
  import { t as require_compiler_runtime } from "./compiler-runtime-CEbnTgxf.js";
4
- import { D as useEvent_default } from "./useTheme-yGsGEk82.js";
4
+ import { D as useEvent_default } from "./useTheme-6eZ3GOTS.js";
5
5
  import { t as invariant } from "./invariant-wRzNXIsJ.js";
6
6
  var import_compiler_runtime = require_compiler_runtime(), import_react = /* @__PURE__ */ __toESM(require_react(), 1), Result = {
7
7
  error(e, s) {
@@ -1,6 +1,6 @@
1
1
  import { s as __toESM } from "./chunk-BNovOVIE.js";
2
2
  import { t as require_react } from "./react-DA-nE2FX.js";
3
- import { E as dequal } from "./useTheme-yGsGEk82.js";
3
+ import { E as dequal } from "./useTheme-6eZ3GOTS.js";
4
4
  var import_react = /* @__PURE__ */ __toESM(require_react(), 1);
5
5
  function useDeepCompareMemoize(e) {
6
6
  let i = import_react.useRef(e);
@@ -4,7 +4,7 @@ import { t as require_react } from "./react-DA-nE2FX.js";
4
4
  import { t as require_compiler_runtime } from "./compiler-runtime-CEbnTgxf.js";
5
5
  import { u as createLucideIcon } from "./dist--2Bqjvs0.js";
6
6
  import { t as require_jsx_runtime } from "./jsx-runtime-DebpN0FN.js";
7
- import { S as atom, b as useSetAtom } from "./useTheme-yGsGEk82.js";
7
+ import { S as atom, b as useSetAtom } from "./useTheme-6eZ3GOTS.js";
8
8
  var Calendar = createLucideIcon("calendar", [
9
9
  ["path", {
10
10
  d: "M8 2v4",
@@ -637,7 +637,10 @@ const UserConfigSchema = looseObject({
637
637
  markdown: boolean().optional(),
638
638
  rtc: boolean().optional()
639
639
  }).prefault(() => ({})),
640
- server: looseObject({ disable_file_downloads: boolean().optional() }).prefault(() => ({})),
640
+ server: looseObject({
641
+ disable_file_downloads: boolean().optional(),
642
+ transport: _enum(["websocket", "sse"]).optional()
643
+ }).prefault(() => ({})),
641
644
  diagnostics: looseObject({
642
645
  enabled: boolean().optional(),
643
646
  sql_linter: boolean().optional()
@@ -701,7 +704,7 @@ const userConfigAtom = atom(defaultUserConfig()), configOverridesAtom = atom({})
701
704
  let A = e(configOverridesAtom);
702
705
  return merge_default({}, e(userConfigAtom), A);
703
706
  }), autoInstantiateAtom = atom((e) => e(resolvedMarimoConfigAtom).runtime.auto_instantiate), hotkeyOverridesAtom = atom((e) => e(resolvedMarimoConfigAtom).keymap.overrides ?? {}), platformAtom = atom(resolvePlatform());
704
- atom((e) => new OverridingHotkeyProvider(e(hotkeyOverridesAtom), { platform: e(platformAtom) })), atom((e) => e(resolvedMarimoConfigAtom).save), atom((e) => e(resolvedMarimoConfigAtom).ai), atom((e) => e(resolvedMarimoConfigAtom).completion), atom((e) => e(resolvedMarimoConfigAtom).keymap.preset);
707
+ atom((e) => new OverridingHotkeyProvider(e(hotkeyOverridesAtom), { platform: e(platformAtom) })), atom((e) => e(resolvedMarimoConfigAtom).save), atom((e) => e(resolvedMarimoConfigAtom).server.transport === "sse" ? "sse" : "websocket"), atom((e) => e(resolvedMarimoConfigAtom).ai), atom((e) => e(resolvedMarimoConfigAtom).completion), atom((e) => e(resolvedMarimoConfigAtom).keymap.preset);
705
708
  function useResolvedMarimoConfig() {
706
709
  let e = (0, import_compiler_runtime$1.c)(3), A = useAtomValue(resolvedMarimoConfigAtom), j = useSetAtom(userConfigAtom), M;
707
710
  return e[0] !== A || e[1] !== j ? (M = [A, j], e[0] = A, e[1] = j, e[2] = M) : M = e[2], M;
@@ -2,23 +2,23 @@ import { s as __toESM } from "./chunk-BNovOVIE.js";
2
2
  import { _ as Logger, c as Objects, g as cn, h as Events } from "./button-BacYv-bE.js";
3
3
  import { t as require_react } from "./react-DA-nE2FX.js";
4
4
  import { t as require_compiler_runtime } from "./compiler-runtime-CEbnTgxf.js";
5
- import { c as asRemoteURL, v as CircleQuestionMark } from "./toDate-D1Z7ZXWh.js";
5
+ import { c as asRemoteURL, v as CircleQuestionMark } from "./toDate-CWNNlFEX.js";
6
6
  import "./react-dom-BTJzcVJ9.js";
7
7
  import { t as require_jsx_runtime } from "./jsx-runtime-DebpN0FN.js";
8
8
  import "./zod-CijjQh4u.js";
9
9
  import { n as ErrorBanner } from "./error-banner-DFPfz_Qf.js";
10
10
  import { t as Tooltip } from "./tooltip-Czds6Qr8.js";
11
11
  import { i as debounce_default } from "./constants-T20xxyNf.js";
12
- import { D as useEvent_default, n as useTheme } from "./useTheme-yGsGEk82.js";
12
+ import { D as useEvent_default, n as useTheme } from "./useTheme-6eZ3GOTS.js";
13
13
  import { s as uniq } from "./arrays-sEtDRoG4.js";
14
- import { a as isValid, i as AlertTitle, n as Alert, t as arrow } from "./formats-WsOgyW_K.js";
14
+ import { a as isValid, i as AlertTitle, n as Alert, t as arrow } from "./formats-Dzx4J_z1.js";
15
15
  import { n as formats } from "./vega-loader.browser-CZ-J8Py3.js";
16
16
  import { a as getContainerWidth, n as vegaLoadData, s as tooltipHandler } from "./loader-Boph2xIS.js";
17
17
  import { t as j } from "./react-vega-B0sAlDTL.js";
18
18
  import "./defaultLocale-u-3osm0P.js";
19
19
  import "./defaultLocale-BoHTsDG6.js";
20
- import { t as useAsyncData } from "./useAsyncData-BMc8itk2.js";
21
- import { t as useDeepCompareMemoize } from "./useDeepCompareMemoize-ZwmDBRDY.js";
20
+ import { t as useAsyncData } from "./useAsyncData-KfHB8wQR.js";
21
+ import { t as useDeepCompareMemoize } from "./useDeepCompareMemoize-nJMtxhm4.js";
22
22
  import { t as Semaphore } from "./semaphore-CNDGTzkX.js";
23
23
  var import_compiler_runtime = require_compiler_runtime(), import_react = /* @__PURE__ */ __toESM(require_react(), 1);
24
24
  function fixRelativeUrl(e) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marimo-team/islands",
3
- "version": "0.23.14-dev9",
3
+ "version": "0.23.14",
4
4
  "main": "dist/main.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "type": "module",
@@ -22,7 +22,7 @@
22
22
  },
23
23
  "dependencies": {
24
24
  "@ai-sdk/react": "^3.0.134",
25
- "@anywidget/types": "^0.2.0",
25
+ "@anywidget/types": "^0.4.0",
26
26
  "@codemirror/autocomplete": "^6.20.1",
27
27
  "@codemirror/commands": "^6.10.2",
28
28
  "@codemirror/lang-markdown": "^6.5.0",
@@ -45,7 +45,7 @@
45
45
  "@dnd-kit/utilities": "^3.2.2",
46
46
  "@emotion/cache": "^11.14.0",
47
47
  "@emotion/react": "^11.14.0",
48
- "@glideapps/glide-data-grid": "6.0.4-alpha9",
48
+ "@glideapps/glide-data-grid": "6.0.4-alpha24",
49
49
  "@hookform/resolvers": "^5.2.2",
50
50
  "@img-comparison-slider/react": "^8.0.2",
51
51
  "@internationalized/date": "^3.10.1",
@@ -56,7 +56,7 @@
56
56
  "@lezer/python": "^1.1.18",
57
57
  "@marimo-team/codemirror-ai": "^0.3.7",
58
58
  "@marimo-team/codemirror-languageserver": "^1.16.12",
59
- "@marimo-team/codemirror-mcp": "^0.1.5",
59
+ "@marimo-team/codemirror-mcp": "^0.1.7",
60
60
  "@marimo-team/codemirror-sql": "^0.2.8",
61
61
  "@marimo-team/llm-info": "workspace:*",
62
62
  "@marimo-team/marimo-api": "workspace:*",
@@ -45,6 +45,7 @@ export const MockRequestClient = {
45
45
  openFile: vi.fn().mockResolvedValue({}),
46
46
  getUsageStats: vi.fn().mockResolvedValue({}),
47
47
  sendPdb: vi.fn().mockResolvedValue({}),
48
+ sendSetBreakpoints: vi.fn().mockResolvedValue({}),
48
49
  sendListFiles: vi.fn().mockResolvedValue({ files: [] }),
49
50
  sendSearchFiles: vi
50
51
  .fn()
@@ -3,11 +3,10 @@
3
3
  import { render } from "@testing-library/react";
4
4
  import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
5
5
  import { TooltipProvider } from "@/components/ui/tooltip";
6
- import type { Seconds } from "@/utils/time";
6
+ import { formatElapsedTime, type Seconds } from "@/utils/time";
7
7
  import {
8
8
  CellStatusComponent,
9
9
  ElapsedTime,
10
- formatElapsedTime,
11
10
  } from "../components/editor/cell/CellStatus";
12
11
 
13
12
  // Mock date-fns to have consistent date formatting in tests
@@ -1288,6 +1288,58 @@ export const UserConfigForm: React.FC = () => {
1288
1288
  </div>
1289
1289
  )}
1290
1290
  />
1291
+ <OverriddenFormField
1292
+ control={form.control}
1293
+ name="experimental.debugger"
1294
+ render={({ field, override }) => (
1295
+ <div className="flex flex-col gap-y-1">
1296
+ <FormItem className={formItemClasses}>
1297
+ <FormLabel className="font-normal">Debugger</FormLabel>
1298
+ <FormControl>
1299
+ <Checkbox
1300
+ data-testid="debugger-checkbox"
1301
+ checked={override.value === true}
1302
+ disabled={override.isOverridden}
1303
+ onCheckedChange={field.onChange}
1304
+ />
1305
+ </FormControl>
1306
+ </FormItem>
1307
+ <IsOverridden override={override} />
1308
+ <FormDescription>
1309
+ Enable the live debugger: a clickable breakpoint gutter,
1310
+ current-line highlighting while a cell runs, and dropping
1311
+ into <Kbd className="inline">pdb</Kbd> at breakpoints. This
1312
+ change requires a page refresh to take effect.
1313
+ </FormDescription>
1314
+ </div>
1315
+ )}
1316
+ />
1317
+ <OverriddenFormField
1318
+ control={form.control}
1319
+ name="experimental.line_timing"
1320
+ render={({ field, override }) => (
1321
+ <div className="flex flex-col gap-y-1">
1322
+ <FormItem className={formItemClasses}>
1323
+ <FormLabel className="font-normal">Line Timing</FormLabel>
1324
+ <FormControl>
1325
+ <Checkbox
1326
+ data-testid="line-timing-checkbox"
1327
+ checked={override.value === true}
1328
+ disabled={override.isOverridden}
1329
+ onCheckedChange={field.onChange}
1330
+ />
1331
+ </FormControl>
1332
+ </FormItem>
1333
+ <IsOverridden override={override} />
1334
+ <FormDescription>
1335
+ Highlight the line a cell is currently executing in green,
1336
+ with an elapsed timer next to long-running lines. Adds
1337
+ tracing overhead to every run while enabled. This change
1338
+ requires a page refresh to take effect.
1339
+ </FormDescription>
1340
+ </div>
1341
+ )}
1342
+ />
1291
1343
  </SettingGroup>
1292
1344
  );
1293
1345
  }
@@ -126,3 +126,28 @@ describe("ColumnExplorerPanel header counts", () => {
126
126
  expect(screen.queryByText(/hidden/)).not.toBeInTheDocument();
127
127
  });
128
128
  });
129
+
130
+ describe("ColumnExplorerPanel visibility actions", () => {
131
+ const showAll = () => screen.getByRole("button", { name: /Show all/ });
132
+ const hideAll = () => screen.getByRole("button", { name: /Hide all/ });
133
+
134
+ it("disables 'Show all' when every column is visible", () => {
135
+ renderPanel();
136
+ expect(showAll()).toBeDisabled();
137
+ expect(hideAll()).toBeEnabled();
138
+ });
139
+
140
+ it("disables 'Hide all' when every column is hidden", () => {
141
+ renderPanel({
142
+ initiallyHidden: ["customer_name", "cust_age", "order_total"],
143
+ });
144
+ expect(showAll()).toBeEnabled();
145
+ expect(hideAll()).toBeDisabled();
146
+ });
147
+
148
+ it("enables both actions when some columns are hidden", () => {
149
+ renderPanel({ initiallyHidden: ["cust_age"] });
150
+ expect(showAll()).toBeEnabled();
151
+ expect(hideAll()).toBeEnabled();
152
+ });
153
+ });
@@ -21,10 +21,17 @@ beforeAll(() => {
21
21
 
22
22
  type Row = Record<string, unknown>;
23
23
 
24
+ // Select, index, and nameless columns are non-hideable in production
25
+ // (see columns.tsx), so the harness mirrors that to keep visibility counts
26
+ // aligned with the real table.
24
27
  const TEST_COLUMNS: ColumnDef<Row>[] = [
25
- { id: SELECT_COLUMN_ID, accessorKey: SELECT_COLUMN_ID },
26
- { id: INDEX_COLUMN_NAME, accessorKey: INDEX_COLUMN_NAME },
27
- { id: "__m_column__0", accessorKey: "__m_column__0" },
28
+ { id: SELECT_COLUMN_ID, accessorKey: SELECT_COLUMN_ID, enableHiding: false },
29
+ {
30
+ id: INDEX_COLUMN_NAME,
31
+ accessorKey: INDEX_COLUMN_NAME,
32
+ enableHiding: false,
33
+ },
34
+ { id: "__m_column__0", accessorKey: "__m_column__0", enableHiding: false },
28
35
  {
29
36
  id: "customer_name",
30
37
  accessorKey: "customer_name",
@@ -101,6 +108,7 @@ describe("ColumnVisibilityDropdown", () => {
101
108
  renderAndOpen({ initiallyHidden: ["cust_age"] });
102
109
  expect(getOptionTexts()).toEqual([
103
110
  "Show all",
111
+ "Hide all",
104
112
  "cust_age",
105
113
  "customer_name",
106
114
  "order_total",
@@ -117,6 +125,7 @@ describe("ColumnVisibilityDropdown", () => {
117
125
 
118
126
  expect(getOptionTexts()).toEqual([
119
127
  "Show all",
128
+ "Hide all",
120
129
  "cust_age",
121
130
  "customer_name",
122
131
  "order_total",
@@ -132,6 +141,7 @@ describe("ColumnVisibilityDropdown", () => {
132
141
  fireEvent.click(getColumnOption("customer_name"));
133
142
  expect(getOptionTexts()).toEqual([
134
143
  "Show all",
144
+ "Hide all",
135
145
  "cust_age",
136
146
  "customer_name",
137
147
  "order_total",
@@ -144,6 +154,7 @@ describe("ColumnVisibilityDropdown", () => {
144
154
  // Reopen sorts both hidden columns first, preserving table order.
145
155
  expect(getOptionTexts()).toEqual([
146
156
  "Show all",
157
+ "Hide all",
147
158
  "customer_name",
148
159
  "cust_age",
149
160
  "order_total",
@@ -189,6 +200,52 @@ describe("ColumnVisibilityDropdown", () => {
189
200
  );
190
201
  });
191
202
 
203
+ it("'Hide all' hides every hideable column", () => {
204
+ renderAndOpen();
205
+ fireEvent.click(getColumnOption("Hide all"));
206
+
207
+ expect(
208
+ getColumnOption("customer_name").querySelector(".lucide-eye-off"),
209
+ ).not.toBeNull();
210
+ expect(
211
+ getColumnOption("cust_age").querySelector(".lucide-eye-off"),
212
+ ).not.toBeNull();
213
+ expect(
214
+ getColumnOption("order_total").querySelector(".lucide-eye-off"),
215
+ ).not.toBeNull();
216
+ expect(getColumnOption("Hide all")).toHaveAttribute(
217
+ "aria-disabled",
218
+ "true",
219
+ );
220
+ });
221
+
222
+ it("disables 'Hide all' when every column is already hidden", () => {
223
+ renderAndOpen({
224
+ initiallyHidden: ["customer_name", "cust_age", "order_total"],
225
+ });
226
+ expect(getColumnOption("Hide all")).toHaveAttribute(
227
+ "aria-disabled",
228
+ "true",
229
+ );
230
+ });
231
+
232
+ it("'Hide all' leaves non-hideable columns visible", () => {
233
+ renderAndOpen({ nonHideable: ["customer_name"] });
234
+ fireEvent.click(getColumnOption("Hide all"));
235
+
236
+ expect(
237
+ getColumnOption("cust_age").querySelector(".lucide-eye-off"),
238
+ ).not.toBeNull();
239
+ const nonHideable = getColumnOption("customer_name");
240
+ expect(nonHideable).toHaveAttribute("aria-disabled", "true");
241
+ expect(nonHideable.querySelector(".lucide-eye-off")).toBeNull();
242
+ // Every hideable column is now hidden, so the action gates off.
243
+ expect(getColumnOption("Hide all")).toHaveAttribute(
244
+ "aria-disabled",
245
+ "true",
246
+ );
247
+ });
248
+
192
249
  it("hides and shows matching columns via bulk actions while searching", () => {
193
250
  renderAndOpen();
194
251
  fireEvent.change(getSearchInput(), { target: { value: "cust" } });
@@ -85,6 +85,10 @@ const Mocks = {
85
85
  type: "arc",
86
86
  innerRadius: 100,
87
87
  },
88
+ arcDefault: {
89
+ type: "arc",
90
+ innerRadius: undefined,
91
+ },
88
92
  },
89
93
  };
90
94
 
@@ -128,6 +132,26 @@ describe("generateAltairChart", () => {
128
132
  `);
129
133
  });
130
134
 
135
+ it("should generate a pie chart when arc has undefined innerRadius", () => {
136
+ const spec = createSpec({
137
+ mark: Mocks.marks.arcDefault,
138
+ encoding: {
139
+ theta: Mocks.thetaAxis,
140
+ },
141
+ });
142
+ const datasource = "df";
143
+
144
+ const result = generateAltairChart(spec, datasource).toCode();
145
+
146
+ expect(result).toMatchInlineSnapshot(`
147
+ "alt.Chart(df)
148
+ .mark_arc()
149
+ .encode(theta=alt.Theta(field='theta', axis={
150
+ 'title': 'Theta Axis'
151
+ }))"
152
+ `);
153
+ });
154
+
131
155
  it("should generate a donut chart", () => {
132
156
  const spec = createSpec({
133
157
  mark: Mocks.marks.arc,
@@ -0,0 +1,33 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+
3
+ import { describe, expect, it } from "vitest";
4
+ import type { FieldTypesWithExternalType } from "@/components/data-table/types";
5
+ import { mergeIndexFields } from "../charts";
6
+
7
+ describe("mergeIndexFields", () => {
8
+ it("appends row-header (index) fields to field types", () => {
9
+ const fieldTypes: FieldTypesWithExternalType = [["v", ["number", "int64"]]];
10
+ const rowHeaders: FieldTypesWithExternalType = [
11
+ ["k", ["string", "object"]],
12
+ ];
13
+ expect(mergeIndexFields(fieldTypes, rowHeaders)).toEqual([
14
+ ["v", ["number", "int64"]],
15
+ ["k", ["string", "object"]],
16
+ ]);
17
+ });
18
+
19
+ it("de-dupes when an index name matches a column name", () => {
20
+ const fieldTypes: FieldTypesWithExternalType = [["k", ["number", "int64"]]];
21
+ const rowHeaders: FieldTypesWithExternalType = [
22
+ ["k", ["string", "object"]],
23
+ ];
24
+ expect(mergeIndexFields(fieldTypes, rowHeaders)).toEqual([
25
+ ["k", ["number", "int64"]],
26
+ ]);
27
+ });
28
+
29
+ it("returns field types unchanged when there are no row headers", () => {
30
+ const fieldTypes: FieldTypesWithExternalType = [["v", ["number", "int64"]]];
31
+ expect(mergeIndexFields(fieldTypes, [])).toEqual(fieldTypes);
32
+ });
33
+ });
@@ -30,7 +30,10 @@ export function generateAltairChart(
30
30
  if (typeof spec.mark === "object" && "type" in spec.mark) {
31
31
  markProps = Object.fromEntries(
32
32
  Object.entries(spec.mark)
33
- .filter(([key]) => key !== "type")
33
+ .filter(
34
+ ([key, value]) =>
35
+ key !== "type" && value !== undefined && value !== null,
36
+ )
34
37
  .map(([key, value]) => [key, new Literal(value)]),
35
38
  );
36
39
  }
@@ -27,6 +27,7 @@ import { useDebouncedCallback } from "@/hooks/useDebounce";
27
27
  import type { GetDataUrl } from "@/plugins/impl/DataTablePlugin";
28
28
  import { vegaLoadData } from "@/plugins/impl/vega/loader";
29
29
  import { useTheme } from "@/theme/useTheme";
30
+ import { uniqueBy } from "@/utils/arrays";
30
31
  import { inferFieldTypes } from "../columns";
31
32
  import {
32
33
  type FieldTypesWithExternalType,
@@ -54,6 +55,22 @@ const CHART_HEIGHT = 290;
54
55
  const CHART_MAX_ROWS = 50_000;
55
56
  const CHART_MAX_COLUMNS = 50;
56
57
 
58
+ /**
59
+ * Append row-header (index) fields to the chart field list, skipping any whose
60
+ * name already exists as a data column so the chart builder never offers the
61
+ * same axis twice.
62
+ */
63
+ export function mergeIndexFields(
64
+ fieldTypes: FieldTypesWithExternalType | null | undefined,
65
+ rowHeaders: FieldTypesWithExternalType | null | undefined,
66
+ ): FieldTypesWithExternalType {
67
+ const base = fieldTypes ?? [];
68
+ if (!rowHeaders || rowHeaders.length === 0) {
69
+ return base;
70
+ }
71
+ return uniqueBy([...base, ...rowHeaders], (f) => f[0]);
72
+ }
73
+
57
74
  export interface TablePanelProps {
58
75
  cellId: CellId | null;
59
76
  data: unknown[];
@@ -64,6 +81,7 @@ export interface TablePanelProps {
64
81
  onCloseChartBuilder?: () => void;
65
82
  getDataUrl?: GetDataUrl;
66
83
  fieldTypes?: FieldTypesWithExternalType | null;
84
+ rowHeaders?: FieldTypesWithExternalType | null;
67
85
  }
68
86
 
69
87
  export const TablePanel: React.FC<TablePanelProps> = ({
@@ -74,6 +92,7 @@ export const TablePanel: React.FC<TablePanelProps> = ({
74
92
  columns,
75
93
  getDataUrl,
76
94
  fieldTypes,
95
+ rowHeaders,
77
96
  displayHeader,
78
97
  onCloseChartBuilder,
79
98
  }) => {
@@ -265,7 +284,10 @@ export const TablePanel: React.FC<TablePanelProps> = ({
265
284
  saveChart={saveChart}
266
285
  saveChartType={saveChartType}
267
286
  getDataUrl={getDataUrl}
268
- fieldTypes={fieldTypes ?? inferFieldTypes(dataTable.props.data)}
287
+ fieldTypes={mergeIndexFields(
288
+ fieldTypes ?? inferFieldTypes(dataTable.props.data),
289
+ rowHeaders,
290
+ )}
269
291
  isLargeDataset={isLargeDataset}
270
292
  />
271
293
  </TabsContent>
@@ -50,7 +50,10 @@ import {
50
50
  import { smartMatch } from "@/utils/smartMatch";
51
51
  import type { Column, Table } from "@tanstack/react-table";
52
52
  import { cn } from "@/utils/cn";
53
- import { getColumnCountForDisplay } from "../hooks/use-column-visibility";
53
+ import {
54
+ getColumnCountForDisplay,
55
+ getUserColumnVisibilityCounts,
56
+ } from "../hooks/use-column-visibility";
54
57
 
55
58
  interface ColumnExplorerPanelProps<TData> {
56
59
  previewColumn: PreviewColumn;
@@ -90,6 +93,7 @@ export function ColumnExplorerPanel<TData>({
90
93
  totalColumns: effectiveTotalColumns,
91
94
  hiddenColumns: hiddenColumnCount,
92
95
  } = getColumnCountForDisplay(table, totalColumns);
96
+ const { visible: visibleColumnCount } = getUserColumnVisibilityCounts(table);
93
97
 
94
98
  const { rowsAndColumns, hiddenSuffix } = prettifyRowColumnCount({
95
99
  numRows: totalRows,
@@ -108,17 +112,34 @@ export function ColumnExplorerPanel<TData>({
108
112
  value={columns?.map(([columnName]) => columnName).join(",\n") || ""}
109
113
  className="h-3 w-3"
110
114
  />
111
- {hiddenColumnCount > 0 && (
112
- <Button
113
- type="button"
114
- variant="link"
115
- size="xs"
116
- className="h-auto p-0"
117
- onClick={() => table.resetColumnVisibility(true)}
118
- >
119
- Unhide all
120
- </Button>
121
- )}
115
+ <div className="ml-auto flex items-center gap-1">
116
+ <Tooltip content="Show all columns" delayDuration={400}>
117
+ <Button
118
+ type="button"
119
+ variant="ghost"
120
+ size="xs"
121
+ className="gap-1"
122
+ disabled={hiddenColumnCount === 0}
123
+ onClick={() => table.toggleAllColumnsVisible(true)}
124
+ >
125
+ <EyeIcon className="w-3 h-3" />
126
+ Show all
127
+ </Button>
128
+ </Tooltip>
129
+ <Tooltip content="Hide all columns" delayDuration={400}>
130
+ <Button
131
+ type="button"
132
+ variant="ghost"
133
+ size="xs"
134
+ className="gap-1"
135
+ disabled={visibleColumnCount === 0}
136
+ onClick={() => table.toggleAllColumnsVisible(false)}
137
+ >
138
+ <EyeOffIcon className="w-3 h-3" />
139
+ Hide all
140
+ </Button>
141
+ </Tooltip>
142
+ </div>
122
143
  </div>
123
144
  <Command className="h-5/6 bg-background" shouldFilter={false}>
124
145
  <CommandInput
@@ -59,6 +59,12 @@ export const ColumnVisibilityDropdown = <TData,>({
59
59
  const hiddenIds = userColumns
60
60
  .filter((column) => !column.getIsVisible())
61
61
  .map((column) => column.id);
62
+ const hideableIds = userColumns
63
+ .filter((column) => column.getCanHide())
64
+ .map((column) => column.id);
65
+ const visibleHideableCount = hideableIds.filter(
66
+ (id) => !hiddenIds.includes(id),
67
+ ).length;
62
68
 
63
69
  const applyHidden = (next: string[] | string | null) => {
64
70
  const hidden = new Set(Array.isArray(next) ? next : []);
@@ -127,6 +133,15 @@ export const ColumnVisibilityDropdown = <TData,>({
127
133
  <EyeIcon className="w-3 h-3 mr-1.5" />
128
134
  Show all
129
135
  </CommandItem>
136
+ <CommandItem
137
+ value="__hide_all__"
138
+ disabled={visibleHideableCount === 0}
139
+ onSelect={() => applyHidden(hideableIds)}
140
+ className="cursor-pointer"
141
+ >
142
+ <EyeOffIcon className="w-3 h-3 mr-1.5" />
143
+ Hide all
144
+ </CommandItem>
130
145
  <CommandSeparator />
131
146
  </>
132
147
  ) : (
@@ -208,7 +208,7 @@ export const DebuggerControls: React.FC<{
208
208
  </Button>
209
209
  </Tooltip>
210
210
  {onClear && (
211
- <Tooltip content="Clear">
211
+ <Tooltip content="Quit and clear">
212
212
  <Button
213
213
  variant="text"
214
214
  size="icon"
@@ -217,7 +217,12 @@ export const DebuggerControls: React.FC<{
217
217
  buttonClasses,
218
218
  "text-(--red-11) hover:text-(--red-11) hover:bg-(--red-2) hover:border-(--red-8)",
219
219
  )}
220
- onClick={onClear}
220
+ onClick={() => {
221
+ // Quit the debugger (`q`) before tearing down the console, so
222
+ // the stdin response targets the still-present pdb prompt.
223
+ onSubmit("q");
224
+ onClear();
225
+ }}
221
226
  >
222
227
  <TrashIcon fontSize={36} className={iconClasses} />
223
228
  </Button>