@marimo-team/islands 0.23.10-dev9 → 0.23.11-dev67

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 (188) hide show
  1. package/dist/{ConnectedDataExplorerComponent-CyV83R2m.js → ConnectedDataExplorerComponent-Z3RP7Vmm.js} +31 -31
  2. package/dist/{ErrorBoundary-rULOrC_p.js → ErrorBoundary-DpbaKVv7.js} +1 -1
  3. package/dist/{any-language-editor-CiES2a2h.js → any-language-editor-CAgFD4Kd.js} +20 -20
  4. package/dist/assets/__vite-browser-external-eshhtsgZ.js +1 -0
  5. package/dist/assets/worker-CC0Oul9k.js +73 -0
  6. package/dist/{chat-ui-K84W5ecY.js → chat-ui-C4-GMW9y.js} +77 -77
  7. package/dist/{check-DTbrK0zt.js → check-BCaJeT-J.js} +1 -1
  8. package/dist/{chunk-5FQGJX7Z-BNjes6Yx.js → chunk-5FQGJX7Z-BbqSm5gU.js} +1227 -1222
  9. package/dist/{code-block-37QAKDTI-m92Yc8pv.js → code-block-37QAKDTI-O6IH6wAi.js} +1 -1
  10. package/dist/{code-visibility-v6ucEVUg.js → code-visibility-UH2371Sv.js} +1943 -1223
  11. package/dist/{copy-5jQ_kGE1.js → copy-UqRYxiOg.js} +1 -1
  12. package/dist/dist-7QfXoMdB.js +5 -0
  13. package/dist/{dist-DgnE8F-r.js → dist-A2846XWO.js} +1 -1
  14. package/dist/dist-BEXXyZig.js +5 -0
  15. package/dist/{dist-B3pZ0Ab6.js → dist-BR_gyG9L.js} +3 -3
  16. package/dist/{dist-CcXxepx6.js → dist-BSAt6RhH.js} +27 -27
  17. package/dist/{dist-Bde4a2kU.js → dist-BY018Paw.js} +8 -8
  18. package/dist/dist-BYj57OV4.js +5 -0
  19. package/dist/{dist-CUCNs1ja.js → dist-BaoDKvdy.js} +2 -2
  20. package/dist/{dist-Cy1WxgBD.js → dist-Bf7SHuNp.js} +5 -5
  21. package/dist/{dist-Bz_sYWbr.js → dist-Bk75fBZA.js} +2 -2
  22. package/dist/dist-BlSvQzNr.js +5 -0
  23. package/dist/{dist-C5VC_yzu.js → dist-BzEzfugY.js} +1 -1
  24. package/dist/dist-CCBlxAgS.js +8 -0
  25. package/dist/dist-CIDTVIUf.js +5 -0
  26. package/dist/{dist-CLUtPrdy.js → dist-CIYBwstr.js} +1 -1
  27. package/dist/{dist-BotSqB48.js → dist-C_Y3oV3C.js} +12 -12
  28. package/dist/{dist-BTfv03uy.js → dist-CcWX6tmx.js} +2 -2
  29. package/dist/{dist-BhM8gdSO.js → dist-CoXAujgg.js} +4 -4
  30. package/dist/{dist-4j4c7bjm.js → dist-CpxNdDkw.js} +3 -3
  31. package/dist/dist-CqQyhAM8.js +8 -0
  32. package/dist/dist-CwRu2Xzh.js +5 -0
  33. package/dist/{dist-BcuoonNH.js → dist-CxJDU6Bh.js} +9 -9
  34. package/dist/{dist-DxvORzUR.js → dist-D-W5ny5a.js} +8 -8
  35. package/dist/dist-D8CDTVgf.js +6 -0
  36. package/dist/dist-D8DNB0nO.js +8 -0
  37. package/dist/dist-DL6N_q-A.js +5 -0
  38. package/dist/{dist-BbbIBDiQ.js → dist-DMjWuVs8.js} +1 -1
  39. package/dist/dist-DOFbNV_b.js +8 -0
  40. package/dist/dist-DPrYzMY0.js +6 -0
  41. package/dist/{dist-h2c8sZvT.js → dist-DZORgqKY.js} +1 -1
  42. package/dist/{dist-B3P2fFpz.js → dist-DZo4nSS0.js} +14 -14
  43. package/dist/{dist-D4CewLk6.js → dist-Dax--nl9.js} +1 -1
  44. package/dist/{dist-DRfcqpxJ.js → dist-DgGbNavJ.js} +2 -2
  45. package/dist/{dist-C1BYNeCR.js → dist-Dk6PV_d3.js} +10 -10
  46. package/dist/{dist-fQ0ViXGs.js → dist-Dv_Y15yk.js} +107 -107
  47. package/dist/{dist-Bfwsv11D.js → dist-DyyjKEYf.js} +2 -2
  48. package/dist/{dist-p2qyWijU.js → dist-GZXUmt0b.js} +2 -2
  49. package/dist/{dist-CLJWPTX2.js → dist-LTU8Hdvn.js} +3 -3
  50. package/dist/{dist-DqAWR3CS.js → dist-M9Vag9Y0.js} +20 -20
  51. package/dist/{dist-DNdhYsgW.js → dist-U4F-tbMs.js} +79 -62
  52. package/dist/{dist-RqXTaiir.js → dist-abid3KgM.js} +11 -11
  53. package/dist/dist-cdmMjgsn.js +5 -0
  54. package/dist/dist-hT4QzYX-.js +1247 -0
  55. package/dist/{dist-luvabDEB.js → dist-t9Kf7xqC.js} +2 -2
  56. package/dist/{error-banner-5bz0L9hS.js → error-banner-CJXYJ6Sb.js} +11 -6
  57. package/dist/esm-BaH2eg5-.js +1171 -0
  58. package/dist/{esm-Duie8iU-.js → esm-ga2Bf3O2.js} +43 -43
  59. package/dist/{extends-CkydH1Q5.js → extends-D_hDsj6R.js} +4 -4
  60. package/dist/files/wasm-intro.py +11 -8
  61. package/dist/{formats-DHxc-FdY.js → formats-C4wO47tk.js} +1 -1
  62. package/dist/{glide-data-editor-CgIxTzAP.js → glide-data-editor-BPkCPs7L.js} +255 -255
  63. package/dist/{html-to-image-B93KtAVY.js → html-to-image-BwfcJJxA.js} +2412 -2282
  64. package/dist/{input-_2sjvfne.js → input-OdWHkobi.js} +263 -245
  65. package/dist/{label-LWtdw5i8.js → label-CC4ytI1X.js} +1 -1
  66. package/dist/main.js +7630 -7558
  67. package/dist/{mermaid-4DMBBIKO-DIdL224_.js → mermaid-4DMBBIKO-Eg3D4FVz.js} +1 -1
  68. package/dist/{mermaid-lXOw5Py9.js → mermaid-BotvIKg9.js} +4 -4
  69. package/dist/{process-output-BOXi9fnS.js → process-output-DsloEDna.js} +3 -3
  70. package/dist/{reveal-component-gRcjQbNL.js → reveal-component-BNU1V1x5.js} +698 -620
  71. package/dist/{spec-B96zNUEA.js → spec-X7FwLJni.js} +4 -4
  72. package/dist/{strings-Bu3vlb6W.js → strings-J57tzLr3.js} +47 -46
  73. package/dist/style.css +1 -1
  74. package/dist/{toDate-x-WRDCH7.js → toDate-d8RCRrRd.js} +2 -2
  75. package/dist/{tooltip-C5FYOpQc.js → tooltip-DpcyNkQ2.js} +2 -2
  76. package/dist/{types-CVvp1fKr.js → types-ChtMFmZ2.js} +1 -1
  77. package/dist/{useAsyncData-iRgKDT5s.js → useAsyncData-PonK__yh.js} +1 -1
  78. package/dist/{useDateFormatter-BA4FCquG.js → useDateFormatter-QB-3MpYr.js} +2 -2
  79. package/dist/{useDeepCompareMemoize-CkQ57VS2.js → useDeepCompareMemoize-D3NGWke6.js} +1 -1
  80. package/dist/{useLifecycle-BBO9PIph.js → useLifecycle-00mO3OSS.js} +2 -2
  81. package/dist/{useTheme-DHIrRQOe.js → useTheme-DEhDzATN.js} +1 -1
  82. package/dist/{vega-component-Dq-SH463.js → vega-component-DGPUhbDs.js} +8 -8
  83. package/dist/{zod-CoBiJ5v4.js → zod-aLSua2NL.js} +24 -23
  84. package/package.json +4 -3
  85. package/src/__tests__/setup.ts +16 -0
  86. package/src/components/chat/chat-display.tsx +2 -2
  87. package/src/components/data-table/TableBottomBar.tsx +1 -15
  88. package/src/components/data-table/TableTopBar.tsx +8 -13
  89. package/src/components/data-table/__tests__/TableBottomBar.test.tsx +6 -12
  90. package/src/components/data-table/__tests__/column-visibility-dropdown.test.tsx +227 -0
  91. package/src/components/data-table/column-visibility-dropdown.tsx +204 -0
  92. package/src/components/data-table/data-table.tsx +1 -1
  93. package/src/components/data-table/filter-by-values-picker.tsx +39 -17
  94. package/src/components/data-table/filter-pills.tsx +1 -1
  95. package/src/components/datasources/__tests__/filter-empty.test.ts +183 -0
  96. package/src/components/datasources/datasources.tsx +92 -3
  97. package/src/components/editor/cell/code/cell-editor.tsx +70 -32
  98. package/src/components/editor/cell/code/code-placeholder.css +18 -0
  99. package/src/components/editor/cell/code/code-placeholder.tsx +47 -0
  100. package/src/components/editor/cell/code/language-toggle.tsx +7 -1
  101. package/src/components/editor/chrome/wrapper/app-chrome.tsx +97 -52
  102. package/src/components/editor/chrome/wrapper/lazy-panels.ts +91 -0
  103. package/src/components/editor/chrome/wrapper/sidebar.tsx +2 -0
  104. package/src/components/editor/connections/storage/__tests__/__snapshots__/as-code.test.ts.snap +9 -2
  105. package/src/components/editor/connections/storage/__tests__/as-code.test.ts +62 -27
  106. package/src/components/editor/connections/storage/add-storage-form.tsx +5 -1
  107. package/src/components/editor/connections/storage/as-code.ts +20 -6
  108. package/src/components/editor/documentation.css +35 -0
  109. package/src/components/editor/file-tree/file-explorer.tsx +8 -18
  110. package/src/components/editor/file-tree/tree-actions.tsx +46 -1
  111. package/src/components/editor/notebook-cell.tsx +3 -3
  112. package/src/components/editor/renderers/slides-layout/__tests__/plugin.test.ts +20 -0
  113. package/src/components/editor/renderers/slides-layout/types.ts +1 -0
  114. package/src/components/markdown/__tests__/markdown-renderer.test.tsx +43 -0
  115. package/src/components/markdown/markdown-renderer.tsx +24 -1
  116. package/src/components/slides/__tests__/minimap-actions.test.tsx +166 -0
  117. package/src/components/slides/__tests__/reveal-component.test.ts +425 -0
  118. package/src/components/slides/minimap.tsx +127 -10
  119. package/src/components/slides/reveal-component.tsx +287 -61
  120. package/src/components/slides/slide-cell-view.tsx +26 -2
  121. package/src/components/slides/slide-form.tsx +26 -4
  122. package/src/components/storage/__tests__/storage-inspector.test.ts +180 -0
  123. package/src/components/storage/storage-inspector.tsx +535 -81
  124. package/src/components/ui/combobox.tsx +51 -32
  125. package/src/components/ui/number-field.tsx +5 -1
  126. package/src/components/ui/reorderable-list.tsx +13 -0
  127. package/src/components/ui/select-core/__tests__/use-select-list.test.ts +294 -0
  128. package/src/components/ui/select-core/__tests__/utils.test.ts +222 -0
  129. package/src/components/ui/select-core/index.ts +16 -0
  130. package/src/components/ui/select-core/option-row.tsx +33 -0
  131. package/src/components/ui/select-core/render-slot.ts +20 -0
  132. package/src/components/ui/select-core/select-list.tsx +248 -0
  133. package/src/components/ui/select-core/types.ts +44 -0
  134. package/src/components/ui/select-core/use-select-list.ts +347 -0
  135. package/src/components/ui/select-core/utils.ts +121 -0
  136. package/src/components/ui/toast.tsx +1 -0
  137. package/src/core/cells/__tests__/document-changes.test.ts +2 -2
  138. package/src/core/cells/__tests__/focus.test.ts +45 -2
  139. package/src/core/cells/focus.ts +1 -1
  140. package/src/core/cells/scrollCellIntoView.ts +4 -0
  141. package/src/core/cells/utils.ts +1 -1
  142. package/src/core/codemirror/__tests__/editor-mount-scheduler.test.ts +143 -0
  143. package/src/core/codemirror/copilot/getCodes.ts +3 -1
  144. package/src/core/codemirror/editor-mount-scheduler.ts +101 -0
  145. package/src/core/codemirror/language/languages/python.ts +2 -0
  146. package/src/core/codemirror/language/languages/sql/utils.ts +3 -1
  147. package/src/core/codemirror/lsp/__tests__/markdown-renderer.test.ts +41 -0
  148. package/src/core/codemirror/lsp/markdown-renderer.ts +59 -0
  149. package/src/core/datasets/data-source-connections.ts +2 -0
  150. package/src/core/errors/errors.ts +1 -1
  151. package/src/core/storage/__tests__/state.test.ts +49 -0
  152. package/src/core/storage/__tests__/useStorageEntries.test.tsx +199 -3
  153. package/src/core/storage/state.ts +124 -13
  154. package/src/core/storage/types.ts +6 -0
  155. package/src/core/wasm/worker/bootstrap.ts +12 -4
  156. package/src/plugins/core/__test__/registerReactComponent.test.ts +96 -0
  157. package/src/plugins/core/registerReactComponent.tsx +42 -10
  158. package/src/plugins/impl/MultiselectPlugin.tsx +19 -142
  159. package/src/plugins/impl/SearchableSelect.tsx +16 -97
  160. package/src/plugins/impl/SliderPlugin.tsx +132 -18
  161. package/src/plugins/impl/__tests__/DropdownPlugin.test.tsx +5 -2
  162. package/src/plugins/impl/__tests__/MultiSelectPlugin.test.ts +1 -1
  163. package/src/plugins/impl/__tests__/SliderPlugin.test.tsx +278 -8
  164. package/src/utils/__tests__/numbers.test.ts +20 -0
  165. package/src/utils/errors.ts +9 -0
  166. package/src/utils/lazy.ts +6 -1
  167. package/src/utils/numbers.ts +27 -0
  168. package/src/utils/schedule-task.ts +71 -0
  169. package/dist/assets/__vite-browser-external-Ci2ZQfXU.js +0 -1
  170. package/dist/assets/worker-ip3AI_sN.js +0 -73
  171. package/dist/dist-0Fif7jnk.js +0 -5
  172. package/dist/dist-B5h_9sHB.js +0 -6
  173. package/dist/dist-B9M6R5ye.js +0 -5
  174. package/dist/dist-BCt3tnck.js +0 -8
  175. package/dist/dist-BUIJwMwn.js +0 -8
  176. package/dist/dist-BpquMd3k.js +0 -5
  177. package/dist/dist-BzJsqYfz.js +0 -5
  178. package/dist/dist-CA5ELXAf.js +0 -6
  179. package/dist/dist-CLBRs6Uv.js +0 -5
  180. package/dist/dist-CStVCMbq.js +0 -5
  181. package/dist/dist-CZRIEY3Y.js +0 -8
  182. package/dist/dist-CuUHbFD0.js +0 -5
  183. package/dist/dist-DV7Iabxb.js +0 -8
  184. package/dist/dist-DhHh0jLg.js +0 -1247
  185. package/dist/dist-DuEeHMvL.js +0 -5
  186. package/dist/esm-CCuYCd3R.js +0 -1171
  187. package/src/plugins/impl/multiselectFilterFn.tsx +0 -22
  188. /package/src/components/{data-table → ui}/value-chips.tsx +0 -0
@@ -0,0 +1,143 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+ import { describe, expect, it } from "vitest";
3
+ import { createEditorMountScheduler } from "../editor-mount-scheduler";
4
+
5
+ function makeManualSchedule() {
6
+ let pending: (() => void) | null = null;
7
+ const schedule = (cb: () => void) => {
8
+ pending = cb;
9
+ };
10
+ const flush = () => {
11
+ const cb = pending;
12
+ pending = null;
13
+ cb?.();
14
+ };
15
+ return { schedule, flush, hasPending: () => pending !== null };
16
+ }
17
+
18
+ describe("createEditorMountScheduler", () => {
19
+ it("builds one queued editor per tick in FIFO order, rescheduling between", () => {
20
+ const { schedule, flush, hasPending } = makeManualSchedule();
21
+ const built: string[] = [];
22
+ const s = createEditorMountScheduler(schedule);
23
+ s.request("a", () => built.push("a"));
24
+ s.request("b", () => built.push("b"));
25
+ flush();
26
+ expect(built).toEqual(["a"]);
27
+ expect(hasPending()).toBe(true);
28
+ flush();
29
+ expect(built).toEqual(["a", "b"]);
30
+ });
31
+
32
+ it("stops rescheduling once the queue is empty", () => {
33
+ const { schedule, flush, hasPending } = makeManualSchedule();
34
+ const built: string[] = [];
35
+ const s = createEditorMountScheduler(schedule);
36
+ s.request("a", () => built.push("a"));
37
+ flush();
38
+ expect(built).toEqual(["a"]);
39
+ expect(hasPending()).toBe(false);
40
+ });
41
+
42
+ it("promote builds immediately and removes from the queue", () => {
43
+ const { schedule, flush } = makeManualSchedule();
44
+ const built: string[] = [];
45
+ const s = createEditorMountScheduler(schedule);
46
+ s.request("a", () => built.push("a"));
47
+ s.request("b", () => built.push("b"));
48
+ s.promote("b");
49
+ expect(built).toEqual(["b"]);
50
+ flush();
51
+ expect(built).toEqual(["b", "a"]);
52
+ });
53
+
54
+ it("cancel prevents a queued build", () => {
55
+ const { schedule, flush } = makeManualSchedule();
56
+ const built: string[] = [];
57
+ const s = createEditorMountScheduler(schedule);
58
+ s.request("a", () => built.push("a"));
59
+ s.cancel("a");
60
+ flush();
61
+ expect(built).toEqual([]);
62
+ });
63
+
64
+ it("builds prioritized cells before earlier-queued ones", () => {
65
+ const { schedule, flush } = makeManualSchedule();
66
+ const built: string[] = [];
67
+ const s = createEditorMountScheduler(schedule);
68
+ s.request("a", () => built.push("a"));
69
+ s.request("b", () => built.push("b"));
70
+ s.request("c", () => built.push("c"));
71
+ s.prioritize("c");
72
+ flush();
73
+ flush();
74
+ flush();
75
+ expect(built).toEqual(["c", "a", "b"]);
76
+ });
77
+
78
+ it("builds multiple prioritized cells in prioritization order, then FIFO", () => {
79
+ const { schedule, flush } = makeManualSchedule();
80
+ const built: string[] = [];
81
+ const s = createEditorMountScheduler(schedule);
82
+ s.request("a", () => built.push("a"));
83
+ s.request("b", () => built.push("b"));
84
+ s.request("c", () => built.push("c"));
85
+ s.request("d", () => built.push("d"));
86
+ s.prioritize("d");
87
+ s.prioritize("b");
88
+ flush();
89
+ flush();
90
+ flush();
91
+ flush();
92
+ expect(built).toEqual(["d", "b", "a", "c"]);
93
+ });
94
+
95
+ it("ignores prioritize for a cell that is not queued", () => {
96
+ const { schedule, flush } = makeManualSchedule();
97
+ const built: string[] = [];
98
+ const s = createEditorMountScheduler(schedule);
99
+ s.request("a", () => built.push("a"));
100
+ s.prioritize("not-queued");
101
+ flush();
102
+ expect(built).toEqual(["a"]);
103
+ });
104
+
105
+ it("deprioritize reverts a cell to FIFO order without dropping it", () => {
106
+ const { schedule, flush } = makeManualSchedule();
107
+ const built: string[] = [];
108
+ const s = createEditorMountScheduler(schedule);
109
+ s.request("a", () => built.push("a"));
110
+ s.request("b", () => built.push("b"));
111
+ s.prioritize("b");
112
+ s.deprioritize("b");
113
+ flush();
114
+ flush();
115
+ expect(built).toEqual(["a", "b"]);
116
+ });
117
+
118
+ it("keeps draining the queue after a build throws", () => {
119
+ const { schedule, flush } = makeManualSchedule();
120
+ const built: string[] = [];
121
+ const s = createEditorMountScheduler(schedule);
122
+ s.request("a", () => {
123
+ throw new Error("boom");
124
+ });
125
+ s.request("b", () => built.push("b"));
126
+ expect(() => flush()).toThrow("boom");
127
+ flush();
128
+ expect(built).toEqual(["b"]);
129
+ });
130
+
131
+ it("cancel removes a prioritized cell", () => {
132
+ const { schedule, flush } = makeManualSchedule();
133
+ const built: string[] = [];
134
+ const s = createEditorMountScheduler(schedule);
135
+ s.request("a", () => built.push("a"));
136
+ s.request("b", () => built.push("b"));
137
+ s.prioritize("b");
138
+ s.cancel("b");
139
+ flush();
140
+ flush();
141
+ expect(built).toEqual(["a"]);
142
+ });
143
+ });
@@ -47,7 +47,9 @@ const notebookCellCodes = atom((get) => {
47
47
  const handle = notebook.cellHandles[id];
48
48
  return [
49
49
  id,
50
- handle?.current ? getEditorCodeAsPython(handle.current.editorView) : "",
50
+ handle?.current?.editorView
51
+ ? getEditorCodeAsPython(handle.current.editorView)
52
+ : "",
51
53
  ];
52
54
  }),
53
55
  );
@@ -0,0 +1,101 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+ import {
3
+ scheduleTask as defaultScheduleTask,
4
+ type ScheduleTaskFn,
5
+ } from "@/utils/schedule-task";
6
+
7
+ type BuildFn = () => void;
8
+
9
+ export interface EditorMountScheduler {
10
+ /** Queue a cell's editor build to run on a future task. */
11
+ request(cellId: string, build: BuildFn): void;
12
+ /** Build a queued cell's editor synchronously, right now (e.g. on focus). */
13
+ promote(cellId: string): void;
14
+ /**
15
+ * Mark a queued cell to build ahead of un-prioritized ones, keeping the
16
+ * one-build-per-task pacing (e.g. when the cell scrolls into view).
17
+ */
18
+ prioritize(cellId: string): void;
19
+ /** Drop a cell's prioritized mark, reverting it to plain FIFO order. */
20
+ deprioritize(cellId: string): void;
21
+ /** Remove a cell's queued build entirely. */
22
+ cancel(cellId: string): void;
23
+ }
24
+
25
+ /**
26
+ * Drains a FIFO queue of editor builds, one per macrotask, so constructing many
27
+ * CodeMirror instances never blocks the main thread in a single long task. The
28
+ * scheduler yields between every build to keep the page responsive. Cells can
29
+ * be prioritized (e.g. when they scroll into view) to jump ahead of the queue
30
+ * without breaking the one-build-per-macrotask pacing.
31
+ */
32
+ export function createEditorMountScheduler(
33
+ scheduleTask: ScheduleTaskFn = defaultScheduleTask,
34
+ ): EditorMountScheduler {
35
+ const queue = new Map<string, BuildFn>();
36
+ const priority = new Set<string>();
37
+ let isProcessingScheduled = false;
38
+
39
+ function scheduleProcessing(): void {
40
+ if (isProcessingScheduled || queue.size === 0) {
41
+ return;
42
+ }
43
+ isProcessingScheduled = true;
44
+ scheduleTask(processNext);
45
+ }
46
+
47
+ // Prioritized cells first, in the order they were prioritized; then FIFO.
48
+ function nextCellId(): string | undefined {
49
+ for (const cellId of priority) {
50
+ if (queue.has(cellId)) {
51
+ return cellId;
52
+ }
53
+ priority.delete(cellId);
54
+ }
55
+ return queue.keys().next().value;
56
+ }
57
+
58
+ function processNext(): void {
59
+ isProcessingScheduled = false;
60
+ try {
61
+ const cellId = nextCellId();
62
+ if (cellId !== undefined) {
63
+ const build = queue.get(cellId);
64
+ queue.delete(cellId);
65
+ priority.delete(cellId);
66
+ build?.();
67
+ }
68
+ } finally {
69
+ scheduleProcessing();
70
+ }
71
+ }
72
+
73
+ return {
74
+ request(cellId, build) {
75
+ queue.set(cellId, build);
76
+ scheduleProcessing();
77
+ },
78
+ promote(cellId) {
79
+ const build = queue.get(cellId);
80
+ if (build) {
81
+ queue.delete(cellId);
82
+ priority.delete(cellId);
83
+ build();
84
+ }
85
+ },
86
+ prioritize(cellId) {
87
+ if (queue.has(cellId)) {
88
+ priority.add(cellId);
89
+ }
90
+ },
91
+ deprioritize(cellId) {
92
+ priority.delete(cellId);
93
+ },
94
+ cancel(cellId) {
95
+ queue.delete(cellId);
96
+ priority.delete(cellId);
97
+ },
98
+ };
99
+ }
100
+
101
+ export const editorMountScheduler = createEditorMountScheduler();
@@ -33,6 +33,7 @@ import { cellActionsState } from "../../cells/state";
33
33
  import { pythonCompletionSource } from "../../completion/completer";
34
34
  import type { PlaceholderType } from "../../config/types";
35
35
  import { FederatedLanguageServerClient } from "../../lsp/federated-lsp";
36
+ import { createLspMarkdownRenderer } from "../../lsp/markdown-renderer";
36
37
  import { NotebookLanguageServerClient } from "../../lsp/notebook-lsp";
37
38
  import { createTransport } from "../../lsp/transports";
38
39
  import { CellDocumentUri, type ILanguageServerClient } from "../../lsp/types";
@@ -342,6 +343,7 @@ export class PythonLanguageAdapter implements LanguageAdapter<{}> {
342
343
  client: client as unknown as LanguageServerClient,
343
344
  languageId: "python",
344
345
  allowHTMLContent: true,
346
+ markdownRenderer: createLspMarkdownRenderer(),
345
347
  useSnippetOnCompletion: true,
346
348
  hoverConfig: hoverOptions,
347
349
  completionConfig: autocompleteOptions,
@@ -14,6 +14,7 @@ import {
14
14
  } from "@codemirror/lang-sql";
15
15
  import {
16
16
  BigQueryDialect,
17
+ DremioDialect,
17
18
  DuckDBDialect,
18
19
  } from "@marimo-team/codemirror-sql/dialects";
19
20
  import type { DataSourceConnection } from "@/core/kernel/messages";
@@ -97,6 +98,8 @@ export function guessDialect(
97
98
  return PLSQL;
98
99
  case "bigquery":
99
100
  return BigQueryDialect;
101
+ case "dremio":
102
+ return DremioDialect;
100
103
  case "timescaledb":
101
104
  return PostgreSQL; // TimescaleDB is a PostgreSQL dialect
102
105
  case "awsathena":
@@ -116,7 +119,6 @@ export function guessDialect(
116
119
  case "spark":
117
120
  case "databricks":
118
121
  case "datafusion":
119
- case "dremio":
120
122
  Logger.debug("Unsupported dialect", { dialect });
121
123
  return ModifiedStandardSQL;
122
124
  default:
@@ -0,0 +1,41 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+ import { describe, expect, it } from "vitest";
3
+ import { createLspMarkdownRenderer } from "../markdown-renderer";
4
+
5
+ describe("createLspMarkdownRenderer", () => {
6
+ it("syntax-highlights python code blocks with tok-* span classes", () => {
7
+ const render = createLspMarkdownRenderer();
8
+ const result = render("```python\ndef foo():\n pass\n```");
9
+ expect(result).toContain('class="language-python"');
10
+ expect(result).toContain("tok-keyword");
11
+ expect(result).toContain("<pre><code");
12
+ });
13
+
14
+ it("does not highlight non-python code blocks", () => {
15
+ const render = createLspMarkdownRenderer();
16
+ const result = render("```bash\necho hello\n```");
17
+ expect(result).not.toContain("tok-keyword");
18
+ expect(result).toContain("<code");
19
+ });
20
+
21
+ it("renders empty python code blocks as empty string", () => {
22
+ const render = createLspMarkdownRenderer();
23
+ const result = render("```python\n \n```");
24
+ expect(result).toBe("");
25
+ });
26
+
27
+ it("renders markdown prose unchanged", () => {
28
+ const render = createLspMarkdownRenderer();
29
+ const result = render("## Examples\n\nSome text.");
30
+ expect(result).toContain("<h2");
31
+ expect(result).toContain("Examples");
32
+ expect(result).toContain("Some text.");
33
+ });
34
+
35
+ it("escapes HTML in highlighted code", () => {
36
+ const render = createLspMarkdownRenderer();
37
+ const result = render('```python\nx = "<div>"\n```');
38
+ expect(result).not.toContain("<div>");
39
+ expect(result).toContain("&lt;div&gt;");
40
+ });
41
+ });
@@ -0,0 +1,59 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+ import { classHighlighter, highlightCode } from "@lezer/highlight";
3
+ import { parser as pythonParser } from "@lezer/python";
4
+ import { marked } from "marked";
5
+
6
+ /**
7
+ * Syntax-highlight a Python code string using the lezer Python parser.
8
+ * Returns an HTML string with tok-* span classes for styling.
9
+ */
10
+ function highlightPython(code: string): string {
11
+ const tree = pythonParser.parse(code);
12
+ let html = "";
13
+ highlightCode(
14
+ code,
15
+ tree,
16
+ classHighlighter,
17
+ (text, classes) => {
18
+ const escaped = text
19
+ .replace(/&/g, "&amp;")
20
+ .replace(/</g, "&lt;")
21
+ .replace(/>/g, "&gt;");
22
+ html += classes ? `<span class="${classes}">${escaped}</span>` : escaped;
23
+ },
24
+ () => {
25
+ html += "\n";
26
+ },
27
+ );
28
+ return html;
29
+ }
30
+
31
+ /**
32
+ * A markdown renderer for LSP hover tooltips that adds syntax highlighting
33
+ * to Python code blocks using the lezer Python parser and classHighlighter.
34
+ * The tok-* CSS classes are defined in documentation.css.
35
+ */
36
+ export function createLspMarkdownRenderer(): (markdown: string) => string {
37
+ const renderer = new marked.Renderer();
38
+ const prevCode = renderer.code.bind(renderer);
39
+
40
+ renderer.code = (token) => {
41
+ const { text, lang } = token;
42
+ if (!text.trim()) {
43
+ return "";
44
+ }
45
+ if (lang === "python" || lang === "py") {
46
+ const highlighted = highlightPython(text);
47
+ return `<pre><code class="language-python">${highlighted}</code></pre>\n`;
48
+ }
49
+ return prevCode(token);
50
+ };
51
+
52
+ return (markdown: string): string =>
53
+ marked(markdown, {
54
+ async: false,
55
+ gfm: true,
56
+ breaks: true,
57
+ renderer,
58
+ }) as string;
59
+ }
@@ -169,6 +169,7 @@ const {
169
169
  return {
170
170
  ...db,
171
171
  schemas: schemas,
172
+ schemas_resolved: true,
172
173
  };
173
174
  }),
174
175
  };
@@ -213,6 +214,7 @@ const {
213
214
  return {
214
215
  ...schema,
215
216
  tables: tables,
217
+ tables_resolved: true,
216
218
  };
217
219
  }),
218
220
  };
@@ -18,7 +18,7 @@ export interface AutoFix {
18
18
  fixType: "manual" | "ai";
19
19
  onFix: (ctx: {
20
20
  addCodeBelow: (code: string) => void;
21
- editor: EditorView | undefined;
21
+ editor: EditorView | null | undefined;
22
22
  cellId: CellId;
23
23
  aiFix?: AIFix;
24
24
  }) => Promise<void>;
@@ -48,6 +48,7 @@ describe("storage state", () => {
48
48
  expect(state).toEqual({
49
49
  namespaces: [],
50
50
  entriesByPath: new Map(),
51
+ pageMetadataByPath: new Map(),
51
52
  });
52
53
  });
53
54
  });
@@ -134,6 +135,49 @@ describe("storage state", () => {
134
135
  expect(state.entriesByPath.get("my_s3::data/")).toEqual(entries);
135
136
  });
136
137
 
138
+ it("should store next page tokens keyed by namespace and prefix", () => {
139
+ const entries = [makeEntry({ path: "a.txt" })];
140
+
141
+ actions.setEntries({
142
+ namespace: "my_s3",
143
+ prefix: "data/",
144
+ entries,
145
+ nextPageToken: "150",
146
+ });
147
+
148
+ expect(state.entriesByPath.get("my_s3::data/")).toEqual(entries);
149
+ expect(state.pageMetadataByPath.get("my_s3::data/")?.nextPageToken).toBe(
150
+ "150",
151
+ );
152
+ });
153
+
154
+ it("should append entries when requested", () => {
155
+ const firstPage = [makeEntry({ path: "a.txt" })];
156
+ const secondPage = [makeEntry({ path: "b.txt" })];
157
+
158
+ actions.setEntries({
159
+ namespace: "my_s3",
160
+ prefix: "data/",
161
+ entries: firstPage,
162
+ nextPageToken: "150",
163
+ });
164
+ actions.setEntries({
165
+ namespace: "my_s3",
166
+ prefix: "data/",
167
+ entries: secondPage,
168
+ nextPageToken: null,
169
+ append: true,
170
+ });
171
+
172
+ expect(state.entriesByPath.get("my_s3::data/")).toEqual([
173
+ ...firstPage,
174
+ ...secondPage,
175
+ ]);
176
+ expect(
177
+ state.pageMetadataByPath.get("my_s3::data/")?.nextPageToken,
178
+ ).toBeNull();
179
+ });
180
+
137
181
  it("should use empty string for null prefix", () => {
138
182
  const entries = [makeEntry({ path: "root.txt" })];
139
183
 
@@ -251,6 +295,11 @@ describe("storage state", () => {
251
295
  expect(state.entriesByPath.get("my_s3::")).toBeUndefined();
252
296
  expect(state.entriesByPath.get("my_s3::data/")).toBeUndefined();
253
297
  expect(state.entriesByPath.get("my_s3::data/nested/")).toBeUndefined();
298
+ expect(state.pageMetadataByPath.get("my_s3::")).toBeUndefined();
299
+ expect(state.pageMetadataByPath.get("my_s3::data/")).toBeUndefined();
300
+ expect(
301
+ state.pageMetadataByPath.get("my_s3::data/nested/"),
302
+ ).toBeUndefined();
254
303
  });
255
304
 
256
305
  it("should not affect entries from other namespaces", () => {