@marimo-team/islands 0.23.11-dev67 → 0.23.11-dev8

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 (25) hide show
  1. package/dist/{chat-ui-C4-GMW9y.js → chat-ui-c1FdlK-C.js} +2 -2
  2. package/dist/{code-visibility-UH2371Sv.js → code-visibility-C-06v5KM.js} +20 -18
  3. package/dist/{html-to-image-BwfcJJxA.js → html-to-image-D5-EpALB.js} +2158 -2132
  4. package/dist/main.js +5 -5
  5. package/dist/{process-output-DsloEDna.js → process-output-WDZE0cyS.js} +1 -1
  6. package/dist/{reveal-component-BNU1V1x5.js → reveal-component-yjK700z1.js} +2 -2
  7. package/dist/style.css +1 -1
  8. package/package.json +1 -1
  9. package/src/components/datasources/__tests__/column-preview.test.tsx +97 -0
  10. package/src/components/datasources/__tests__/filter-empty.test.ts +81 -0
  11. package/src/components/datasources/__tests__/utils.test.ts +62 -1
  12. package/src/components/datasources/column-preview.tsx +2 -4
  13. package/src/components/datasources/components.tsx +15 -7
  14. package/src/components/datasources/datasources.tsx +311 -178
  15. package/src/components/datasources/utils.ts +40 -1
  16. package/src/components/editor/ai/ai-completion-editor.tsx +6 -5
  17. package/src/components/editor/connections/components.tsx +13 -0
  18. package/src/components/editor/connections/storage/__tests__/__snapshots__/as-code.test.ts.snap +4 -4
  19. package/src/components/editor/connections/storage/as-code.ts +11 -4
  20. package/src/core/ai/__tests__/strip-wrapping-backticks.test.ts +133 -0
  21. package/src/core/ai/stream-completion-text.ts +48 -0
  22. package/src/core/ai/strip-wrapping-backticks.ts +88 -0
  23. package/src/core/codemirror/ai/request.ts +2 -14
  24. package/src/core/datasets/__tests__/data-source.test.ts +226 -0
  25. package/src/core/datasets/data-source-connections.ts +88 -24
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marimo-team/islands",
3
- "version": "0.23.11-dev67",
3
+ "version": "0.23.11-dev8",
4
4
  "main": "dist/main.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "type": "module",
@@ -0,0 +1,97 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+
3
+ import { render } from "@testing-library/react";
4
+ import { Provider } from "jotai";
5
+ import type React from "react";
6
+ import { beforeEach, describe, expect, it, vi } from "vitest";
7
+ import { MockRequestClient } from "@/__mocks__/requests";
8
+ import type { SQLTableContext } from "@/core/datasets/data-source-connections";
9
+ import type { DataTable, DataTableColumn } from "@/core/kernel/messages";
10
+ import { requestClientAtom } from "@/core/network/requests";
11
+ import { store } from "@/core/state/jotai";
12
+ import { DatasetColumnPreview } from "../column-preview";
13
+
14
+ const wrapper = ({ children }: { children: React.ReactNode }) => (
15
+ <Provider store={store}>{children}</Provider>
16
+ );
17
+
18
+ const table: DataTable = {
19
+ name: "users",
20
+ columns: [],
21
+ source: "my_engine",
22
+ // Not "connection"/"catalog", so a preview is requested on mount.
23
+ source_type: "duckdb",
24
+ type: "table",
25
+ engine: "my_engine" as DataTable["engine"],
26
+ indexes: null,
27
+ num_columns: null,
28
+ num_rows: null,
29
+ variable_name: null,
30
+ primary_keys: null,
31
+ };
32
+
33
+ const column = { name: "email" } as DataTableColumn;
34
+
35
+ function renderPreview(sqlTableContext?: SQLTableContext) {
36
+ const client = MockRequestClient.create();
37
+ store.set(requestClientAtom, client);
38
+ render(
39
+ <DatasetColumnPreview
40
+ table={table}
41
+ column={column}
42
+ preview={undefined}
43
+ onAddColumnChart={vi.fn()}
44
+ sqlTableContext={sqlTableContext}
45
+ />,
46
+ { wrapper },
47
+ );
48
+ return client;
49
+ }
50
+
51
+ const ctx = (
52
+ overrides: Partial<SQLTableContext> & { database: string; schema: string },
53
+ ): SQLTableContext => ({
54
+ engine: "my_engine",
55
+ dialect: "duckdb",
56
+ ...overrides,
57
+ });
58
+
59
+ describe("DatasetColumnPreview fullyQualifiedTableName", () => {
60
+ beforeEach(() => {
61
+ vi.clearAllMocks();
62
+ });
63
+
64
+ it("uses just the table name without a SQL context", () => {
65
+ const client = renderPreview(undefined);
66
+ expect(client.previewDatasetColumn).toHaveBeenCalledWith(
67
+ expect.objectContaining({ fullyQualifiedTableName: "users" }),
68
+ );
69
+ });
70
+
71
+ it("qualifies with database and schema for flat engines", () => {
72
+ const client = renderPreview(ctx({ database: "db", schema: "public" }));
73
+ expect(client.previewDatasetColumn).toHaveBeenCalledWith(
74
+ expect.objectContaining({ fullyQualifiedTableName: "db.public.users" }),
75
+ );
76
+ });
77
+
78
+ it("does not emit a double dot for schemaless tables", () => {
79
+ // Regression: previously produced "db..users".
80
+ const client = renderPreview(ctx({ database: "db", schema: "" }));
81
+ expect(client.previewDatasetColumn).toHaveBeenCalledWith(
82
+ expect.objectContaining({ fullyQualifiedTableName: "db.users" }),
83
+ );
84
+ });
85
+
86
+ it("includes the full schema path for nested namespaces", () => {
87
+ // Regression: previously dropped schemaPath and emitted "top.deep.users".
88
+ const client = renderPreview(
89
+ ctx({ database: "top", schema: "deep", schemaPath: ["nested", "deep"] }),
90
+ );
91
+ expect(client.previewDatasetColumn).toHaveBeenCalledWith(
92
+ expect.objectContaining({
93
+ fullyQualifiedTableName: "top.nested.deep.users",
94
+ }),
95
+ );
96
+ });
97
+ });
@@ -28,11 +28,15 @@ function makeSchema(opts: {
28
28
  name: string;
29
29
  tables: DataTable[];
30
30
  tables_resolved?: boolean;
31
+ child_schemas?: DatabaseSchema[];
32
+ child_schemas_resolved?: boolean;
31
33
  }): DatabaseSchema {
32
34
  return {
33
35
  name: opts.name,
34
36
  tables: opts.tables,
35
37
  tables_resolved: opts.tables_resolved ?? true,
38
+ child_schemas: opts.child_schemas ?? [],
39
+ child_schemas_resolved: opts.child_schemas_resolved ?? true,
36
40
  };
37
41
  }
38
42
 
@@ -180,4 +184,81 @@ describe("filterEmptyDatabases", () => {
180
184
 
181
185
  expect(databases).toEqual(snapshot);
182
186
  });
187
+
188
+ it("keeps a namespace that has only child namespaces (no own tables)", () => {
189
+ const databases = [
190
+ makeDatabase("iceberg", [
191
+ makeSchema({
192
+ name: "top",
193
+ tables: [],
194
+ child_schemas: [
195
+ makeSchema({ name: "nested", tables: [makeTable("t1")] }),
196
+ ],
197
+ }),
198
+ ]),
199
+ ];
200
+
201
+ expect(filterEmptyDatabases(databases)).toBe(databases);
202
+ });
203
+
204
+ it("preserves a namespace whose child schemas are deferred", () => {
205
+ const databases = [
206
+ makeDatabase("iceberg", [
207
+ makeSchema({
208
+ name: "top",
209
+ tables: [],
210
+ child_schemas: [],
211
+ child_schemas_resolved: false,
212
+ }),
213
+ ]),
214
+ ];
215
+
216
+ expect(filterEmptyDatabases(databases)).toBe(databases);
217
+ });
218
+
219
+ it("hides a nested namespace that is resolved-empty", () => {
220
+ const databases = [
221
+ makeDatabase("iceberg", [
222
+ makeSchema({
223
+ name: "top",
224
+ tables: [makeTable("t1")],
225
+ child_schemas: [
226
+ makeSchema({ name: "empty_child", tables: [] }),
227
+ makeSchema({ name: "full_child", tables: [makeTable("t2")] }),
228
+ ],
229
+ }),
230
+ ]),
231
+ ];
232
+
233
+ expect(filterEmptyDatabases(databases)).toEqual([
234
+ makeDatabase("iceberg", [
235
+ makeSchema({
236
+ name: "top",
237
+ tables: [makeTable("t1")],
238
+ child_schemas: [
239
+ makeSchema({ name: "full_child", tables: [makeTable("t2")] }),
240
+ ],
241
+ }),
242
+ ]),
243
+ ]);
244
+ });
245
+
246
+ it("hides a parent namespace when all its descendants are empty", () => {
247
+ const databases = [
248
+ makeDatabase("iceberg", [
249
+ makeSchema({
250
+ name: "top",
251
+ tables: [],
252
+ child_schemas: [makeSchema({ name: "empty_child", tables: [] })],
253
+ }),
254
+ makeSchema({ name: "other", tables: [makeTable("t1")] }),
255
+ ]),
256
+ ];
257
+
258
+ expect(filterEmptyDatabases(databases)).toEqual([
259
+ makeDatabase("iceberg", [
260
+ makeSchema({ name: "other", tables: [makeTable("t1")] }),
261
+ ]),
262
+ ]);
263
+ });
183
264
  });
@@ -4,7 +4,7 @@ import { describe, expect, it } from "vitest";
4
4
  import type { SQLTableContext } from "@/core/datasets/data-source-connections";
5
5
  import { DUCKDB_ENGINE } from "@/core/datasets/engines";
6
6
  import type { DataTable, DataTableColumn } from "@/core/kernel/messages";
7
- import { sqlCode } from "../utils";
7
+ import { sqlCode, tableUniqueId } from "../utils";
8
8
 
9
9
  describe("sqlCode", () => {
10
10
  const mockTable: DataTable = {
@@ -458,3 +458,64 @@ describe("sqlCode", () => {
458
458
  });
459
459
  });
460
460
  });
461
+
462
+ describe("tableUniqueId", () => {
463
+ const ctx = (
464
+ overrides: Partial<SQLTableContext> & { database: string; schema: string },
465
+ ): SQLTableContext => ({
466
+ engine: "e",
467
+ dialect: "duckdb",
468
+ ...overrides,
469
+ });
470
+
471
+ it("returns just the table name without a context", () => {
472
+ expect(tableUniqueId(undefined, "t")).toBe("t");
473
+ });
474
+
475
+ it("uses database + schema for flat engines", () => {
476
+ expect(tableUniqueId(ctx({ database: "db", schema: "public" }), "t")).toBe(
477
+ "db.public.t",
478
+ );
479
+ });
480
+
481
+ it("does not duplicate the leaf schema for nested namespaces", () => {
482
+ // Regression: previously produced "top.nested.nested.t".
483
+ expect(
484
+ tableUniqueId(
485
+ ctx({ database: "top", schema: "nested", schemaPath: ["nested"] }),
486
+ "t",
487
+ ),
488
+ ).toBe("top.nested.t");
489
+ });
490
+
491
+ it("includes the full schema path for deeply nested namespaces", () => {
492
+ expect(
493
+ tableUniqueId(
494
+ ctx({
495
+ database: "top",
496
+ schema: "deep",
497
+ schemaPath: ["nested", "deep"],
498
+ }),
499
+ "t",
500
+ ),
501
+ ).toBe("top.nested.deep.t");
502
+ });
503
+
504
+ it("falls back to the flat schema when schemaPath is empty", () => {
505
+ expect(
506
+ tableUniqueId(
507
+ ctx({ database: "db", schema: "public", schemaPath: [] }),
508
+ "t",
509
+ ),
510
+ ).toBe("db.public.t");
511
+ });
512
+
513
+ it("does not emit a double dot for schemaless tables", () => {
514
+ expect(
515
+ tableUniqueId(ctx({ database: "db", schema: "", schemaPath: [] }), "t"),
516
+ ).toBe("db.t");
517
+ expect(tableUniqueId(ctx({ database: "db", schema: "" }), "t")).toBe(
518
+ "db.t",
519
+ );
520
+ });
521
+ });
@@ -29,7 +29,7 @@ import { Button } from "../ui/button";
29
29
  import { Tooltip } from "../ui/tooltip";
30
30
  import { ColumnPreviewContainer } from "./components";
31
31
  import { InstallPackageButton } from "./install-package-button";
32
- import { convertStatsName, sqlCode } from "./utils";
32
+ import { convertStatsName, sqlCode, tableUniqueId } from "./utils";
33
33
 
34
34
  export const DatasetColumnPreview: React.FC<{
35
35
  table: DataTable;
@@ -48,9 +48,7 @@ export const DatasetColumnPreview: React.FC<{
48
48
  tableName: table.name,
49
49
  columnName: column.name,
50
50
  sourceType: table.source_type,
51
- fullyQualifiedTableName: sqlTableContext
52
- ? `${sqlTableContext.database}.${sqlTableContext.schema}.${table.name}`
53
- : table.name,
51
+ fullyQualifiedTableName: tableUniqueId(sqlTableContext, table.name),
54
52
  });
55
53
  };
56
54
 
@@ -14,25 +14,31 @@ export const RotatingChevron: React.FC<{ isExpanded: boolean }> = ({
14
14
  export const DatasourceLabel: React.FC<{
15
15
  children: React.ReactNode;
16
16
  className?: string;
17
- }> = ({ children, className }) => {
17
+ style?: CSSProperties;
18
+ }> = ({ children, className, style }) => {
18
19
  return (
19
20
  <div
20
21
  className={cn(
21
22
  "flex gap-1.5 items-center font-bold py-1.5 text-muted-foreground bg-(--slate-2) text-sm",
22
23
  className,
23
24
  )}
25
+ style={style}
24
26
  >
25
27
  {children}
26
28
  </div>
27
29
  );
28
30
  };
29
31
 
30
- export const EmptyState: React.FC<{ content: string; className?: string }> = ({
31
- content,
32
- className,
33
- }) => {
32
+ export const EmptyState: React.FC<{
33
+ content: string;
34
+ className?: string;
35
+ style?: CSSProperties;
36
+ }> = ({ content, className, style }) => {
34
37
  return (
35
- <div className={cn("text-sm text-muted-foreground py-1", className)}>
38
+ <div
39
+ className={cn("text-sm text-muted-foreground py-1", className)}
40
+ style={style}
41
+ >
36
42
  {content}
37
43
  </div>
38
44
  );
@@ -61,13 +67,15 @@ export const ErrorState: React.FC<{
61
67
  export const LoadingState: React.FC<{
62
68
  message: string;
63
69
  className?: string;
64
- }> = ({ message, className }) => {
70
+ style?: CSSProperties;
71
+ }> = ({ message, className, style }) => {
65
72
  return (
66
73
  <div
67
74
  className={cn(
68
75
  "text-sm bg-blue-50 dark:bg-(--accent) text-blue-500 dark:text-blue-50 flex items-center gap-2 p-2 h-8",
69
76
  className,
70
77
  )}
78
+ style={style}
71
79
  >
72
80
  <LoaderCircle className="h-4 w-4 animate-spin" />
73
81
  {message}