@marimo-team/islands 0.19.7-dev43 → 0.19.7-dev46

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.
package/dist/main.js CHANGED
@@ -17762,7 +17762,7 @@ ${JSON.stringify(e2, null, 4)}`);
17762
17762
  label: f,
17763
17763
  detail: (d == null ? void 0 : d.detail) || c,
17764
17764
  apply(e2, c2, d2, _2) {
17765
- if (v && isLSPTextEdit(v) ? e2.dispatch(insertCompletionText(e2.state, v.newText, posToOffsetOrZero(e2.state.doc, v.range.start), posToOffsetOrZero(e2.state.doc, v.range.end))) : y && E === InsertTextFormat.Snippet && r.useSnippetOnCompletion ? snippet(convertSnippet(y))(e2, null, d2, _2) : e2.dispatch(insertCompletionText(e2.state, f, d2, _2)), !w) return;
17765
+ if (v && isLSPTextEdit(v) ? e2.dispatch(insertCompletionText(e2.state, v.newText, posToOffsetOrZero(e2.state.doc, v.range.start), posToOffsetOrZero(e2.state.doc, v.range.end))) : y && E === InsertTextFormat.Snippet && r.useSnippetOnCompletion ? snippet(convertSnippet(y))(e2, null, d2, _2) : e2.dispatch(insertCompletionText(e2.state, y || f, d2, _2)), !w) return;
17766
17766
  let S2 = w.sort(({ range: { end: r2 } }, { range: { end: c3 } }) => posToOffsetOrZero(e2.state.doc, r2) < posToOffsetOrZero(e2.state.doc, c3) ? 1 : posToOffsetOrZero(e2.state.doc, r2) > posToOffsetOrZero(e2.state.doc, c3) ? -1 : 0);
17767
17767
  for (let r2 of S2) e2.dispatch(e2.state.update({
17768
17768
  changes: {
@@ -18703,7 +18703,7 @@ ${JSON.stringify(e2, null, 4)}`);
18703
18703
  }
18704
18704
  function singleFacet() {
18705
18705
  return Facet$1.define({
18706
- combine: (e) => e[0]
18706
+ combine: (e) => e.find((e2) => e2 !== void 0)
18707
18707
  });
18708
18708
  }
18709
18709
  singleFacet(), singleFacet(), singleFacet();
@@ -32350,7 +32350,6 @@ ${c.sqlString}
32350
32350
  var Snapshotter = class {
32351
32351
  constructor(e) {
32352
32352
  __publicField(this, "documentVersion", 0);
32353
- __publicField(this, "versionToCellNumberAndVersion", new LRUCache(20));
32354
32353
  __publicField(this, "lastSnapshot", null);
32355
32354
  this.getNotebookCode = e;
32356
32355
  }
@@ -32367,11 +32366,6 @@ ${c.sqlString}
32367
32366
  version: this.documentVersion
32368
32367
  };
32369
32368
  }
32370
- getSnapshot(e) {
32371
- let r = this.versionToCellNumberAndVersion.get(e);
32372
- if (!r) throw Error(`No snapshot for version ${e}`);
32373
- return r;
32374
- }
32375
32369
  getLatestSnapshot() {
32376
32370
  if (!this.lastSnapshot) throw Error("No snapshots");
32377
32371
  return {
@@ -32611,13 +32605,26 @@ ${c.sqlString}
32611
32605
  }), d.contents === "" ? null : (d.range && (d.range = r.reverseRange(d.range, c)), d)) : (Logger.debug("[lsp] no hover result"), d);
32612
32606
  }
32613
32607
  async textDocumentCompletion(e) {
32614
- let { lens: r } = this.snapshotter.getLatestSnapshot(), c = CellDocumentUri.parse(e.textDocument.uri);
32608
+ let { lens: r } = this.snapshotter.getLatestSnapshot();
32609
+ if (!CellDocumentUri.is(e.textDocument.uri)) return Logger.error("[lsp] Invalid cell document URI in completion request", e.textDocument.uri), null;
32610
+ let c = CellDocumentUri.parse(e.textDocument.uri);
32611
+ if (!c || c === "undefined") return Logger.error("[lsp] Invalid cellId 'undefined' in completion request", {
32612
+ cellId: c,
32613
+ uri: e.textDocument.uri,
32614
+ availableCellIds: r.cellIds
32615
+ }), null;
32616
+ r.cellIds.includes(c) || Logger.warn("[lsp] CellId in completion request not found in current lens", {
32617
+ cellId: c,
32618
+ uri: e.textDocument.uri,
32619
+ availableCellIds: r.cellIds
32620
+ });
32621
+ let d = r.transformPosition(e.position, c);
32615
32622
  return this.client.textDocumentCompletion({
32616
32623
  ...e,
32617
32624
  textDocument: {
32618
32625
  uri: this.documentUri
32619
32626
  },
32620
- position: r.transformPosition(e.position, c)
32627
+ position: d
32621
32628
  });
32622
32629
  }
32623
32630
  patchProcessNotification() {
@@ -32944,7 +32951,7 @@ ${c.sqlString}
32944
32951
  client: r2,
32945
32952
  languageId: "python",
32946
32953
  allowHTMLContent: true,
32947
- useSnippetOnCompletion: false,
32954
+ useSnippetOnCompletion: true,
32948
32955
  hoverConfig: _,
32949
32956
  completionConfig: d2,
32950
32957
  diagnosticsEnabled: ((_d2 = f.diagnostics) == null ? void 0 : _d2.enabled) ?? false,
@@ -73168,7 +73175,7 @@ Image URL: ${r.imageUrl}`)), contextToXml({
73168
73175
  return Logger.warn("Failed to get version from mount config"), null;
73169
73176
  }
73170
73177
  }
73171
- const marimoVersionAtom = atom(getVersionFromMountConfig() || "0.19.7-dev43"), showCodeInRunModeAtom = atom(true);
73178
+ const marimoVersionAtom = atom(getVersionFromMountConfig() || "0.19.7-dev46"), showCodeInRunModeAtom = atom(true);
73172
73179
  atom(null);
73173
73180
  var import_compiler_runtime$88 = require_compiler_runtime();
73174
73181
  function useKeydownOnElement(e, r) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marimo-team/islands",
3
- "version": "0.19.7-dev43",
3
+ "version": "0.19.7-dev46",
4
4
  "main": "dist/main.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "type": "module",
@@ -55,7 +55,7 @@
55
55
  "@lezer/markdown": "^1.6.2",
56
56
  "@lezer/python": "^1.1.18",
57
57
  "@marimo-team/codemirror-ai": "^0.3.5",
58
- "@marimo-team/codemirror-languageserver": "^1.16.10",
58
+ "@marimo-team/codemirror-languageserver": "^1.16.11",
59
59
  "@marimo-team/codemirror-mcp": "^0.1.5",
60
60
  "@marimo-team/codemirror-sql": "^0.2.4",
61
61
  "@marimo-team/llm-info": "workspace:*",
@@ -44,6 +44,7 @@ function createMockEditorView(code: string): EditorView {
44
44
  lspConfig: {},
45
45
  }),
46
46
  cellConfigExtension({
47
+ cellId: "cell1" as CellId,
47
48
  completionConfig: {
48
49
  copilot: false,
49
50
  activate_on_typing: true,
@@ -115,6 +115,7 @@ export const setupCodeMirror = (opts: CodeMirrorSetupOpts): Extension[] => {
115
115
  jupyterHelpExtension(),
116
116
  // Cell editing
117
117
  cellConfigExtension({
118
+ cellId,
118
119
  completionConfig,
119
120
  hotkeys,
120
121
  placeholderType,
@@ -41,12 +41,14 @@ export const lspConfigState = singleFacet<
41
41
  * Extension for cell config
42
42
  */
43
43
  export function cellConfigExtension({
44
+ cellId,
44
45
  completionConfig,
45
46
  hotkeys,
46
47
  placeholderType,
47
48
  lspConfig,
48
49
  diagnosticsConfig,
49
50
  }: {
51
+ cellId: CellId;
50
52
  completionConfig: CompletionConfig;
51
53
  hotkeys: HotkeyProvider;
52
54
  placeholderType: PlaceholderType;
@@ -55,6 +57,7 @@ export function cellConfigExtension({
55
57
  }) {
56
58
  return [
57
59
  // Store state
60
+ cellIdState.of(cellId),
58
61
  completionConfigState.of(completionConfig),
59
62
  hotkeysProviderState.of(hotkeys),
60
63
  placeholderState.of(placeholderType),
@@ -45,6 +45,7 @@ function createMockEditorView(code: string) {
45
45
  lspConfig: {},
46
46
  }),
47
47
  cellConfigExtension({
48
+ cellId: "cell1" as CellId,
48
49
  completionConfig: {
49
50
  copilot: false,
50
51
  activate_on_typing: true,
@@ -14,6 +14,9 @@ import { Facet } from "@codemirror/state";
14
14
  */
15
15
  export function singleFacet<T>() {
16
16
  return Facet.define<T, T>({
17
- combine: (values) => values[0],
17
+ combine: (values) => {
18
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
19
+ return values.find((v) => v !== undefined)!;
20
+ },
18
21
  });
19
22
  }
@@ -49,6 +49,7 @@ function createState(content: string, selection?: { anchor: number }) {
49
49
  placeholderType: "marimo-import",
50
50
  }),
51
51
  cellConfigExtension({
52
+ cellId: "cell1" as CellId,
52
53
  completionConfig: {
53
54
  copilot: false,
54
55
  activate_on_typing: true,
@@ -31,6 +31,7 @@ function createEditor(doc: string) {
31
31
  lspConfig: {},
32
32
  }),
33
33
  cellConfigExtension({
34
+ cellId: "cell1" as CellId,
34
35
  completionConfig: {
35
36
  activate_on_typing: true,
36
37
  signature_hint_on_typing: false,
@@ -15,6 +15,7 @@ import type {
15
15
  LSPConfig,
16
16
  } from "@/core/config/config-schema";
17
17
  import type { HotkeyProvider } from "@/core/hotkeys/hotkeys";
18
+ import { Logger } from "@/utils/Logger";
18
19
  import { clamp } from "@/utils/math";
19
20
  import {
20
21
  cellIdState,
@@ -310,6 +311,17 @@ export function reconfigureLanguageEffect(
310
311
  const language = view.state.field(languageAdapterState);
311
312
  const placeholderType = view.state.facet(placeholderState);
312
313
  const cellId = view.state.facet(cellIdState);
314
+
315
+ if (cellId === undefined) {
316
+ Logger.error("Cell ID is undefined in reconfigureLanguageEffect");
317
+ }
318
+ if (placeholderType === undefined) {
319
+ Logger.error("Placeholder type is undefined in reconfigureLanguageEffect");
320
+ }
321
+ if (completionConfig === undefined) {
322
+ Logger.error("Completion config is undefined in reconfigureLanguageEffect");
323
+ }
324
+
313
325
  return languageCompartment.reconfigure(
314
326
  language.getExtension(
315
327
  cellId,
@@ -293,7 +293,7 @@ export class PythonLanguageAdapter implements LanguageAdapter<{}> {
293
293
  client: client as unknown as LanguageServerClient,
294
294
  languageId: "python",
295
295
  allowHTMLContent: true,
296
- useSnippetOnCompletion: false,
296
+ useSnippetOnCompletion: true,
297
297
  hoverConfig: hoverOptions,
298
298
  completionConfig: autocompleteOptions,
299
299
  // Default to false
@@ -47,13 +47,6 @@ class Snapshotter {
47
47
  this.getNotebookCode = getNotebookCode;
48
48
  }
49
49
 
50
- /**
51
- * Map from the global document version to the cell id and version.
52
- */
53
- private versionToCellNumberAndVersion = new LRUCache<number, NotebookLens>(
54
- 20,
55
- );
56
-
57
50
  private lastSnapshot: NotebookLens | null = null;
58
51
 
59
52
  public snapshot() {
@@ -78,14 +71,6 @@ class Snapshotter {
78
71
  };
79
72
  }
80
73
 
81
- public getSnapshot(version: number) {
82
- const snapshot = this.versionToCellNumberAndVersion.get(version);
83
- if (!snapshot) {
84
- throw new Error(`No snapshot for version ${version}`);
85
- }
86
- return snapshot;
87
- }
88
-
89
74
  public getLatestSnapshot() {
90
75
  if (!this.lastSnapshot) {
91
76
  throw new Error("No snapshots");
@@ -657,14 +642,49 @@ export class NotebookLanguageServerClient implements ILanguageServerClient {
657
642
 
658
643
  public async textDocumentCompletion(params: LSP.CompletionParams) {
659
644
  const { lens } = this.snapshotter.getLatestSnapshot();
645
+
646
+ // Check if URI is valid
647
+ if (!CellDocumentUri.is(params.textDocument.uri)) {
648
+ Logger.error(
649
+ "[lsp] Invalid cell document URI in completion request",
650
+ params.textDocument.uri,
651
+ );
652
+ return null;
653
+ }
654
+
660
655
  const cellId = CellDocumentUri.parse(params.textDocument.uri);
661
656
 
657
+ // Check if cellId is valid (not undefined string)
658
+ if (!cellId || cellId === "undefined") {
659
+ Logger.error("[lsp] Invalid cellId 'undefined' in completion request", {
660
+ cellId,
661
+ uri: params.textDocument.uri,
662
+ availableCellIds: lens.cellIds,
663
+ });
664
+ // Return null to fail gracefully instead of sending wrong position
665
+ return null;
666
+ }
667
+
668
+ // Warn if cellId not found in lens (might be okay if cell was just added)
669
+ if (!lens.cellIds.includes(cellId)) {
670
+ Logger.warn(
671
+ "[lsp] CellId in completion request not found in current lens",
672
+ {
673
+ cellId,
674
+ uri: params.textDocument.uri,
675
+ availableCellIds: lens.cellIds,
676
+ },
677
+ );
678
+ }
679
+
680
+ const transformedPosition = lens.transformPosition(params.position, cellId);
681
+
662
682
  return this.client.textDocumentCompletion({
663
683
  ...params,
664
684
  textDocument: {
665
685
  uri: this.documentUri,
666
686
  },
667
- position: lens.transformPosition(params.position, cellId),
687
+ position: transformedPosition,
668
688
  });
669
689
  }
670
690
 
@@ -1,13 +1,8 @@
1
1
  /* Copyright 2026 Marimo. All rights reserved. */
2
2
  import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
3
- import { captureIframeAsImage } from "../iframe";
3
+ import { captureExternalIframes } from "../iframe";
4
4
 
5
- // Mock html-to-image
6
- vi.mock("html-to-image", () => ({
7
- toPng: vi.fn().mockResolvedValue("data:image/png;base64,mock"),
8
- }));
9
-
10
- describe("captureIframeAsImage", () => {
5
+ describe("captureExternalIframes", () => {
11
6
  const originalCreateElement = document.createElement.bind(document);
12
7
 
13
8
  beforeEach(() => {
@@ -53,7 +48,7 @@ describe("captureIframeAsImage", () => {
53
48
  const element = document.createElement("div");
54
49
  element.innerHTML = "<p>No iframe here</p>";
55
50
 
56
- const result = await captureIframeAsImage(element);
51
+ const result = await captureExternalIframes(element);
57
52
  expect(result).toBeNull();
58
53
  });
59
54
 
@@ -61,7 +56,7 @@ describe("captureIframeAsImage", () => {
61
56
  const element = document.createElement("div");
62
57
  element.innerHTML = '<iframe src="https://external.com/page"></iframe>';
63
58
 
64
- const result = await captureIframeAsImage(element);
59
+ const result = await captureExternalIframes(element);
65
60
 
66
61
  expect(result).not.toBeNull();
67
62
  expect(result).toMatch(/^data:image\/png;base64,/);
@@ -72,7 +67,7 @@ describe("captureIframeAsImage", () => {
72
67
  element.innerHTML =
73
68
  '<iframe src="https://www.openstreetmap.org/export/embed.html"></iframe>';
74
69
 
75
- const result = await captureIframeAsImage(element);
70
+ const result = await captureExternalIframes(element);
76
71
 
77
72
  expect(result).not.toBeNull();
78
73
  expect(result).toMatch(/^data:image\/png;base64,/);
@@ -85,7 +80,7 @@ describe("captureIframeAsImage", () => {
85
80
  element.append(iframe);
86
81
 
87
82
  // The iframe has no body accessible in jsdom
88
- const result = await captureIframeAsImage(element);
83
+ const result = await captureExternalIframes(element);
89
84
 
90
85
  // In jsdom, contentDocument may not be accessible
91
86
  expect(result).toBeNull();
@@ -96,7 +91,7 @@ describe("captureIframeAsImage", () => {
96
91
  element.innerHTML = '<iframe src="./@file/123.html"></iframe>';
97
92
 
98
93
  // Same-origin relative URL, but contentDocument not accessible in jsdom
99
- const result = await captureIframeAsImage(element);
94
+ const result = await captureExternalIframes(element);
100
95
 
101
96
  // Returns null because jsdom can't access contentDocument for file URLs
102
97
  expect(result).toBeNull();
@@ -113,7 +108,7 @@ describe("captureIframeAsImage", () => {
113
108
  const element = document.createElement("div");
114
109
  element.innerHTML = `<iframe src="${url}"></iframe>`;
115
110
 
116
- const result = await captureIframeAsImage(element);
111
+ const result = await captureExternalIframes(element);
117
112
 
118
113
  expect(result).not.toBeNull();
119
114
  expect(result).toMatch(/^data:image\/png;base64,/);
@@ -128,7 +123,7 @@ describe("captureIframeAsImage", () => {
128
123
  const element = document.createElement("div");
129
124
  element.innerHTML = `<iframe src="${url}"></iframe>`;
130
125
 
131
- const result = await captureIframeAsImage(element);
126
+ const result = await captureExternalIframes(element);
132
127
 
133
128
  // In jsdom, these return null because contentDocument isn't accessible
134
129
  // but they should NOT return a placeholder (which would indicate external detection)
@@ -8,7 +8,7 @@ import { Filenames } from "@/utils/filenames";
8
8
  import { Paths } from "@/utils/paths";
9
9
  import { prettyError } from "./errors";
10
10
  import { toPng } from "./html-to-image";
11
- import { captureIframeAsImage } from "./iframe";
11
+ import { captureExternalIframes } from "./iframe";
12
12
  import { Logger } from "./Logger";
13
13
  import { ProgressState } from "./progress";
14
14
  import { ToastProgress } from "./toast-progress";
@@ -66,9 +66,11 @@ export async function getImageDataUrlForCell(
66
66
  return;
67
67
  }
68
68
 
69
- const iframeDataUrl = await captureIframeAsImage(element);
70
- if (iframeDataUrl) {
71
- return iframeDataUrl;
69
+ // TODO: This doesn't handle external iframes + normal elements together (eg. in vstack).
70
+ // It will return the iframe only
71
+ const externalIframeDataUrl = await captureExternalIframes(element);
72
+ if (externalIframeDataUrl) {
73
+ return externalIframeDataUrl;
72
74
  }
73
75
 
74
76
  const startTime = Date.now();
@@ -1,7 +1,5 @@
1
1
  /* Copyright 2026 Marimo. All rights reserved. */
2
2
 
3
- import { toPng } from "./html-to-image";
4
-
5
3
  const PLACEHOLDER_WIDTH = 320;
6
4
  const PLACEHOLDER_HEIGHT = 180;
7
5
 
@@ -95,11 +93,11 @@ function createPlaceholderImage(url: string | null): string {
95
93
  }
96
94
 
97
95
  /**
98
- * Capture an iframe as a PNG image. We need to do this because external iframes are not supported by html-to-image.
96
+ * Capture external iframes as a PNG image. External iframes are not supported by html-to-image.
99
97
  * @param element - The element to capture the iframe from
100
98
  * @returns The image data URL of the iframe, or a placeholder image if the iframe is external
101
99
  */
102
- export async function captureIframeAsImage(
100
+ export async function captureExternalIframes(
103
101
  element: HTMLElement,
104
102
  ): Promise<string | null> {
105
103
  const iframe = element.querySelector("iframe");
@@ -133,10 +131,5 @@ export async function captureIframeAsImage(
133
131
  }
134
132
  }
135
133
 
136
- // Capture the iframe content
137
- try {
138
- return await toPng(doc.body);
139
- } catch {
140
- return createPlaceholderImage(null);
141
- }
134
+ return null;
142
135
  }