@marimo-team/islands 0.25.3-dev91 → 0.25.3-dev93

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 (103) hide show
  1. package/dist/{ConnectedDataExplorerComponent-Da5OoEUL.js → ConnectedDataExplorerComponent-Dhf5EGs5.js} +4 -4
  2. package/dist/{ErrorBoundary-B2fk7qNp.js → ErrorBoundary-jiRCrm1p.js} +2 -2
  3. package/dist/{abnfDiagram-VCTEODGH-Bz3mrprX.js → abnfDiagram-VCTEODGH-Di400kYu.js} +2 -2
  4. package/dist/{architectureDiagram-5GKGNRK7-Cs5ujb9K.js → architectureDiagram-5GKGNRK7-BdTJcsNJ.js} +1 -1
  5. package/dist/assets/__vite-browser-external-35VXYmnw.js +1 -0
  6. package/dist/assets/{worker-HPY9fbhz.js → worker-Dq8y-WM5.js} +2 -2
  7. package/dist/{chat-ui-C9S_7vEJ.js → chat-ui-D0jT6WV3.js} +7 -7
  8. package/dist/{chunk-IMKFNOWR-CcTSaxZt.js → chunk-IMKFNOWR-mtD7aJBw.js} +1 -1
  9. package/dist/{chunk-SHT3W25Y-DcJcjOaO.js → chunk-SHT3W25Y-DybB1ShH.js} +1 -1
  10. package/dist/{chunk-SVP7TREG-BVtDTjaF.js → chunk-SVP7TREG-DNhhdCex.js} +1 -1
  11. package/dist/{chunk-TICWLB2K-BkDFkh8q.js → chunk-TICWLB2K-NhAQhlDf.js} +1 -1
  12. package/dist/{classDiagram-ZZMXUADV-CfAt7HT5.js → classDiagram-ZZMXUADV-GTL0l5xS.js} +2 -2
  13. package/dist/{classDiagram-v2-VYDZK3BY-CGEjNYa2.js → classDiagram-v2-VYDZK3BY-BWrM2ruI.js} +2 -2
  14. package/dist/{code-visibility-Cgx6Zcjn.js → code-visibility-B_y_dByX.js} +4 -4
  15. package/dist/{common-bzgiULVY.js → common-CmpSkUjV.js} +12 -12
  16. package/dist/{cynefinDiagram-5FMLGOSQ-BW6NmfJi.js → cynefinDiagram-5FMLGOSQ-CX22wa7c.js} +1 -1
  17. package/dist/{dagre-GXQ25YYZ-CWg3CTuF.js → dagre-GXQ25YYZ-Df6Pp-cl.js} +1 -1
  18. package/dist/{data-grid-overlay-editor-sPSVUywU.js → data-grid-overlay-editor-CJTJtfj-.js} +1 -1
  19. package/dist/{diagram-S7CK7UJ4-BPxtoS-z.js → diagram-S7CK7UJ4-CctU9kVx.js} +1 -1
  20. package/dist/{diagram-UQ7AKVKN-DA-vJdFL.js → diagram-UQ7AKVKN-FVHjCF9j.js} +1 -1
  21. package/dist/{diagram-VX7I27RA-D6bkR84S.js → diagram-VX7I27RA-BSDbwFFv.js} +1 -1
  22. package/dist/{diagram-Z3DM3KII-m60g4cAT.js → diagram-Z3DM3KII-BX8kW7Nc.js} +1 -1
  23. package/dist/{dropdown-menu-B3eUiVJB.js → dropdown-menu-B_7xD2X4.js} +1 -1
  24. package/dist/{ebnfDiagram-PWID7BFC-CK99sho2.js → ebnfDiagram-PWID7BFC-DtapdLet.js} +2 -2
  25. package/dist/{erDiagram-RLTQ6QDP-Bryq0tdE.js → erDiagram-RLTQ6QDP-BAru9jHt.js} +1 -1
  26. package/dist/{error-entries-Cmiq1RT-.js → error-entries-BZiemZfD.js} +5 -5
  27. package/dist/{feedback-button-D1xPWL8j.js → feedback-button-BpHnBE3A.js} +5 -5
  28. package/dist/{flowDiagram-HODETNUW-D-3aRpfS.js → flowDiagram-HODETNUW-CjaeFeea.js} +2 -2
  29. package/dist/{formats-ZmooA-MD.js → formats-V8ifA0Bk.js} +1 -1
  30. package/dist/{glide-data-editor-DP-ZHFkV.js → glide-data-editor-B3xUjh0M.js} +5 -5
  31. package/dist/{globals-DPNV_VHW.js → globals-DI7gqOyy.js} +2 -2
  32. package/dist/{html-to-image-Dc3mTXAs.js → html-to-image-DBOTlHF9.js} +5 -5
  33. package/dist/{infoDiagram-27XIBGKW-s1C1kgfU.js → infoDiagram-27XIBGKW-B1hmUggC.js} +1 -1
  34. package/dist/{ishikawaDiagram-5VMMS53U-CCsRgolY.js → ishikawaDiagram-5VMMS53U-DUeT3t-d.js} +1 -1
  35. package/dist/{kanban-definition-UXKFOSKX-BrmxDlcc.js → kanban-definition-UXKFOSKX-DEUnNNgc.js} +1 -1
  36. package/dist/main.js +24 -24
  37. package/dist/{mermaid-DKI0869I.js → mermaid-B0fXpsNP.js} +34 -34
  38. package/dist/{mindmap-definition-YA3MSWOX-641xJysF.js → mindmap-definition-YA3MSWOX-CPexlhDQ.js} +1 -1
  39. package/dist/{number-overlay-editor-BExs_gHI.js → number-overlay-editor-DQ6KAcGy.js} +1 -1
  40. package/dist/{pegDiagram-XKGWAZYB-keVUAUeS.js → pegDiagram-XKGWAZYB-CMpp1OOQ.js} +2 -2
  41. package/dist/{pieDiagram-E7YTZNPT-DHktXPO_.js → pieDiagram-E7YTZNPT-DlMWg5t1.js} +1 -1
  42. package/dist/{process-output-C47Xs8_M.js → process-output-BKLVbFnU.js} +2 -2
  43. package/dist/{railroadDiagram-O6MQD6OU-BVEF4OV_.js → railroadDiagram-O6MQD6OU-DBJfseeO.js} +2 -2
  44. package/dist/{requirementDiagram-BXWQKSXE-CWo-UAKV.js → requirementDiagram-BXWQKSXE-BC7V639r.js} +1 -1
  45. package/dist/{reveal-component-DSxmD7Ft.js → reveal-component-DsWQWmYM.js} +6 -6
  46. package/dist/{sequenceDiagram-WJ2MYXX4-CHQvLnq4.js → sequenceDiagram-WJ2MYXX4-CgesU6F-.js} +1 -1
  47. package/dist/{spec-C-LRKGtT.js → spec-CcT0slVc.js} +1 -1
  48. package/dist/{stateDiagram-D77RDMKH-BOJZgbUJ.js → stateDiagram-D77RDMKH-lH_BUhs3.js} +2 -2
  49. package/dist/{stateDiagram-v2-MP3YSRHH-C53XXdYH.js → stateDiagram-v2-MP3YSRHH-8LL6FrBe.js} +2 -2
  50. package/dist/{swimlanes-42K2YHIH-D2o1gOtw.js → swimlanes-42K2YHIH-Cuf9B9kE.js} +1 -1
  51. package/dist/{swimlanesDiagram-VR7AAH4N-BHFzN1Wk.js → swimlanesDiagram-VR7AAH4N-BoMMtOPh.js} +2 -2
  52. package/dist/{timeline-definition-24CTP7MA-rv7QUa46.js → timeline-definition-24CTP7MA-yzzw4TF4.js} +1 -1
  53. package/dist/{toDate-C6BRw8i5.js → toDate-DKH0_kMo.js} +1 -1
  54. package/dist/{useAsyncData-ChgNtdmE.js → useAsyncData-D0mLJstx.js} +1 -1
  55. package/dist/{useDeepCompareMemoize-Bet7kSsI.js → useDeepCompareMemoize-BpPWi3G3.js} +1 -1
  56. package/dist/{useLifecycle-VVmz_24o.js → useLifecycle-Dum8WSXq.js} +1 -1
  57. package/dist/{useTheme-DympCsQg.js → useTheme-CfeaVw5D.js} +1 -1
  58. package/dist/{vega-component-B8IjMdad.js → vega-component-DgytCKrL.js} +5 -5
  59. package/dist/{vennDiagram-4TSXK5OY-D36Zk7t-.js → vennDiagram-4TSXK5OY-eJOrTqqC.js} +1 -1
  60. package/dist/{wardleyDiagram-VM6X3IG4-BLBolvyQ.js → wardleyDiagram-VM6X3IG4-TILPvee6.js} +1 -1
  61. package/dist/{xychartDiagram-S5SC5T6Z-CfH57l0v.js → xychartDiagram-S5SC5T6Z-DUPvRwwJ.js} +1 -1
  62. package/package.json +1 -1
  63. package/src/components/chat/acp/__tests__/state.test.ts +2 -2
  64. package/src/components/data-table/cell-selection/feature.ts +2 -2
  65. package/src/components/data-table/cell-selection/types.ts +2 -2
  66. package/src/components/data-table/column-summary/column-summary.tsx +2 -4
  67. package/src/components/datasources/__tests__/table-list.test.tsx +118 -0
  68. package/src/components/datasources/datasources.tsx +35 -21
  69. package/src/components/editor/SortableCell.tsx +1 -3
  70. package/src/components/editor/chrome/wrapper/footer-items/runtime-settings.tsx +1 -7
  71. package/src/components/editor/chrome/wrapper/storage.ts +1 -1
  72. package/src/components/editor/output/MarimoTracebackOutput.tsx +1 -1
  73. package/src/components/icons/spinner.tsx +1 -1
  74. package/src/components/ui/alert-dialog.tsx +1 -1
  75. package/src/core/ai/context/providers/datasource.ts +6 -2
  76. package/src/core/cells/session.ts +1 -1
  77. package/src/core/codemirror/__tests__/setup.test.ts +1 -1
  78. package/src/core/codemirror/keymaps/vim.ts +2 -6
  79. package/src/core/codemirror/language/languages/markdown.ts +1 -1
  80. package/src/core/codemirror/language/languages/python.ts +3 -3
  81. package/src/core/kernel/__tests__/RuntimeState.test.ts +1 -1
  82. package/src/core/state/__tests__/jotai.test.tsx +108 -0
  83. package/src/core/state/jotai.ts +1 -1
  84. package/src/core/wasm/bridge.ts +2 -2
  85. package/src/core/wasm/store.ts +3 -3
  86. package/src/core/wasm/worker/bootstrap.ts +1 -1
  87. package/src/core/wasm/worker/getMarimoWheel.ts +1 -1
  88. package/src/plugins/impl/CodeEditorPlugin.tsx +1 -1
  89. package/src/plugins/impl/RangeSliderPlugin.tsx +0 -1
  90. package/src/plugins/impl/data-explorer/ConnectedDataExplorerComponent.tsx +1 -1
  91. package/src/plugins/impl/data-explorer/state/reducer.ts +1 -1
  92. package/src/plugins/impl/data-frames/DataFramePlugin.tsx +0 -1
  93. package/src/plugins/impl/panel/PanelPlugin.tsx +1 -1
  94. package/src/plugins/impl/vega/make-selectable.ts +1 -1
  95. package/src/plugins/layout/RoutesPlugin.tsx +1 -1
  96. package/src/stories/form-wrapper.stories.tsx +2 -2
  97. package/src/stories/select.stories.tsx +2 -2
  98. package/src/stories/switchable-multi-select.stories.tsx +1 -1
  99. package/src/utils/__tests__/batch-requests.test.ts +3 -3
  100. package/src/utils/blob.ts +1 -1
  101. package/src/utils/zod-utils.ts +1 -3
  102. package/dist/assets/__vite-browser-external-BXYkiFsk.js +0 -1
  103. package/src/core/state/__tests__/jotai.test.ts +0 -25
@@ -30,7 +30,7 @@ export interface CellSelectionCell {
30
30
  }
31
31
 
32
32
  // These are additional properties add to the table instance in createTable
33
- export interface CellSelectionInstance<TData extends RowData> {
33
+ export interface CellSelectionInstance {
34
34
  setCellSelection?: (updater: Updater<CellSelectionState>) => void;
35
35
 
36
36
  resetCellSelection?(defaultValue?: CellSelectionState): void;
@@ -44,7 +44,7 @@ declare module "@tanstack/react-table" {
44
44
  TData extends RowData,
45
45
  > extends CellSelectionOptions {}
46
46
 
47
- interface Table<TData extends RowData> extends CellSelectionInstance<TData> {}
47
+ interface Table<TData extends RowData> extends CellSelectionInstance {}
48
48
 
49
49
  interface Cell<TData extends RowData, TValue> extends CellSelectionCell {}
50
50
  }
@@ -16,7 +16,7 @@ export const ColumnChartContext = React.createContext<
16
16
  ColumnChartSpecModel<unknown>
17
17
  >(ColumnChartSpecModel.EMPTY);
18
18
 
19
- interface Props<TData, TValue> {
19
+ interface Props {
20
20
  columnId: string;
21
21
  }
22
22
 
@@ -24,9 +24,7 @@ interface Props<TData, TValue> {
24
24
  // for all calls with the same key.
25
25
  const batchedLoader = createBatchedLoader();
26
26
 
27
- export const TableColumnSummary = <TData, TValue>({
28
- columnId,
29
- }: Props<TData, TValue>) => {
27
+ export const TableColumnSummary = ({ columnId }: Props) => {
30
28
  const { locale } = useLocale();
31
29
 
32
30
  const chartSpecModel = React.use(ColumnChartContext);
@@ -0,0 +1,118 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+
3
+ import { render, screen, waitFor } from "@testing-library/react";
4
+ import { Provider } from "jotai";
5
+ import type React from "react";
6
+ import { describe, expect, it } from "vitest";
7
+ import { MockRequestClient } from "@/__mocks__/requests";
8
+ import type { SQLTableContext } from "@/core/datasets/data-source-connections";
9
+ import { PreviewSQLTableList } from "@/core/datasets/request-registry";
10
+ import type { SQLTableListPreview } from "@/core/kernel/messages";
11
+ import type { RequestId } from "@/core/network/DeferredRequestRegistry";
12
+ import { requestClientAtom } from "@/core/network/requests";
13
+ import { store } from "@/core/state/jotai";
14
+ import { TableList } from "../datasources";
15
+
16
+ const wrapper = ({ children }: { children: React.ReactNode }) => (
17
+ <Provider store={store}>{children}</Provider>
18
+ );
19
+
20
+ const sqlTableContext: SQLTableContext = {
21
+ engine: "my_engine",
22
+ database: "my_db",
23
+ schema: "public",
24
+ dialect: "postgres",
25
+ schemaPath: ["public"],
26
+ };
27
+
28
+ // The kernel answers table-list requests over the websocket, so the mock
29
+ // settles the deferred request instead of returning the response directly.
30
+ function renderDeferredTableList(settle?: (requestId: RequestId) => void) {
31
+ const client = MockRequestClient.create();
32
+ client.previewSQLTableList.mockImplementation(async ({ requestId }) => {
33
+ settle?.(requestId);
34
+ return null;
35
+ });
36
+ store.set(requestClientAtom, client);
37
+ render(
38
+ <TableList
39
+ tables={[]}
40
+ tablesResolved={false}
41
+ sqlTableContext={sqlTableContext}
42
+ />,
43
+ { wrapper },
44
+ );
45
+ return client;
46
+ }
47
+
48
+ describe("TableList deferred discovery", () => {
49
+ it("shows the error when the request rejects", async () => {
50
+ renderDeferredTableList((requestId) =>
51
+ PreviewSQLTableList.reject(requestId, new Error("engine offline")),
52
+ );
53
+
54
+ expect(screen.getByText("Loading tables...")).toBeInTheDocument();
55
+
56
+ await waitFor(() => {
57
+ expect(screen.getByText("engine offline")).toBeInTheDocument();
58
+ });
59
+ expect(screen.queryByText("Loading tables...")).toBeNull();
60
+ });
61
+
62
+ it("shows the error when the response has no tables field", async () => {
63
+ renderDeferredTableList((requestId) =>
64
+ PreviewSQLTableList.resolve(requestId, {} as SQLTableListPreview),
65
+ );
66
+
67
+ await waitFor(() => {
68
+ expect(screen.getByText("No tables available")).toBeInTheDocument();
69
+ });
70
+ expect(screen.queryByText("Loading tables...")).toBeNull();
71
+ });
72
+
73
+ it("shows the empty state for an empty table list", async () => {
74
+ renderDeferredTableList((requestId) =>
75
+ PreviewSQLTableList.resolve(requestId, {
76
+ tables: [],
77
+ } as unknown as SQLTableListPreview),
78
+ );
79
+
80
+ await waitFor(() => {
81
+ expect(screen.getByText("No tables found")).toBeInTheDocument();
82
+ });
83
+ expect(screen.queryByText("Loading tables...")).toBeNull();
84
+ });
85
+
86
+ it("requests the table list with the table context and waits for the kernel", async () => {
87
+ let pendingRequestId: RequestId | undefined;
88
+ const client = renderDeferredTableList((requestId) => {
89
+ pendingRequestId = requestId;
90
+ });
91
+
92
+ await waitFor(() => {
93
+ expect(client.previewSQLTableList).toHaveBeenCalledTimes(1);
94
+ });
95
+ expect(client.previewSQLTableList).toHaveBeenCalledWith(
96
+ expect.objectContaining({
97
+ engine: "my_engine",
98
+ database: "my_db",
99
+ schema: "public",
100
+ schemaPath: ["public"],
101
+ }),
102
+ );
103
+ expect(screen.getByText("Loading tables...")).toBeInTheDocument();
104
+
105
+ // Settle the request so the process-wide registry does not keep the entry.
106
+ expect(pendingRequestId).toBeDefined();
107
+ PreviewSQLTableList.resolve(
108
+ pendingRequestId as RequestId,
109
+ {
110
+ tables: [],
111
+ } as unknown as SQLTableListPreview,
112
+ );
113
+ await waitFor(() => {
114
+ expect(screen.queryByText("Loading tables...")).toBeNull();
115
+ });
116
+ expect(PreviewSQLTableList.requests.size).toBe(0);
117
+ });
118
+ });
@@ -702,7 +702,40 @@ const SchemaNode: React.FC<SchemaNodeProps> = (props) => {
702
702
  );
703
703
  };
704
704
 
705
- const TableList: React.FC<{
705
+ // Kept outside the component because the React Compiler does not yet
706
+ // support try/finally inside component bodies.
707
+ async function loadTableList(
708
+ sqlTableContext: SQLTableContext,
709
+ addTableList: ReturnType<typeof useDataSourceActions>["addTableList"],
710
+ setLoading: (loading: boolean) => void,
711
+ ) {
712
+ setLoading(true);
713
+ try {
714
+ const { engine, database, schema, schemaPath } = sqlTableContext;
715
+ const previewTableList = await PreviewSQLTableList.request({
716
+ engine: engine,
717
+ database: database,
718
+ schema: schema,
719
+ schemaPath: schemaPath ?? [],
720
+ });
721
+
722
+ if (!previewTableList?.tables) {
723
+ throw new Error("No tables available");
724
+ }
725
+
726
+ addTableList({
727
+ tables: previewTableList.tables,
728
+ sqlTableContext: sqlTableContext,
729
+ });
730
+ } finally {
731
+ setLoading(false);
732
+ }
733
+ }
734
+
735
+ /**
736
+ * This is exported for testing purposes only.
737
+ */
738
+ export const TableList: React.FC<{
706
739
  tables: DataTable[];
707
740
  sqlTableContext?: SQLTableContext;
708
741
  searchValue?: string;
@@ -731,26 +764,7 @@ const TableList: React.FC<{
731
764
  // resets tablesResolved to false).
732
765
  const { isPending, error } = useAsyncData(async () => {
733
766
  if (!tablesResolved && sqlTableContext) {
734
- setTablesLoading(true);
735
-
736
- const { engine, database, schema, schemaPath } = sqlTableContext;
737
- const previewTableList = await PreviewSQLTableList.request({
738
- engine: engine,
739
- database: database,
740
- schema: schema,
741
- schemaPath: schemaPath ?? [],
742
- });
743
-
744
- if (!previewTableList?.tables) {
745
- setTablesLoading(false);
746
- throw new Error("No tables available");
747
- }
748
-
749
- addTableList({
750
- tables: previewTableList.tables,
751
- sqlTableContext: sqlTableContext,
752
- });
753
- setTablesLoading(false);
767
+ await loadTableList(sqlTableContext, addTableList, setTablesLoading);
754
768
  }
755
769
  }, [tablesResolved, sqlTableContext]);
756
770
 
@@ -71,7 +71,6 @@ export const SortableCell = React.forwardRef(
71
71
  return (
72
72
  <SortableCellInternal
73
73
  ref={ref}
74
- cellId={cellId}
75
74
  canMoveX={canMoveX}
76
75
  {...props}
77
76
  attributes={attributes}
@@ -86,7 +85,7 @@ export const SortableCell = React.forwardRef(
86
85
  );
87
86
  SortableCell.displayName = "SortableCell";
88
87
 
89
- interface SortableCellInternalProps extends Props {
88
+ interface SortableCellInternalProps extends Omit<Props, "cellId"> {
90
89
  children: React.ReactNode;
91
90
  attributes: DraggableAttributes;
92
91
  listeners: SyntheticListenerMap | undefined;
@@ -99,7 +98,6 @@ interface SortableCellInternalProps extends Props {
99
98
  const SortableCellInternal = React.forwardRef(
100
99
  (
101
100
  {
102
- cellId,
103
101
  canMoveX,
104
102
  children,
105
103
  attributes,
@@ -26,13 +26,7 @@ import { isWasm } from "@/core/wasm/utils";
26
26
  import { cn } from "@/utils/cn";
27
27
  import { FooterItem } from "../footer-item";
28
28
 
29
- interface RuntimeSettingsProps {
30
- className?: string;
31
- }
32
-
33
- export const RuntimeSettings: React.FC<RuntimeSettingsProps> = ({
34
- className,
35
- }) => {
29
+ export const RuntimeSettings: React.FC = () => {
36
30
  const { saveUserConfig } = useRequestClient();
37
31
  const [config, setUserConfig] = useResolvedMarimoConfig();
38
32
 
@@ -15,7 +15,7 @@ let storedValue: string | null = null;
15
15
  */
16
16
  export function createStorage(location: "left" | "bottom"): PanelGroupStorage {
17
17
  return {
18
- getItem(name) {
18
+ getItem(_name) {
19
19
  if (!storedValue) {
20
20
  return storedValue ?? null;
21
21
  }
@@ -274,7 +274,7 @@ export const replaceTracebackFilenames = (domNode: DOMNode) => {
274
274
  return (
275
275
  <div
276
276
  className="inline-block cursor-pointer text-destructive hover:underline"
277
- onClick={(_) => {
277
+ onClick={() => {
278
278
  getRequestClient().openFile({
279
279
  path: info.filePath,
280
280
  lineNumber: info.lineNumber,
@@ -25,7 +25,7 @@ const spinnerVariants = cva("animate-spin", {
25
25
  const Spinner = React.forwardRef<
26
26
  SVGSVGElement,
27
27
  React.SVGProps<SVGSVGElement> & VariantProps<typeof spinnerVariants>
28
- >(({ className, children, centered, size, ...props }, ref) => (
28
+ >(({ className, children: _children, centered, size, ...props }, ref) => (
29
29
  <Loader2Icon
30
30
  ref={ref}
31
31
  className={cn(spinnerVariants({ centered, size }), className)}
@@ -28,7 +28,7 @@ AlertDialogPortal.displayName = AlertDialogPrimitive.Portal.displayName;
28
28
  const AlertDialogOverlay = React.forwardRef<
29
29
  React.ElementRef<typeof AlertDialogPrimitive.Overlay>,
30
30
  React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Overlay>
31
- >(({ className, children, ...props }, ref) => (
31
+ >(({ className, children: _children, ...props }, ref) => (
32
32
  <AlertDialogPrimitive.Overlay
33
33
  className={cn(
34
34
  "fixed inset-0 z-50 bg-background/80 backdrop-blur-xs transition-opacity animate-in fade-in",
@@ -98,8 +98,12 @@ export class DatasourceContextProvider extends AIContextProvider<DatasourceConte
98
98
  public formatContext(item: DatasourceContextItem): string {
99
99
  const data = item.data;
100
100
  // Remove certain fields that are not needed in the context
101
- const { name, display_name, source, ...filteredDatasource } =
102
- data.connection;
101
+ const {
102
+ name,
103
+ display_name: _displayName,
104
+ source: _source,
105
+ ...filteredDatasource
106
+ } = data.connection;
103
107
 
104
108
  let datasource = filteredDatasource;
105
109
  const isInternalEngine = INTERNAL_SQL_ENGINES.has(name as ConnectionName);
@@ -56,7 +56,7 @@ function mergeSessionAndNotebookCells(
56
56
  return {
57
57
  cellIds,
58
58
  sessionCellData: new Map(
59
- cellIds.map((id, idx) => [id, null] as [CellId, SessionCell | null]),
59
+ cellIds.map((id) => [id, null] as [CellId, SessionCell | null]),
60
60
  ),
61
61
  notebookCellData: new Map(
62
62
  cellIds.map(
@@ -100,7 +100,7 @@ function getDuplicateKeymaps(state: EditorState) {
100
100
  (keymap) => keymap,
101
101
  );
102
102
  const duplicates = Objects.fromEntries(
103
- Object.entries(groupBy).filter(([key, value]) => value.length > 1),
103
+ Object.entries(groupBy).filter(([_key, value]) => value.length > 1),
104
104
  );
105
105
  return duplicates;
106
106
  }
@@ -369,7 +369,7 @@ class CodeMirrorVimSync {
369
369
  // We use onIdle to keep the focused editor snappy
370
370
  onIdle(() => {
371
371
  if (!skipBroadcast) {
372
- this.broadcastModeChange(instance, e.mode, e.subMode);
372
+ this.broadcastModeChange(instance, e.mode);
373
373
  }
374
374
  this.isBroadcasting = false;
375
375
  });
@@ -380,11 +380,7 @@ class CodeMirrorVimSync {
380
380
  this.instances.delete(instance);
381
381
  }
382
382
 
383
- public broadcastModeChange(
384
- originInstance: EditorView,
385
- mode: string,
386
- subMode?: string,
387
- ) {
383
+ public broadcastModeChange(originInstance: EditorView, mode: string) {
388
384
  invariant(
389
385
  "exitInsertMode" in Vim,
390
386
  "Vim does not have an exitInsertMode method",
@@ -73,7 +73,7 @@ export class MarkdownLanguageAdapter implements LanguageAdapter<MarkdownLanguage
73
73
  _cellId: CellId,
74
74
  _completionConfig: CompletionConfig,
75
75
  hotkeys: HotkeyProvider,
76
- _: PlaceholderType,
76
+ _placeholderType: PlaceholderType,
77
77
  ): Extension[] {
78
78
  const markdownLanguageData = markdown().language.data;
79
79
  let view: EditorView | undefined;
@@ -157,7 +157,7 @@ const pylspClient = once((lspConfig: LSPConfig) => {
157
157
  return notebookClient;
158
158
  });
159
159
 
160
- const tyLspClient = once((_: LSPConfig) => {
160
+ const tyLspClient = once((_config: LSPConfig) => {
161
161
  // oxlint-disable-next-line prefer-const -- reassigned after closure capture
162
162
  let resyncCallback: (() => Promise<void>) | undefined;
163
163
 
@@ -176,7 +176,7 @@ const tyLspClient = once((_: LSPConfig) => {
176
176
  const notebookClient = new NotebookLanguageServerClient(
177
177
  new LanguageServerClient({
178
178
  ...lspClientOpts,
179
- getWorkspaceConfiguration: (_) => [{ disableLanguageServices: true }],
179
+ getWorkspaceConfiguration: () => [{ disableLanguageServices: true }],
180
180
  }),
181
181
  {},
182
182
  );
@@ -226,7 +226,7 @@ const pyreflyClient = once(
226
226
  },
227
227
  );
228
228
 
229
- const pyrightClient = once((_: LSPConfig) => {
229
+ const pyrightClient = once((_config: LSPConfig) => {
230
230
  // oxlint-disable-next-line prefer-const -- reassigned after closure capture
231
231
  let resyncCallback: (() => Promise<void>) | undefined;
232
232
 
@@ -25,7 +25,7 @@ describe("RuntimeState", () => {
25
25
 
26
26
  beforeEach(() => {
27
27
  vi.clearAllMocks();
28
- mockSendComponentValues = vi.fn((args) => Promise.resolve(null));
28
+ mockSendComponentValues = vi.fn(() => Promise.resolve(null));
29
29
  uiElementRegistry = UIElementRegistry.INSTANCE;
30
30
  uiElementRegistry.entries.clear();
31
31
 
@@ -0,0 +1,108 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+
3
+ import { act, renderHook } from "@testing-library/react";
4
+ import { atom, Provider } from "jotai";
5
+ import type React from "react";
6
+ import { describe, expect, it, vi } from "vitest";
7
+ import { store, useJotaiEffect, waitFor } from "../jotai";
8
+
9
+ const wrapper = ({ children }: { children: React.ReactNode }) => (
10
+ <Provider store={store}>{children}</Provider>
11
+ );
12
+
13
+ describe("waitFor function", () => {
14
+ it("should resolve when the atom satisfies the predicate", async () => {
15
+ const testAtom = atom(0);
16
+ store.set(testAtom, 10);
17
+
18
+ const result = await waitFor(testAtom, (value) => value === 10);
19
+ expect(result).toBe(10);
20
+ });
21
+
22
+ it("should resolve when the atom changes to satisfy the predicate", async () => {
23
+ const testAtom = atom(0);
24
+
25
+ setTimeout(() => store.set(testAtom, 15), 5);
26
+ setTimeout(() => store.set(testAtom, 20), 10);
27
+
28
+ const result = await waitFor(testAtom, (value) => value === 20);
29
+ expect(result).toBe(20);
30
+ });
31
+ });
32
+
33
+ describe("useJotaiEffect", () => {
34
+ it("calls the effect with the new and previous values", () => {
35
+ const testAtom = atom(0);
36
+ const effect = vi.fn();
37
+ renderHook(() => useJotaiEffect(testAtom, effect), { wrapper });
38
+
39
+ act(() => store.set(testAtom, 1));
40
+ act(() => store.set(testAtom, 2));
41
+
42
+ expect(effect.mock.calls).toEqual([
43
+ [1, 0],
44
+ [2, 1],
45
+ ]);
46
+ });
47
+
48
+ it("calls only the latest callback after a rerender", () => {
49
+ const testAtom = atom(0);
50
+ const first = vi.fn();
51
+ const second = vi.fn();
52
+ const { rerender } = renderHook(
53
+ ({ effect }) => useJotaiEffect(testAtom, effect),
54
+ { initialProps: { effect: first }, wrapper },
55
+ );
56
+
57
+ rerender({ effect: second });
58
+ act(() => store.set(testAtom, 1));
59
+
60
+ expect(first).not.toHaveBeenCalled();
61
+ expect(second).toHaveBeenCalledTimes(1);
62
+ });
63
+
64
+ it("subscribes once per rerender with an inline callback", () => {
65
+ const testAtom = atom(0);
66
+ const calls: number[] = [];
67
+ const { rerender } = renderHook(
68
+ () => useJotaiEffect(testAtom, (value) => calls.push(value)),
69
+ { wrapper },
70
+ );
71
+
72
+ rerender();
73
+ rerender();
74
+ act(() => store.set(testAtom, 1));
75
+
76
+ expect(calls).toEqual([1]);
77
+ });
78
+
79
+ it("stops listening to the previous atom when the atom changes", () => {
80
+ const firstAtom = atom(0);
81
+ const secondAtom = atom(0);
82
+ const effect = vi.fn();
83
+ const { rerender } = renderHook(
84
+ ({ target }) => useJotaiEffect(target, effect),
85
+ { initialProps: { target: firstAtom }, wrapper },
86
+ );
87
+
88
+ rerender({ target: secondAtom });
89
+ act(() => store.set(firstAtom, 1));
90
+ expect(effect).not.toHaveBeenCalled();
91
+
92
+ act(() => store.set(secondAtom, 1));
93
+ expect(effect).toHaveBeenCalledTimes(1);
94
+ });
95
+
96
+ it("unsubscribes on unmount", () => {
97
+ const testAtom = atom(0);
98
+ const effect = vi.fn();
99
+ const { unmount } = renderHook(() => useJotaiEffect(testAtom, effect), {
100
+ wrapper,
101
+ });
102
+
103
+ unmount();
104
+ act(() => store.set(testAtom, 1));
105
+
106
+ expect(effect).not.toHaveBeenCalled();
107
+ });
108
+ });
@@ -42,7 +42,7 @@ export function useJotaiEffect<T>(
42
42
  const store = useStore();
43
43
  useEffect(() => {
44
44
  let prevValue = store.get(atom);
45
- store.sub(atom, () => {
45
+ return store.sub(atom, () => {
46
46
  const value = store.get(atom);
47
47
  effect(value, prevValue);
48
48
  prevValue = value;
@@ -407,7 +407,7 @@ export class PyodideBridge implements RunRequests, EditRequests {
407
407
  return response as Snippets;
408
408
  };
409
409
 
410
- public openFile: EditRequests["openFile"] = async ({ path }) => {
410
+ public openFile: EditRequests["openFile"] = async () => {
411
411
  const url = createShareableLink({
412
412
  code: null,
413
413
  baseUrl: window.location.origin,
@@ -451,7 +451,7 @@ export class PyodideBridge implements RunRequests, EditRequests {
451
451
  return null;
452
452
  };
453
453
 
454
- public sendInstantiate: RunRequests["sendInstantiate"] = async (request) => {
454
+ public sendInstantiate: RunRequests["sendInstantiate"] = async () => {
455
455
  return null;
456
456
  };
457
457
 
@@ -41,7 +41,7 @@ const urlFileStore: FileStore = {
41
41
  };
42
42
 
43
43
  export const domElementFileStore: FileStore = {
44
- saveFile(contents: string) {
44
+ saveFile(_contents: string) {
45
45
  // Do nothing
46
46
  },
47
47
  readFile() {
@@ -54,7 +54,7 @@ export const domElementFileStore: FileStore = {
54
54
  };
55
55
 
56
56
  const remoteDefaultFileStore: FileStore = {
57
- saveFile(contents: string) {
57
+ saveFile(_contents: string) {
58
58
  // Do nothing
59
59
  },
60
60
  readFile() {
@@ -79,7 +79,7 @@ export const mountConfigFileStore: FileStore = {
79
79
  };
80
80
 
81
81
  const emptyFileStore: FileStore = {
82
- saveFile(contents: string) {
82
+ saveFile(_contents: string) {
83
83
  // Do nothing
84
84
  },
85
85
  readFile() {
@@ -85,7 +85,7 @@ export class DefaultWasmController implements WasmController {
85
85
  packages: [
86
86
  "micropip",
87
87
  "msgspec",
88
- getMarimoWheel(opts.version),
88
+ getMarimoWheel(),
89
89
  "Markdown",
90
90
  "pymdown-extensions",
91
91
  "narwhals",
@@ -1,6 +1,6 @@
1
1
  /* Copyright 2026 Marimo. All rights reserved. */
2
2
 
3
- export function getMarimoWheel(version: string) {
3
+ export function getMarimoWheel() {
4
4
  if (import.meta.env.DEV && import.meta.env.VITE_WASM_MARIMO_PREBUILT_WHEEL) {
5
5
  return "marimo-base";
6
6
  }
@@ -71,7 +71,7 @@ const CodeEditorComponent = (props: CodeEditorComponentProps) => {
71
71
  });
72
72
 
73
73
  const handleChange = useEvent((newValue: string) => {
74
- setLocalValue((_) => newValue);
74
+ setLocalValue(newValue);
75
75
  if (typeof props.debounce === "number") {
76
76
  setValueDebounced(newValue);
77
77
  } else if (!props.debounce) {
@@ -74,7 +74,6 @@ const RangeSliderComponent = ({
74
74
  start,
75
75
  stop,
76
76
  step,
77
- steps,
78
77
  debounce,
79
78
  orientation,
80
79
  showValue,
@@ -47,7 +47,7 @@ const ConnectedDataExplorerComponent = (props: DataTableProps): JSX.Element => {
47
47
  // Subscribe to the store
48
48
  const unsub = store.sub(chartSpecAtom, () => {
49
49
  const value = store.get(chartSpecAtom);
50
- const { schema, ...withoutSchema } = value;
50
+ const { schema: _schema, ...withoutSchema } = value;
51
51
  props.setValue(withoutSchema);
52
52
  });
53
53
 
@@ -47,7 +47,7 @@ const { valueAtom: chartSpecAtom, useActions } = createReducerAndAtoms(
47
47
  },
48
48
  set: (state, next: ChartSpec) => {
49
49
  // remove schema
50
- const { schema, ...rest } = next;
50
+ const { schema: _schema, ...rest } = next;
51
51
  return { ...state, ...rest };
52
52
  },
53
53
  },
@@ -192,7 +192,6 @@ const EMPTY: Transformations = {
192
192
  export const DataFrameComponent = memo(
193
193
  ({
194
194
  columns,
195
- dataframeName,
196
195
  pageSize,
197
196
  showDownload,
198
197
  lazy,
@@ -78,7 +78,7 @@ type T = Record<string, unknown>;
78
78
 
79
79
  // oxlint-disable-next-line typescript/consistent-type-definitions
80
80
  type PluginFunctions = {
81
- send_to_widget: <T>(req: {
81
+ send_to_widget: (req: {
82
82
  message?: unknown;
83
83
  buffers?: unknown;
84
84
  }) => Promise<null | undefined>;
@@ -219,7 +219,7 @@ function removeSpecificParamsFromNestedSpecs(
219
219
  }
220
220
 
221
221
  if (filteredParams.length === 0) {
222
- const { params, ...rest } = spec;
222
+ const { params: _params, ...rest } = spec;
223
223
  return rest as SpecWithParams;
224
224
  }
225
225