@marimo-team/islands 0.23.9-dev4 → 0.23.9-dev40

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 (96) hide show
  1. package/dist/{ConnectedDataExplorerComponent-OzrfMM5L.js → ConnectedDataExplorerComponent-CyV83R2m.js} +4 -4
  2. package/dist/assets/__vite-browser-external-Ci2ZQfXU.js +1 -0
  3. package/dist/assets/{worker-CpBbwbQo.js → worker-ip3AI_sN.js} +2 -2
  4. package/dist/{chat-ui-BDI3FMI8.js → chat-ui-ChD4VvCo.js} +3060 -3033
  5. package/dist/{code-visibility-VZebNmSs.js → code-visibility-CajOShec.js} +1634 -1314
  6. package/dist/{formats-DQ5qjo_Q.js → formats-DHxc-FdY.js} +1 -1
  7. package/dist/{glide-data-editor-DqRY9naW.js → glide-data-editor-BOmK9ETQ.js} +2 -2
  8. package/dist/{html-to-image-CiSinpSR.js → html-to-image-BHv7CEU_.js} +2145 -2153
  9. package/dist/{input-CZD2z6X2.js → input-_2sjvfne.js} +1 -1
  10. package/dist/main.js +1522 -1556
  11. package/dist/{mermaid-IU93XzmY.js → mermaid-lXOw5Py9.js} +2 -2
  12. package/dist/{process-output-5qJjMRKh.js → process-output-BvySRgli.js} +33 -25
  13. package/dist/{reveal-component-DZtPMEoM.js → reveal-component-B-fRdTqs.js} +17 -17
  14. package/dist/{spec-a6DaqW__.js → spec-B96zNUEA.js} +1 -1
  15. package/dist/style.css +1 -1
  16. package/dist/{toDate-ZVVIBmdk.js → toDate-x-WRDCH7.js} +1 -1
  17. package/dist/{useAsyncData-C008zUPi.js → useAsyncData-iRgKDT5s.js} +1 -1
  18. package/dist/{useDeepCompareMemoize-BrA3_n61.js → useDeepCompareMemoize-CkQ57VS2.js} +1 -1
  19. package/dist/{useLifecycle-BNaoJ5a4.js → useLifecycle-BBO9PIph.js} +1 -1
  20. package/dist/{useTheme-7O0YWlE5.js → useTheme-DHIrRQOe.js} +34 -21
  21. package/dist/{vega-component-DJNmOdUj.js → vega-component-Dq-SH463.js} +5 -5
  22. package/package.json +1 -1
  23. package/src/components/ai/__tests__/ai-utils.test.ts +43 -38
  24. package/src/components/ai/ai-model-dropdown.tsx +2 -2
  25. package/src/components/app-config/ai-config.tsx +147 -16
  26. package/src/components/app-config/user-config-form.tsx +37 -1
  27. package/src/components/chat/__tests__/chat-utils.test.ts +269 -0
  28. package/src/components/chat/chat-panel.tsx +38 -5
  29. package/src/components/chat/chat-utils.ts +14 -58
  30. package/src/components/data-table/TableBottomBar.tsx +27 -6
  31. package/src/components/data-table/TableTopBar.tsx +7 -1
  32. package/src/components/data-table/__tests__/TableBottomBar.test.tsx +73 -0
  33. package/src/components/data-table/__tests__/column-explorer.test.tsx +128 -0
  34. package/src/components/data-table/__tests__/data-table.test.tsx +52 -1
  35. package/src/components/data-table/__tests__/header-items.test.tsx +257 -1
  36. package/src/components/data-table/__tests__/useColumnVisibility.test.ts +42 -0
  37. package/src/components/data-table/column-explorer-panel/column-explorer.tsx +98 -26
  38. package/src/components/data-table/column-header.tsx +19 -12
  39. package/src/components/data-table/columns.tsx +3 -4
  40. package/src/components/data-table/data-table.tsx +37 -0
  41. package/src/components/data-table/export-actions.tsx +36 -18
  42. package/src/components/data-table/header-items.tsx +58 -17
  43. package/src/components/data-table/hooks/use-column-visibility.ts +56 -0
  44. package/src/components/data-table/pagination.tsx +16 -3
  45. package/src/components/data-table/schemas.ts +2 -2
  46. package/src/components/data-table/table-explorer-panel/table-explorer-panel.tsx +16 -6
  47. package/src/components/databases/display.tsx +2 -0
  48. package/src/components/datasources/__tests__/utils.test.ts +82 -0
  49. package/src/components/datasources/utils.ts +16 -15
  50. package/src/components/editor/actions/pair-with-agent-modal.tsx +1 -0
  51. package/src/components/editor/actions/useCellActionButton.tsx +3 -3
  52. package/src/components/editor/actions/useNotebookActions.tsx +5 -2
  53. package/src/components/editor/cell/code/cell-editor.tsx +7 -4
  54. package/src/components/editor/chrome/types.ts +13 -6
  55. package/src/components/editor/chrome/wrapper/app-chrome.tsx +6 -4
  56. package/src/components/editor/chrome/wrapper/footer-items/ai-status.tsx +10 -1
  57. package/src/components/editor/chrome/wrapper/sidebar.tsx +7 -5
  58. package/src/components/editor/errors/auto-fix.tsx +3 -3
  59. package/src/components/editor/errors/mangled-local-chip.tsx +50 -0
  60. package/src/components/editor/navigation/__tests__/navigation.test.ts +15 -0
  61. package/src/components/editor/navigation/navigation.ts +5 -0
  62. package/src/components/editor/output/MarimoErrorOutput.tsx +110 -27
  63. package/src/components/editor/output/MarimoTracebackOutput.tsx +55 -37
  64. package/src/components/editor/renderers/cell-array.tsx +27 -24
  65. package/src/components/editor/renderers/slides-layout/slides-layout.tsx +1 -1
  66. package/src/components/slides/reveal-component.tsx +3 -3
  67. package/src/components/slides/slide-form.tsx +11 -3
  68. package/src/components/static-html/static-banner.tsx +28 -22
  69. package/src/core/ai/__tests__/model-registry.test.ts +72 -60
  70. package/src/core/ai/model-registry.ts +33 -28
  71. package/src/core/cells/__tests__/actions.test.ts +48 -0
  72. package/src/core/cells/actions.ts +5 -6
  73. package/src/core/codemirror/__tests__/setup.test.ts +29 -0
  74. package/src/core/codemirror/cells/traceback-decorations.ts +1 -1
  75. package/src/core/codemirror/cm.ts +3 -2
  76. package/src/core/codemirror/format.ts +1 -0
  77. package/src/core/codemirror/keymaps/vim.ts +63 -0
  78. package/src/core/codemirror/language/languages/sql/sql.ts +1 -0
  79. package/src/core/codemirror/language/languages/sql/utils.ts +2 -0
  80. package/src/core/config/__tests__/config-schema.test.ts +4 -0
  81. package/src/core/config/config-schema.ts +4 -0
  82. package/src/core/config/config.ts +16 -0
  83. package/src/css/app/Cell.css +0 -1
  84. package/src/plugins/impl/DataTablePlugin.tsx +94 -33
  85. package/src/plugins/impl/__tests__/DataTablePlugin.test.tsx +1 -0
  86. package/src/plugins/impl/chat/ChatPlugin.tsx +7 -1
  87. package/src/plugins/impl/chat/__tests__/chat-ui.test.ts +278 -0
  88. package/src/plugins/impl/chat/chat-ui.tsx +106 -59
  89. package/src/plugins/impl/chat/types.ts +5 -0
  90. package/src/plugins/impl/data-frames/DataFramePlugin.tsx +8 -6
  91. package/src/stories/dataframe.stories.tsx +1 -0
  92. package/src/utils/__tests__/json-parser.test.ts +1 -69
  93. package/src/utils/__tests__/local-variables.test.ts +132 -0
  94. package/src/utils/json/json-parser.ts +0 -30
  95. package/src/utils/local-variables.ts +67 -0
  96. package/dist/assets/__vite-browser-external-CAdMKBac.js +0 -1
@@ -8,6 +8,7 @@ import {
8
8
  ArrowUpNarrowWideIcon,
9
9
  ChevronsUpDown,
10
10
  CopyIcon,
11
+ EyeOffIcon,
11
12
  FilterX,
12
13
  PinOffIcon,
13
14
  WrapTextIcon,
@@ -28,10 +29,13 @@ import { formattingExample } from "./column-formatting/feature";
28
29
  import { formatOptions } from "./column-formatting/types";
29
30
  import { NAMELESS_COLUMN_PREFIX } from "./columns";
30
31
 
31
- export function renderFormatOptions<TData, TValue>(
32
- column: Column<TData, TValue>,
33
- locale: string,
34
- ) {
32
+ export function FormatOptions<TData, TValue>({
33
+ column,
34
+ locale,
35
+ }: {
36
+ column: Column<TData, TValue>;
37
+ locale: string;
38
+ }) {
35
39
  const dataType: DataType | undefined = column.columnDef.meta?.dataType;
36
40
  const columnFormatOptions = dataType ? formatOptions[dataType] : [];
37
41
 
@@ -82,9 +86,11 @@ export function renderFormatOptions<TData, TValue>(
82
86
  );
83
87
  }
84
88
 
85
- export function renderColumnWrapping<TData, TValue>(
86
- column: Column<TData, TValue>,
87
- ) {
89
+ export function ColumnWrapping<TData, TValue>({
90
+ column,
91
+ }: {
92
+ column: Column<TData, TValue>;
93
+ }) {
88
94
  if (!column.getCanWrap?.() || !column.getColumnWrapping) {
89
95
  return null;
90
96
  }
@@ -107,9 +113,11 @@ export function renderColumnWrapping<TData, TValue>(
107
113
  );
108
114
  }
109
115
 
110
- export function renderColumnPinning<TData, TValue>(
111
- column: Column<TData, TValue>,
112
- ) {
116
+ export function ColumnPinning<TData, TValue>({
117
+ column,
118
+ }: {
119
+ column: Column<TData, TValue>;
120
+ }) {
113
121
  if (!column.getCanPin?.() || !column.getIsPinned) {
114
122
  return null;
115
123
  }
@@ -139,7 +147,28 @@ export function renderColumnPinning<TData, TValue>(
139
147
  );
140
148
  }
141
149
 
142
- export function renderCopyColumn<TData, TValue>(column: Column<TData, TValue>) {
150
+ export function HideColumn<TData, TValue>({
151
+ column,
152
+ }: {
153
+ column: Column<TData, TValue>;
154
+ }) {
155
+ if (!column.getCanHide()) {
156
+ return null;
157
+ }
158
+
159
+ return (
160
+ <DropdownMenuItem onClick={() => column.toggleVisibility(false)}>
161
+ <EyeOffIcon className="mo-dropdown-icon" />
162
+ Hide column
163
+ </DropdownMenuItem>
164
+ );
165
+ }
166
+
167
+ export function CopyColumn<TData, TValue>({
168
+ column,
169
+ }: {
170
+ column: Column<TData, TValue>;
171
+ }) {
143
172
  if (!column.getCanCopy?.()) {
144
173
  return null;
145
174
  }
@@ -159,10 +188,19 @@ export function renderCopyColumn<TData, TValue>(column: Column<TData, TValue>) {
159
188
  const AscIcon = ArrowUpNarrowWideIcon;
160
189
  const DescIcon = ArrowDownWideNarrowIcon;
161
190
 
162
- export function renderSorts<TData, TValue>(
163
- column: Column<TData, TValue>,
164
- table?: Table<TData>,
165
- ) {
191
+ /**
192
+ * `table` is optional: it is only needed to detect multi-column sorting and
193
+ * offer "Clear all sorts". Call sites that build their header inside column
194
+ * definitions (where the table instance isn't yet in scope) omit it and fall
195
+ * back to single-column "Clear sort".
196
+ */
197
+ export function Sorts<TData, TValue>({
198
+ column,
199
+ table,
200
+ }: {
201
+ column: Column<TData, TValue>;
202
+ table?: Table<TData>;
203
+ }) {
166
204
  if (!column.getCanSort()) {
167
205
  return null;
168
206
  }
@@ -233,7 +271,6 @@ export function renderSorts<TData, TValue>(
233
271
  {sortDirection === "desc" && renderSortIndex()}
234
272
  </DropdownMenuItem>
235
273
  {renderClearSort()}
236
- <DropdownMenuSeparator />
237
274
  </>
238
275
  );
239
276
  }
@@ -254,7 +291,11 @@ export function renderSortIcon<TData, TValue>(column: Column<TData, TValue>) {
254
291
  return <Icon className="h-3 w-3" />;
255
292
  }
256
293
 
257
- export function renderDataType<TData, TValue>(column: Column<TData, TValue>) {
294
+ export function DataType<TData, TValue>({
295
+ column,
296
+ }: {
297
+ column: Column<TData, TValue>;
298
+ }) {
258
299
  const dtype: string | undefined = column.columnDef.meta?.dtype;
259
300
  if (!dtype) {
260
301
  return null;
@@ -0,0 +1,56 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+ "use no memo";
3
+
4
+ import { useInternalStateWithSync } from "@/hooks/useInternalStateWithSync";
5
+ import type { Table, VisibilityState } from "@tanstack/react-table";
6
+ import { dequal as isDeepEqual } from "dequal";
7
+ import type React from "react";
8
+
9
+ interface UseColumnVisibilityResult {
10
+ columnVisibility: VisibilityState;
11
+ setColumnVisibility: React.Dispatch<React.SetStateAction<VisibilityState>>;
12
+ }
13
+
14
+ export function useColumnVisibility(
15
+ hiddenColumns?: string[],
16
+ ): UseColumnVisibilityResult {
17
+ const [columnVisibility, setColumnVisibility] =
18
+ useInternalStateWithSync<VisibilityState>(
19
+ Object.fromEntries((hiddenColumns ?? []).map((c) => [c, false])),
20
+ isDeepEqual,
21
+ );
22
+
23
+ return { columnVisibility, setColumnVisibility };
24
+ }
25
+
26
+ interface ColumnVisibilityCounts {
27
+ total: number;
28
+ visible: number;
29
+ hidden: number;
30
+ }
31
+
32
+ export function getUserColumnVisibilityCounts<TData>(
33
+ table: Table<TData>,
34
+ ): ColumnVisibilityCounts {
35
+ const userColumns = table.getAllLeafColumns().filter((c) => c.getCanHide());
36
+ const visible = userColumns.filter((c) => c.getIsVisible()).length;
37
+ return {
38
+ total: userColumns.length,
39
+ visible,
40
+ hidden: userColumns.length - visible,
41
+ };
42
+ }
43
+
44
+ // When columns are clipped server-side, the TanStack instance only holds the
45
+ // rendered subset, so visible/hidden math must use that subset's total. The
46
+ // dataset-wide value is still correct for the no-hidden "N columns" label.
47
+ export function getColumnCountForDisplay<TData>(
48
+ table: Table<TData>,
49
+ datasetTotalColumns: number,
50
+ ): { totalColumns: number; hiddenColumns: number } {
51
+ const counts = getUserColumnVisibilityCounts(table);
52
+ return {
53
+ totalColumns: counts.hidden > 0 ? counts.total : datasetTotalColumns,
54
+ hiddenColumns: counts.hidden,
55
+ };
56
+ }
@@ -443,15 +443,28 @@ export function prettifyRowCount(rowCount: number, locale: string): string {
443
443
  export const prettifyRowColumnCount = ({
444
444
  numRows,
445
445
  totalColumns,
446
+ hiddenColumns,
446
447
  locale,
447
448
  }: {
448
449
  numRows: number | "too_many";
449
450
  totalColumns: number;
451
+ hiddenColumns?: number;
450
452
  locale: string;
451
- }): string => {
453
+ }): { rowsAndColumns: string; hiddenSuffix: string | null } => {
452
454
  const rowsLabel =
453
455
  numRows === "too_many" ? "Unknown" : prettifyRowCount(numRows, locale);
454
- const columnsLabel = `${prettyNumber(totalColumns, locale)} ${new PluralWord("column").pluralize(totalColumns)}`;
455
456
 
456
- return [rowsLabel, columnsLabel].join(", ");
457
+ const hidden = hiddenColumns ?? 0;
458
+ const visibleColumns = totalColumns - hidden;
459
+
460
+ const columnsLabel =
461
+ hidden > 0
462
+ ? `${prettyNumber(visibleColumns, locale)} visible`
463
+ : `${prettyNumber(totalColumns, locale)} ${new PluralWord("column").pluralize(totalColumns)}`;
464
+
465
+ return {
466
+ rowsAndColumns: [rowsLabel, columnsLabel].join(", "),
467
+ hiddenSuffix:
468
+ hidden > 0 ? `(${prettyNumber(hidden, locale)} hidden)` : null,
469
+ };
457
470
  };
@@ -4,7 +4,7 @@ import z from "zod";
4
4
  import { rpc } from "@/plugins/core/rpc";
5
5
 
6
6
  export type DownloadAsArgs = (req: {
7
- format: "csv" | "json" | "parquet";
7
+ format: "csv" | "json" | "parquet" | "tsv";
8
8
  }) => Promise<{
9
9
  url: string;
10
10
  filename: string;
@@ -15,7 +15,7 @@ export type DownloadAsArgs = (req: {
15
15
  export const DownloadAsSchema = rpc
16
16
  .input(
17
17
  z.object({
18
- format: z.enum(["csv", "json", "parquet"]),
18
+ format: z.enum(["csv", "json", "parquet", "tsv"]),
19
19
  }),
20
20
  )
21
21
  .output(
@@ -1,8 +1,15 @@
1
1
  /* Copyright 2026 Marimo. All rights reserved. */
2
+ "use no memo";
3
+
4
+ // tanstack/table is not compatible with React compiler
5
+ // https://github.com/TanStack/table/issues/5567
2
6
 
3
7
  import { Fill } from "@marimo-team/react-slotz";
4
- import type { OnChangeFn, RowSelectionState } from "@tanstack/react-table";
5
- import type React from "react";
8
+ import type {
9
+ OnChangeFn,
10
+ RowSelectionState,
11
+ Table,
12
+ } from "@tanstack/react-table";
6
13
  import { Button } from "@/components/ui/button";
7
14
  import { Tabs, TabsContent } from "@/components/ui/tabs";
8
15
  import { SlotNames } from "@/core/slots/slots";
@@ -19,7 +26,7 @@ import { ColumnExplorerPanel } from "../column-explorer-panel/column-explorer";
19
26
  import { RowViewerPanel } from "../row-viewer-panel/row-viewer";
20
27
  import type { FieldTypesWithExternalType, TooManyRows } from "../types";
21
28
 
22
- export interface TableExplorerPanelProps {
29
+ export interface TableExplorerPanelProps<TData> {
23
30
  // Row viewer props
24
31
  rowIdx: number;
25
32
  setRowIdx: (rowIdx: number) => void;
@@ -33,6 +40,7 @@ export interface TableExplorerPanelProps {
33
40
  previewColumn?: PreviewColumn;
34
41
  totalColumns: number;
35
42
  tableId: string;
43
+ table: Table<TData>;
36
44
  // Visibility flags
37
45
  showRowExplorer: boolean;
38
46
  showColumnExplorer: boolean;
@@ -46,7 +54,7 @@ const tabTriggerClassName =
46
54
  const activeClassName = "text-primary";
47
55
  const inactiveClassName = "hover:text-foreground";
48
56
 
49
- export const TableExplorerPanel: React.FC<TableExplorerPanelProps> = ({
57
+ export function TableExplorerPanel<TData>({
50
58
  // Row viewer
51
59
  rowIdx,
52
60
  setRowIdx,
@@ -60,13 +68,14 @@ export const TableExplorerPanel: React.FC<TableExplorerPanelProps> = ({
60
68
  previewColumn,
61
69
  totalColumns,
62
70
  tableId,
71
+ table,
63
72
  // Visibility
64
73
  showRowExplorer,
65
74
  showColumnExplorer,
66
75
  // Tab state
67
76
  activeTab,
68
77
  onTabChange,
69
- }) => {
78
+ }: TableExplorerPanelProps<TData>) {
70
79
  const showTabs = showRowExplorer && showColumnExplorer;
71
80
 
72
81
  const rowViewer = (
@@ -89,6 +98,7 @@ export const TableExplorerPanel: React.FC<TableExplorerPanelProps> = ({
89
98
  totalRows={totalRows}
90
99
  totalColumns={totalColumns}
91
100
  tableId={tableId}
101
+ table={table}
92
102
  />
93
103
  );
94
104
 
@@ -158,4 +168,4 @@ export const TableExplorerPanel: React.FC<TableExplorerPanelProps> = ({
158
168
  </TabsContent>
159
169
  </Tabs>
160
170
  );
161
- };
171
+ }
@@ -53,6 +53,8 @@ export function dbDisplayName(name: string) {
53
53
  return "MongoDB";
54
54
  case "iceberg":
55
55
  return "Apache Iceberg";
56
+ case "dremio":
57
+ return "Dremio";
56
58
  default:
57
59
  return name;
58
60
  }
@@ -313,6 +313,26 @@ describe("sqlCode", () => {
313
313
  );
314
314
  });
315
315
 
316
+ it("should preserve dots inside quoted schema names", () => {
317
+ const sqlTableContext: SQLTableContext = {
318
+ engine: "postgres",
319
+ schema: "analytics.events",
320
+ defaultSchema: "public",
321
+ defaultDatabase: "mydb",
322
+ database: "remote",
323
+ dialect: "postgres",
324
+ };
325
+
326
+ const result = sqlCode({
327
+ table: mockTable,
328
+ columnName: mockColumn.name,
329
+ sqlTableContext,
330
+ });
331
+ expect(result).toBe(
332
+ '_df = mo.sql(f"""\nSELECT "email" FROM "remote"."analytics.events"."users" LIMIT 100\n""", engine=postgres)',
333
+ );
334
+ });
335
+
316
336
  it("should not quote * column name", () => {
317
337
  const sqlTableContext: SQLTableContext = {
318
338
  engine: "postgres",
@@ -334,6 +354,68 @@ describe("sqlCode", () => {
334
354
  });
335
355
  });
336
356
 
357
+ describe("Dremio dialect", () => {
358
+ it("should quote reserved column names and table path parts", () => {
359
+ const sqlTableContext: SQLTableContext = {
360
+ engine: "dremio_conn",
361
+ schema: "operations",
362
+ defaultSchema: "",
363
+ defaultDatabase: "",
364
+ database: "lakehouse",
365
+ dialect: "dremio",
366
+ };
367
+
368
+ const result = sqlCode({
369
+ table: { ...mockTable, name: "shipments" as const },
370
+ columnName: "order",
371
+ sqlTableContext,
372
+ });
373
+ expect(result).toBe(
374
+ '_df = mo.sql(f"""\nSELECT "order" FROM "lakehouse"."operations"."shipments" LIMIT 100\n""", engine=dremio_conn)',
375
+ );
376
+ });
377
+
378
+ it("should not quote * column name", () => {
379
+ const sqlTableContext: SQLTableContext = {
380
+ engine: "dremio_conn",
381
+ schema: "operations",
382
+ defaultSchema: "",
383
+ defaultDatabase: "",
384
+ database: "lakehouse",
385
+ dialect: "dremio",
386
+ };
387
+
388
+ const result = sqlCode({
389
+ table: { ...mockTable, name: "customers" as const },
390
+ columnName: "*",
391
+ sqlTableContext,
392
+ });
393
+ expect(result).toBe(
394
+ '_df = mo.sql(f"""\nSELECT * FROM "lakehouse"."operations"."customers" LIMIT 100\n""", engine=dremio_conn)',
395
+ );
396
+ });
397
+
398
+ it("should preserve dots inside quoted schema names", () => {
399
+ const sqlTableContext: SQLTableContext = {
400
+ engine: "dremio_conn",
401
+ schema: "samples.dremio.com",
402
+ defaultSchema: "",
403
+ defaultDatabase: "",
404
+ database: "Samples",
405
+ dialect: "dremio",
406
+ };
407
+
408
+ const result = sqlCode({
409
+ table: { ...mockTable, name: "airlines" as const },
410
+ columnName: "*",
411
+ sqlTableContext,
412
+ });
413
+ expect(result).toBe(
414
+ '_df = mo.sql(f"""\nSELECT * FROM "Samples"."samples.dremio.com"."airlines" LIMIT 100\n""", engine=dremio_conn)',
415
+ );
416
+ });
417
+ });
418
+
337
419
  describe("fallback behavior", () => {
338
420
  it("should use default formatter for unknown dialect", () => {
339
421
  const sqlTableContext: SQLTableContext = {
@@ -15,9 +15,9 @@ export function isSchemaless(schemaName: string) {
15
15
 
16
16
  interface SqlCodeFormatter {
17
17
  /**
18
- * Format the table name based on dialect-specific rules
18
+ * Format the table path based on dialect-specific rules
19
19
  */
20
- formatTableName: (tableName: string) => string;
20
+ formatTablePath: (tablePath: string[]) => string;
21
21
  /**
22
22
  * Format the SELECT clause
23
23
  */
@@ -25,7 +25,7 @@ interface SqlCodeFormatter {
25
25
  }
26
26
 
27
27
  const defaultFormatter: SqlCodeFormatter = {
28
- formatTableName: (tableName: string) => tableName,
28
+ formatTablePath: (tablePath: string[]) => tablePath.join("."),
29
29
  formatSelectClause: (columnName: string, tableName: string) =>
30
30
  `SELECT ${columnName} FROM ${tableName} LIMIT 100`,
31
31
  };
@@ -41,7 +41,8 @@ function getFormatter(dialect: string): SqlCodeFormatter {
41
41
  const quote = BigQueryDialect.spec.identifierQuotes;
42
42
  return {
43
43
  // BigQuery uses backticks for identifiers
44
- formatTableName: (tableName: string) => `${quote}${tableName}${quote}`,
44
+ formatTablePath: (tablePath: string[]) =>
45
+ `${quote}${tablePath.join(".")}${quote}`,
45
46
  formatSelectClause: defaultFormatter.formatSelectClause,
46
47
  };
47
48
  }
@@ -49,7 +50,7 @@ function getFormatter(dialect: string): SqlCodeFormatter {
49
50
  case "sqlserver":
50
51
  case "microsoft sql server":
51
52
  return {
52
- formatTableName: defaultFormatter.formatTableName,
53
+ formatTablePath: defaultFormatter.formatTablePath,
53
54
  formatSelectClause: (columnName: string, tableName: string) =>
54
55
  `SELECT TOP 100 ${columnName} FROM ${tableName}`,
55
56
  };
@@ -57,12 +58,11 @@ function getFormatter(dialect: string): SqlCodeFormatter {
57
58
  case "postgres":
58
59
  case "postgresql":
59
60
  case "duckdb":
61
+ case "dremio":
60
62
  // Quote column and table names to avoid raising errors on weird characters
61
63
  return {
62
- formatTableName: (tableName: string) => {
63
- const parts = tableName.split(".");
64
- return parts.map((part) => `"${part}"`).join(".");
65
- },
64
+ formatTablePath: (tablePath: string[]) =>
65
+ tablePath.map((part) => `"${part}"`).join("."),
66
66
  formatSelectClause: (columnName: string, tableName: string) =>
67
67
  `SELECT ${columnName === "*" ? "*" : `"${columnName}"`} FROM ${tableName} LIMIT 100`,
68
68
  };
@@ -114,26 +114,27 @@ export function sqlCode({
114
114
  database,
115
115
  dialect,
116
116
  } = sqlTableContext;
117
- let tableName = table.name;
117
+ const tablePath = [table.name];
118
118
 
119
119
  // Set the fully qualified table name based on schema and database
120
120
  if (isSchemaless(schema)) {
121
- tableName =
122
- database === defaultDatabase ? tableName : `${database}.${tableName}`;
121
+ if (database !== defaultDatabase) {
122
+ tablePath.unshift(database);
123
+ }
123
124
  } else {
124
125
  // Include schema if it's not the default schema
125
126
  if (schema !== defaultSchema) {
126
- tableName = `${schema}.${tableName}`;
127
+ tablePath.unshift(schema);
127
128
  }
128
129
 
129
130
  // Include database if it's not the default database
130
131
  if (database !== defaultDatabase) {
131
- tableName = `${database}.${tableName}`;
132
+ tablePath.unshift(database);
132
133
  }
133
134
  }
134
135
 
135
136
  const formatter = getFormatter(dialect);
136
- const formattedTableName = formatter.formatTableName(tableName);
137
+ const formattedTableName = formatter.formatTablePath(tablePath);
137
138
  const selectClause = formatter.formatSelectClause(
138
139
  columnName,
139
140
  formattedTableName,
@@ -113,6 +113,7 @@ export const PairWithAgentModal: React.FC<{
113
113
  className="underline"
114
114
  >
115
115
  Learn more
116
+ <span className="sr-only"> about pairing marimo with an agent</span>
116
117
  </a>
117
118
  .
118
119
  </DialogDescription>
@@ -48,7 +48,7 @@ import {
48
48
  import { switchLanguage } from "@/core/codemirror/language/extension";
49
49
  import { MARKDOWN_INITIAL_HIDE_CODE } from "@/core/codemirror/language/languages/markdown";
50
50
  import {
51
- aiEnabledAtom,
51
+ aiFeaturesEnabledAtom,
52
52
  appWidthAtom,
53
53
  autoInstantiateAtom,
54
54
  } from "@/core/config/config";
@@ -100,7 +100,7 @@ export function useCellActionButtons({ cell, closePopover }: Props) {
100
100
  const deleteCell = useDeleteCellCallback();
101
101
  const { openModal } = useImperativeModal();
102
102
  const setAiCompletionCell = useSetAtom(aiCompletionCellAtom);
103
- const aiEnabled = useAtomValue(aiEnabledAtom);
103
+ const aiFeaturesEnabled = useAtomValue(aiFeaturesEnabledAtom);
104
104
  const autoInstantiate = useAtomValue(autoInstantiateAtom);
105
105
  const kioskMode = useAtomValue(kioskModeAtom);
106
106
  const appWidth = useAtomValue(appWidthAtom);
@@ -162,7 +162,7 @@ export function useCellActionButtons({ cell, closePopover }: Props) {
162
162
  {
163
163
  icon: <SparklesIcon size={13} strokeWidth={1.5} />,
164
164
  label: "Refactor with AI",
165
- hidden: !aiEnabled,
165
+ hidden: !aiFeaturesEnabled,
166
166
  handle: () => {
167
167
  setAiCompletionCell((current) =>
168
168
  current?.cellId === cellId ? null : { cellId },
@@ -142,9 +142,10 @@ export function useNotebookActions() {
142
142
  const { selectedLayout } = useLayoutState();
143
143
  const { setLayoutView } = useLayoutActions();
144
144
  const togglePresenting = useTogglePresenting();
145
- // Fallback: if sharing is undefined, both are enabled by default
145
+ // Fallback: if sharing is undefined, all options are enabled by default
146
146
  const sharingHtmlEnabled = resolvedConfig.sharing?.html ?? true;
147
147
  const sharingWasmEnabled = resolvedConfig.sharing?.wasm ?? true;
148
+ const sharingMolabEnabled = resolvedConfig.sharing?.molab ?? true;
148
149
 
149
150
  // Server-side PDF export is always available outside WASM.
150
151
  // Browser print fallback is used in WASM.
@@ -360,7 +361,8 @@ export function useNotebookActions() {
360
361
  icon: <Share2Icon size={14} strokeWidth={1.5} />,
361
362
  label: "Share",
362
363
  handle: NOOP_HANDLER,
363
- hidden: !sharingHtmlEnabled && !sharingWasmEnabled,
364
+ hidden:
365
+ !sharingHtmlEnabled && !sharingWasmEnabled && !sharingMolabEnabled,
364
366
  dropdown: [
365
367
  {
366
368
  icon: <GlobeIcon size={14} strokeWidth={1.5} />,
@@ -387,6 +389,7 @@ export function useNotebookActions() {
387
389
  {
388
390
  icon: <MarimoPlusIcon size={14} strokeWidth={1.5} />,
389
391
  label: "Create molab notebook",
392
+ hidden: !sharingMolabEnabled,
390
393
  handle: async () => {
391
394
  const code = await readCode();
392
395
  const url = createShareableLink({
@@ -26,7 +26,7 @@ import {
26
26
  connectedDocAtom,
27
27
  realTimeCollaboration,
28
28
  } from "@/core/codemirror/rtc/extension";
29
- import { autoInstantiateAtom, isAiEnabled } from "@/core/config/config";
29
+ import { autoInstantiateAtom, isAiFeatureEnabled } from "@/core/config/config";
30
30
  import type { UserConfig } from "@/core/config/config-schema";
31
31
  import { OverridingHotkeyProvider } from "@/core/hotkeys/hotkeys";
32
32
  import { connectionAtom } from "@/core/network/connection";
@@ -173,13 +173,13 @@ const CellEditorInternal = ({
173
173
  });
174
174
  });
175
175
 
176
- const aiEnabled = isAiEnabled(userConfig);
176
+ const aiFeaturesEnabled = isAiFeatureEnabled(userConfig);
177
177
 
178
178
  const extensions = useMemo(() => {
179
179
  const extensions = setupCodeMirror({
180
180
  cellId,
181
181
  showPlaceholder,
182
- enableAI: aiEnabled,
182
+ enableAI: aiFeaturesEnabled,
183
183
  cellActions: {
184
184
  ...cellActions,
185
185
  afterToggleMarkdown,
@@ -201,6 +201,9 @@ const CellEditorInternal = ({
201
201
  splitCell,
202
202
  toggleHideCode,
203
203
  aiCellCompletion: () => {
204
+ if (!aiFeaturesEnabled) {
205
+ return false;
206
+ }
204
207
  let closed = false;
205
208
  setAiCompletionCell((v) => {
206
209
  // Toggle close
@@ -271,7 +274,7 @@ const CellEditorInternal = ({
271
274
  userConfig.display,
272
275
  userConfig.diagnostics,
273
276
  userConfig.ai?.inline_tooltip,
274
- aiEnabled,
277
+ aiFeaturesEnabled,
275
278
  theme,
276
279
  showPlaceholder,
277
280
  cellActions,
@@ -201,16 +201,23 @@ export const PANEL_MAP = new Map<PanelType, PanelDescriptor>(
201
201
  );
202
202
 
203
203
  /**
204
- * Check if a panel should be hidden based on its `hidden` property
205
- * and `requiredCapability`.
204
+ * Check if a panel should be hidden based on its descriptor and runtime state.
206
205
  */
207
- export function isPanelHidden(
208
- panel: PanelDescriptor,
209
- capabilities: Capabilities,
210
- ): boolean {
206
+ export function isPanelHidden({
207
+ panel,
208
+ capabilities,
209
+ aiEnabled,
210
+ }: {
211
+ panel: PanelDescriptor;
212
+ capabilities: Capabilities;
213
+ aiEnabled: boolean;
214
+ }): boolean {
211
215
  if (panel.hidden) {
212
216
  return true;
213
217
  }
218
+ if (panel.type === "ai" && !aiEnabled) {
219
+ return true;
220
+ }
214
221
  if (panel.requiredCapability && !capabilities[panel.requiredCapability]) {
215
222
  return true;
216
223
  }