@marimo-team/frontend 0.25.3-dev105 → 0.25.3-dev108

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 (109) hide show
  1. package/dist/assets/{CellStatus-DVrvejMC.js → CellStatus-_Aw9_foI.js} +1 -1
  2. package/dist/assets/{ConnectedDataExplorerComponent-Dft8R6l5.js → ConnectedDataExplorerComponent-BRgqbAsc.js} +1 -1
  3. package/dist/assets/{ErrorBoundary-0akNKvJm.js → ErrorBoundary-DxSzjvbx.js} +2 -2
  4. package/dist/assets/{JsonOutput-DeUmpc_f.js → JsonOutput-B7pGj79O.js} +2 -2
  5. package/dist/assets/{MarimoErrorOutput-BazxeAYV.js → MarimoErrorOutput-Bzo6Bkp5.js} +1 -1
  6. package/dist/assets/{RenderHTML-CbAdeual.js → RenderHTML-D6AC0IhC.js} +1 -1
  7. package/dist/assets/{RunButton-CjKwgCFU.js → RunButton-BjYsPiKa.js} +1 -1
  8. package/dist/assets/{add-cell-with-ai-BKVEws3J.js → add-cell-with-ai-DlpS2-wQ.js} +4 -4
  9. package/dist/assets/{add-connection-dialog-DS7LTz1T.js → add-connection-dialog-CyNAB_W2.js} +1 -1
  10. package/dist/assets/{agent-panel-B34QcdOK.js → agent-panel-_lzeDJ1g.js} +1 -1
  11. package/dist/assets/{ai-model-dropdown-DqFJCFVR.js → ai-model-dropdown-DSTTUxpi.js} +1 -1
  12. package/dist/assets/{app-config-button-CVh4UKi9.js → app-config-button-ChYKLsLO.js} +1 -1
  13. package/dist/assets/{blob-Bw0ZQoLx.js → blob-DTYOX-m5.js} +1 -1
  14. package/dist/assets/{cell-editor-DQ0io5Ye.js → cell-editor-Beg8ahPA.js} +2 -2
  15. package/dist/assets/{cell-link-DvOIu6A3.js → cell-link-D3rkqgbU.js} +1 -1
  16. package/dist/assets/{cells-Do-63MKT.js → cells-2HETnlvE.js} +5 -5
  17. package/dist/assets/{chat-display-DnkhBxdh.js → chat-display-0GIEXLvN.js} +1 -1
  18. package/dist/assets/{chat-panel-CN3qoX9G.js → chat-panel-BuWn4Z_F.js} +1 -1
  19. package/dist/assets/{chat-ui-B54K5qqr.js → chat-ui-X0s3vvdf.js} +1 -1
  20. package/dist/assets/{column-preview-DmxzHzaW.js → column-preview-yI7-INkZ.js} +1 -1
  21. package/dist/assets/{command-palette-CciCeRhy.js → command-palette-D6WLW1Ie.js} +1 -1
  22. package/dist/assets/{common-BCyx7t1i.js → common-DgXKwblm.js} +1 -1
  23. package/dist/assets/{components-CDvWXfXq.js → components-CuMhY4Sf.js} +1 -1
  24. package/dist/assets/{datasource-DieftweT.js → datasource-D9JMcEAP.js} +1 -1
  25. package/dist/assets/{dependency-graph-panel-IfcqTHgq.js → dependency-graph-panel-BVJWPCvw.js} +3 -3
  26. package/dist/assets/{documentation-panel-DeLCgVcq.js → documentation-panel--wI4XHso.js} +1 -1
  27. package/dist/assets/{download-BzfVCqUq.js → download-Dod42wfD.js} +1 -1
  28. package/dist/assets/{edit-page-HMyk4cWw.js → edit-page-EmacXgdB.js} +4 -4
  29. package/dist/assets/{error-entries-DjjWrbt4.js → error-entries-D00nWhWE.js} +1 -1
  30. package/dist/assets/{error-panel-CiG3JJA3.js → error-panel-CcN_1PWf.js} +1 -1
  31. package/dist/assets/{feedback-button-B8zVQEi6.js → feedback-button-ByGgqUsX.js} +1 -1
  32. package/dist/assets/feedback-button-tbWwJ_t6.js +1 -0
  33. package/dist/assets/{file-explorer-panel-BPD8Q0L-.js → file-explorer-panel-eTEqtMPW.js} +1 -1
  34. package/dist/assets/{file-name-input-CdkF9K3I.js → file-name-input-D5SfTNWh.js} +1 -1
  35. package/dist/assets/{floating-outline-TfDWJXBm.js → floating-outline-B-k3P_AO.js} +1 -1
  36. package/dist/assets/{gallery-page-BJpenPdQ.js → gallery-page-BzbMhDHi.js} +1 -1
  37. package/dist/assets/{glide-data-editor-DO15Nm-R.js → glide-data-editor-BMqhUrHI.js} +1 -1
  38. package/dist/assets/{home-page-mvqldGvD.js → home-page-BvA1BCmX.js} +1 -1
  39. package/dist/assets/{hooks-C4AJfqV0.js → hooks-CaRBg2ni.js} +1 -1
  40. package/dist/assets/{index-C2XzUsS7.js → index-hwaZTQ9l.js} +9 -9
  41. package/dist/assets/{layout-1kSh6fBF.js → layout-C-NAerIZ.js} +3 -3
  42. package/dist/assets/{logs-panel--QbHo7uS.js → logs-panel-B3T1GFBk.js} +1 -1
  43. package/dist/assets/{markdown-renderer-DJ7H8Rps.js → markdown-renderer-BhiTvxtL.js} +1 -1
  44. package/dist/assets/{name-cell-input-6Gd19S8T.js → name-cell-input-CgAObR6N.js} +1 -1
  45. package/dist/assets/{outline-panel-CjKvunvX.js → outline-panel-BhBb7Vqw.js} +1 -1
  46. package/dist/assets/{packages-panel-Gyrqi-MZ.js → packages-panel-Dz8rOIrh.js} +1 -1
  47. package/dist/assets/{panel-accordion-state-D49TNCLQ.js → panel-accordion-state-ZsUdpAYn.js} +1 -1
  48. package/dist/assets/{panels-DfbxoeGa.js → panels-B0lqLcI0.js} +1 -1
  49. package/dist/assets/{process-output-CgkoeA1-.js → process-output-CeOMwE17.js} +1 -1
  50. package/dist/assets/{readonly-code-display-BMbl_4BR.js → readonly-code-display-TvOC7ESE.js} +1 -1
  51. package/dist/assets/{readonly-python-code-BnPfpOJM.js → readonly-python-code-Cqkfdeyo.js} +1 -1
  52. package/dist/assets/{reveal-component-D84yAzG7.js → reveal-component-y06O6YuH.js} +1 -1
  53. package/dist/assets/{run-page-B10s8Pre.js → run-page-DHE6ET5K.js} +1 -1
  54. package/dist/assets/{runtime-details-state-Bkmgb59p.js → runtime-details-state-CKqJSfSR.js} +1 -1
  55. package/dist/assets/{scratchpad-panel-DtyjJ3yN.js → scratchpad-panel-BW_HfG5l.js} +1 -1
  56. package/dist/assets/{session-panel-Chaq1do8.js → session-panel-CdHdKUCF.js} +1 -1
  57. package/dist/assets/{snippets-panel-BaZmddOW.js → snippets-panel-DYLWOLYw.js} +1 -1
  58. package/dist/assets/{spec-uCpVvaAR.js → spec-DlH4Mp7d.js} +1 -1
  59. package/dist/assets/{state-Db73TZVD.js → state-DAqdiGqq.js} +1 -1
  60. package/dist/assets/{state-CqnCwHhY.js → state-DZN9vZ6z.js} +1 -1
  61. package/dist/assets/{tracing-C2vQMUI0.js → tracing-B0J2ctTm.js} +1 -1
  62. package/dist/assets/{tracing-panel-B4L2S2ac.js → tracing-panel-C1OCpoeE.js} +2 -2
  63. package/dist/assets/{tree-actions-JXDiioHK.js → tree-actions-sjBxIglc.js} +1 -1
  64. package/dist/assets/{useAddCell-CP3HIjGl.js → useAddCell-CaXnFGki.js} +1 -1
  65. package/dist/assets/{useCellActionButton-yi1_-emd.js → useCellActionButton-CJ90CMEj.js} +1 -1
  66. package/dist/assets/{useDeleteCell-BPLwyClM.js → useDeleteCell-D9N85822.js} +1 -1
  67. package/dist/assets/{useDependencyPanelTab-DmBiydXO.js → useDependencyPanelTab-CzAE0cm-.js} +1 -1
  68. package/dist/assets/{useNotebookActions-CfmgcJGx.js → useNotebookActions-CTKkktJB.js} +1 -1
  69. package/dist/assets/{useRunCells-jF-7tJkw.js → useRunCells-DGakx8hj.js} +1 -1
  70. package/dist/assets/{useSplitCell-B6zWL8Zf.js → useSplitCell-AK5BKPGM.js} +1 -1
  71. package/dist/index.html +21 -21
  72. package/package.json +2 -2
  73. package/src/components/data-table/__tests__/chart-spec-model.test.ts +195 -195
  74. package/src/components/data-table/charts/__tests__/spec-snapshot.test.ts +20 -20
  75. package/src/components/data-table/charts/__tests__/spec.test.ts +78 -52
  76. package/src/components/data-table/charts/chart-spec/spec.ts +68 -43
  77. package/src/components/data-table/charts/charts.tsx +5 -5
  78. package/src/components/data-table/column-summary/chart-spec-model.tsx +23 -16
  79. package/src/components/dependency-graph/dependency-graph-tree.tsx +8 -8
  80. package/src/components/dependency-graph/elements.ts +24 -21
  81. package/src/components/dependency-graph/utils/__tests__/layout.test.ts +18 -11
  82. package/src/components/editor/output/ansi-reduce.ts +30 -23
  83. package/src/components/editor/renderers/grid-layout/grid-layout.tsx +12 -6
  84. package/src/core/ai/tools/base.ts +16 -7
  85. package/src/core/ai/tools/edit-notebook-tool.ts +47 -45
  86. package/src/core/ai/tools/registry.ts +13 -8
  87. package/src/core/ai/tools/sample-tool.ts +6 -7
  88. package/src/core/cells/__tests__/session.test.ts +117 -36
  89. package/src/core/codemirror/__tests__/setup.test.ts +28 -28
  90. package/src/core/codemirror/language/__tests__/sql.test.ts +22 -20
  91. package/src/core/codemirror/language/extension.ts +8 -8
  92. package/src/core/codemirror/language/languages/markdown.ts +8 -6
  93. package/src/core/codemirror/language/languages/python.ts +13 -7
  94. package/src/core/codemirror/language/languages/sql/sql.ts +9 -7
  95. package/src/core/codemirror/language/types.ts +7 -7
  96. package/src/core/codemirror/rtc/extension.ts +24 -12
  97. package/src/core/codemirror/rtc/loro/awareness.ts +34 -19
  98. package/src/core/codemirror/rtc/loro/sync.ts +1 -0
  99. package/src/core/codemirror/utils.ts +1 -0
  100. package/src/core/islands/components/__tests__/IslandControls.test.tsx +7 -5
  101. package/src/core/static/__tests__/files.test.ts +38 -10
  102. package/src/plugins/__tests__/plugin-object-schemas.test.ts +104 -0
  103. package/src/plugins/impl/DataEditorPlugin.tsx +1 -1
  104. package/src/plugins/impl/DataTablePlugin.tsx +15 -18
  105. package/src/plugins/impl/data-frames/DataFramePlugin.tsx +1 -1
  106. package/src/plugins/impl/plotly/PlotlyPlugin.tsx +2 -5
  107. package/src/plugins/impl/plotly/selection.ts +25 -19
  108. package/src/plugins/impl/vega/VegaPlugin.tsx +2 -3
  109. package/dist/assets/feedback-button-DQdMkSvR.js +0 -1
@@ -42,12 +42,14 @@ const adapter = new SQLLanguageAdapter();
42
42
  const TEST_ENGINE = "test_engine" as ConnectionName;
43
43
 
44
44
  const TEST_EXTENSION_ARGS = [
45
- {} as CellId,
46
- {} as CompletionConfig,
47
- {} as HotkeyProvider,
48
- {} as PlaceholderType,
49
- {} as LSPConfig & {
50
- diagnostics: DiagnosticsConfig;
45
+ {
46
+ cellId: {} as CellId,
47
+ completionConfig: {} as CompletionConfig,
48
+ hotkeys: {} as HotkeyProvider,
49
+ placeholderType: {} as PlaceholderType,
50
+ lspConfig: {} as LSPConfig & {
51
+ diagnostics: DiagnosticsConfig;
52
+ },
51
53
  },
52
54
  ] as const;
53
55
 
@@ -930,15 +932,15 @@ describe("SQL analysis features", () => {
930
932
 
931
933
  it("reports semantic diagnostics as warnings", async () => {
932
934
  const adapter = new SQLLanguageAdapter();
933
- const extensions = adapter.getExtension(
934
- {} as CellId,
935
- {} as CompletionConfig,
936
- {} as HotkeyProvider,
937
- {} as PlaceholderType,
938
- {
935
+ const extensions = adapter.getExtension({
936
+ cellId: {} as CellId,
937
+ completionConfig: {} as CompletionConfig,
938
+ hotkeys: {} as HotkeyProvider,
939
+ placeholderType: {} as PlaceholderType,
940
+ lspConfig: {
939
941
  diagnostics: { sql_linter: true },
940
942
  } as LSPConfig & { diagnostics: DiagnosticsConfig },
941
- );
943
+ });
942
944
  const state = EditorState.create({
943
945
  doc: "SELECT missing FROM users",
944
946
  extensions: [languageMetadataField.init(() => metadata), ...extensions],
@@ -963,15 +965,15 @@ describe("SQL analysis features", () => {
963
965
 
964
966
  it("does not report unknown tables", async () => {
965
967
  const adapter = new SQLLanguageAdapter();
966
- const extensions = adapter.getExtension(
967
- {} as CellId,
968
- {} as CompletionConfig,
969
- {} as HotkeyProvider,
970
- {} as PlaceholderType,
971
- {
968
+ const extensions = adapter.getExtension({
969
+ cellId: {} as CellId,
970
+ completionConfig: {} as CompletionConfig,
971
+ hotkeys: {} as HotkeyProvider,
972
+ placeholderType: {} as PlaceholderType,
973
+ lspConfig: {
972
974
  diagnostics: { sql_linter: true },
973
975
  } as LSPConfig & { diagnostics: DiagnosticsConfig },
974
- );
976
+ });
975
977
  const state = EditorState.create({
976
978
  doc: "SELECT missing FROM users; SELECT * FROM unknown_table",
977
979
  extensions: [languageMetadataField.init(() => metadata), ...extensions],
@@ -167,13 +167,13 @@ function updateLanguageAdapterAndCode({
167
167
  setLanguageAdapter.of(nextLanguage),
168
168
  setLanguageMetadata.of(metadata),
169
169
  languageCompartment.reconfigure(
170
- nextLanguage.getExtension(
170
+ nextLanguage.getExtension({
171
171
  cellId,
172
172
  completionConfig,
173
- hotkeysProvider,
173
+ hotkeys: hotkeysProvider,
174
174
  placeholderType,
175
175
  lspConfig,
176
- ),
176
+ }),
177
177
  ),
178
178
  // Clear history
179
179
  historyCompartment.reconfigure([]),
@@ -218,13 +218,13 @@ export function adaptiveLanguageConfiguration(opts: {
218
218
  // Language adapter
219
219
  languageToggleKeymaps(),
220
220
  languageCompartment.of(
221
- LanguageAdapters.python.getExtension(
221
+ LanguageAdapters.python.getExtension({
222
222
  cellId,
223
223
  completionConfig,
224
224
  hotkeys,
225
225
  placeholderType,
226
226
  lspConfig,
227
- ),
227
+ }),
228
228
  ),
229
229
  languageAdapterState,
230
230
  languageMetadataField,
@@ -323,12 +323,12 @@ export function reconfigureLanguageEffect(
323
323
  }
324
324
 
325
325
  return languageCompartment.reconfigure(
326
- language.getExtension(
326
+ language.getExtension({
327
327
  cellId,
328
328
  completionConfig,
329
- hotkeysProvider,
329
+ hotkeys: hotkeysProvider,
330
330
  placeholderType,
331
331
  lspConfig,
332
- ),
332
+ }),
333
333
  );
334
334
  }
@@ -69,12 +69,14 @@ export class MarkdownLanguageAdapter implements LanguageAdapter<MarkdownLanguage
69
69
  return this.parser.isSupported(pythonCode);
70
70
  }
71
71
 
72
- public getExtension(
73
- _cellId: CellId,
74
- _completionConfig: CompletionConfig,
75
- hotkeys: HotkeyProvider,
76
- _placeholderType: PlaceholderType,
77
- ): Extension[] {
72
+ public getExtension({
73
+ hotkeys,
74
+ }: {
75
+ cellId: CellId;
76
+ completionConfig: CompletionConfig;
77
+ hotkeys: HotkeyProvider;
78
+ placeholderType: PlaceholderType;
79
+ }): Extension[] {
78
80
  const markdownLanguageData = markdown().language.data;
79
81
  let view: EditorView | undefined;
80
82
 
@@ -279,13 +279,19 @@ export class PythonLanguageAdapter implements LanguageAdapter<{}> {
279
279
  return this.parser.isSupported(code);
280
280
  }
281
281
 
282
- public getExtension(
283
- cellId: CellId,
284
- completionConfig: CompletionConfig,
285
- hotkeys: HotkeyProvider,
286
- placeholderType: PlaceholderType,
287
- lspConfig: LSPConfig & { diagnostics: DiagnosticsConfig },
288
- ): Extension[] {
282
+ public getExtension({
283
+ cellId,
284
+ completionConfig,
285
+ hotkeys,
286
+ placeholderType,
287
+ lspConfig,
288
+ }: {
289
+ cellId: CellId;
290
+ completionConfig: CompletionConfig;
291
+ hotkeys: HotkeyProvider;
292
+ placeholderType: PlaceholderType;
293
+ lspConfig: LSPConfig & { diagnostics: DiagnosticsConfig };
294
+ }): Extension[] {
289
295
  const getCompletionsExtension = () => {
290
296
  const autocompleteOptions = {
291
297
  // We remove the default keymap because we use our own which
@@ -155,13 +155,15 @@ export class SQLLanguageAdapter implements LanguageAdapter<SQLLanguageAdapterMet
155
155
  return this.parser.isSupported(pythonCode);
156
156
  }
157
157
 
158
- public getExtension(
159
- _cellId: CellId,
160
- _completionConfig: CompletionConfig,
161
- _hotkeys: HotkeyProvider,
162
- _placeholderType: PlaceholderType,
163
- lspConfig: LSPConfig & { diagnostics: DiagnosticsConfig },
164
- ): Extension[] {
158
+ public getExtension({
159
+ lspConfig,
160
+ }: {
161
+ cellId: CellId;
162
+ completionConfig: CompletionConfig;
163
+ hotkeys: HotkeyProvider;
164
+ placeholderType: PlaceholderType;
165
+ lspConfig: LSPConfig & { diagnostics: DiagnosticsConfig };
166
+ }): Extension[] {
165
167
  const analysis = createSQLAnalysis();
166
168
  const extensions = [
167
169
  // This can be updated with a dispatch effect
@@ -26,13 +26,13 @@ export interface LanguageAdapter<M = Record<string, any>> {
26
26
  transformIn(code: string): [string, number, M];
27
27
  transformOut(code: string, metadata: M): [string, number];
28
28
  isSupported(code: string): boolean;
29
- getExtension(
30
- cellId: CellId,
31
- completionConfig: CompletionConfig,
32
- hotkeys: HotkeyProvider,
33
- placeholderType: PlaceholderType,
34
- lspConfig: LSPConfig & { diagnostics?: DiagnosticsConfig },
35
- ): Extension[];
29
+ getExtension(options: {
30
+ cellId: CellId;
31
+ completionConfig: CompletionConfig;
32
+ hotkeys: HotkeyProvider;
33
+ placeholderType: PlaceholderType;
34
+ lspConfig: LSPConfig & { diagnostics?: DiagnosticsConfig };
35
+ }): Extension[];
36
36
  }
37
37
 
38
38
  export type LanguageMetadataOf<T extends LanguageAdapter> =
@@ -207,7 +207,13 @@ export function realTimeCollaboration(
207
207
  extension: [
208
208
  languageObserverExtension(cellId),
209
209
  languageListenerExtension(cellId),
210
- loroAwarenessPlugin(doc, awareness, userState, () => loroText, cellId),
210
+ loroAwarenessPlugin({
211
+ doc,
212
+ awareness,
213
+ user: userState,
214
+ getTextFromDoc: () => loroText,
215
+ cellId,
216
+ }),
211
217
  loroSyncPlugin(doc, ["codes", cellId], () => {
212
218
  return loroText;
213
219
  }),
@@ -419,14 +425,21 @@ function languageListenerExtension(cellId: CellId) {
419
425
  /**
420
426
  * Create a plugin to observe awareness changes
421
427
  */
422
- function loroAwarenessPlugin(
423
- doc: LoroDoc,
424
- awareness: Awareness<AwarenessState>,
425
- user: UserState,
426
- getTextFromDoc: (doc: LoroDoc) => LoroText,
427
- cellId: CellId,
428
- getUserId?: () => Uid,
429
- ): Extension[] {
428
+ function loroAwarenessPlugin({
429
+ doc,
430
+ awareness,
431
+ user,
432
+ getTextFromDoc,
433
+ cellId,
434
+ getUserId,
435
+ }: {
436
+ doc: LoroDoc;
437
+ awareness: Awareness<AwarenessState>;
438
+ user: UserState;
439
+ getTextFromDoc: (doc: LoroDoc) => LoroText;
440
+ cellId: CellId;
441
+ getUserId?: () => Uid;
442
+ }): Extension[] {
430
443
  const scopeId = `loro:cell:${cellId}` as ScopeId;
431
444
 
432
445
  return [
@@ -435,15 +448,14 @@ function loroAwarenessPlugin(
435
448
  createSelectionLayer(),
436
449
  ViewPlugin.define(
437
450
  (view) =>
438
- new AwarenessPlugin(
439
- view,
451
+ new AwarenessPlugin(view, {
440
452
  doc,
441
453
  user,
442
454
  awareness,
443
455
  getTextFromDoc,
444
456
  scopeId,
445
457
  getUserId,
446
- ),
458
+ }),
447
459
  ),
448
460
  ViewPlugin.define(
449
461
  (view) => new RemoteAwarenessPlugin(view, doc, awareness, scopeId),
@@ -208,13 +208,19 @@ export class RemoteCursorMarker implements LayerMarker {
208
208
  private name: string;
209
209
  private colorClassName: string;
210
210
 
211
- public constructor(
212
- left: number,
213
- top: number,
214
- height: number,
215
- name: string,
216
- colorClassName: string,
217
- ) {
211
+ public constructor({
212
+ left,
213
+ top,
214
+ height,
215
+ name,
216
+ colorClassName,
217
+ }: {
218
+ left: number;
219
+ top: number;
220
+ height: number;
221
+ name: string;
222
+ colorClassName: string;
223
+ }) {
218
224
  this.left = left;
219
225
  this.top = top;
220
226
  this.height = height;
@@ -271,13 +277,13 @@ export class RemoteCursorMarker implements LayerMarker {
271
277
  const baseLeft = left - view.scrollDOM.scrollLeft;
272
278
  const baseTop = rect.top - view.scrollDOM.scrollTop;
273
279
  return [
274
- new RemoteCursorMarker(
275
- absolutePosition.left - baseLeft,
276
- absolutePosition.top - baseTop,
277
- absolutePosition.bottom - absolutePosition.top,
278
- displayName,
280
+ new RemoteCursorMarker({
281
+ left: absolutePosition.left - baseLeft,
282
+ top: absolutePosition.top - baseTop,
283
+ height: absolutePosition.bottom - absolutePosition.top,
284
+ name: displayName,
279
285
  colorClassName,
280
- ),
286
+ }),
281
287
  ];
282
288
  }
283
289
 
@@ -375,12 +381,21 @@ export class AwarenessPlugin implements PluginValue {
375
381
 
376
382
  public constructor(
377
383
  view: EditorView,
378
- doc: LoroDoc,
379
- user: UserState,
380
- awareness: Awareness<AwarenessState>,
381
- getTextFromDoc: (doc: LoroDoc) => LoroText,
382
- scopeId: ScopeId,
383
- getUserId?: () => Uid,
384
+ {
385
+ doc,
386
+ user,
387
+ awareness,
388
+ getTextFromDoc,
389
+ scopeId,
390
+ getUserId,
391
+ }: {
392
+ doc: LoroDoc;
393
+ user: UserState;
394
+ awareness: Awareness<AwarenessState>;
395
+ getTextFromDoc: (doc: LoroDoc) => LoroText;
396
+ scopeId: ScopeId;
397
+ getUserId?: () => Uid;
398
+ },
384
399
  ) {
385
400
  this.view = view;
386
401
  this.doc = doc;
@@ -148,6 +148,7 @@ export class LoroSyncPluginValue implements PluginValue {
148
148
  return;
149
149
  }
150
150
  let adj = 0;
151
+ // oxlint-disable-next-line marimo/prefer-object-params -- CodeMirror supplies positional change ranges and inserted text.
151
152
  update.changes.iterChanges((fromA, toA, fromB, toB, insert) => {
152
153
  const insertText = insert.sliceString(0, insert.length, "\n");
153
154
  if (fromA !== toA) {
@@ -122,6 +122,7 @@ export function hasNewLines(tr: Transaction | ViewUpdate): boolean {
122
122
 
123
123
  let hasNewLines = false;
124
124
  // Iterate through all changes in the transaction
125
+ // oxlint-disable-next-line marimo/prefer-object-params -- CodeMirror supplies positional change ranges and inserted text.
125
126
  tr.changes.iterChanges((fromA, toA, fromB, toB, inserted) => {
126
127
  // Check if the inserted text contains line breaks
127
128
  if (inserted.toString().includes("\n")) {
@@ -2,6 +2,7 @@
2
2
 
3
3
  import { fireEvent, render, screen } from "@testing-library/react";
4
4
  import { beforeEach, describe, expect, it, vi } from "vitest";
5
+ import { MockRequestClient } from "@/__mocks__/requests";
5
6
  import { TooltipProvider } from "@/components/ui/tooltip";
6
7
  import type { CellId } from "@/core/cells/ids";
7
8
  import * as requestsModule from "@/core/network/requests";
@@ -18,7 +19,8 @@ vi.mock("@/utils/copy", () => ({
18
19
  }));
19
20
 
20
21
  describe("IslandControls", () => {
21
- const mockSendRun = vi.fn();
22
+ const mockSendRun =
23
+ vi.fn<ReturnType<typeof requestsModule.useRequestClient>["sendRun"]>();
22
24
  const mockCopyToClipboard = vi.fn();
23
25
  const mockCodeCallback = vi.fn(() => "print('test code')");
24
26
  const cellId = "test-cell-id" as CellId;
@@ -30,11 +32,11 @@ describe("IslandControls", () => {
30
32
 
31
33
  beforeEach(() => {
32
34
  vi.clearAllMocks();
33
- mockSendRun.mockResolvedValue(undefined);
35
+ mockSendRun.mockResolvedValue(null);
34
36
 
35
- vi.spyOn(requestsModule, "useRequestClient").mockReturnValue({
36
- sendRun: mockSendRun,
37
- } as any);
37
+ vi.spyOn(requestsModule, "useRequestClient").mockReturnValue(
38
+ MockRequestClient.create({ sendRun: mockSendRun }),
39
+ );
38
40
 
39
41
  vi.spyOn(copyModule, "copyToClipboard").mockImplementation(
40
42
  mockCopyToClipboard,
@@ -654,19 +654,47 @@ describe("error handling and edge cases", () => {
654
654
  unpatch();
655
655
  });
656
656
 
657
- it("should handle malformed URLs gracefully", async () => {
658
- const virtualFiles = {};
659
- const unpatch = patchFetch(virtualFiles);
657
+ it("forwards relative URLs and options to the original fetch", async () => {
658
+ const response = new Response("relative content");
659
+ const originalFetch = vi.spyOn(window, "fetch").mockResolvedValue(response);
660
+ const unpatch = patchFetch({});
661
+ const options = { headers: { Accept: "text/plain" } };
662
+
663
+ try {
664
+ expect(await window.fetch("relative-file.txt", options)).toBe(response);
665
+ expect(originalFetch).toHaveBeenCalledExactlyOnceWith(
666
+ "relative-file.txt",
667
+ options,
668
+ );
669
+ } finally {
670
+ unpatch();
671
+ originalFetch.mockRestore();
672
+ }
673
+ });
660
674
 
661
- // Mock Logger.error to avoid test output
675
+ it("preserves the original fetch rejection for malformed URLs", async () => {
676
+ const error = new TypeError("Invalid URL");
677
+ const originalFetch = vi.spyOn(window, "fetch").mockRejectedValue(error);
662
678
  const loggerSpy = vi
663
679
  .spyOn(console, "error")
664
680
  .mockImplementation(Functions.NOOP);
665
-
666
- // This should catch the error and fallback to original fetch
667
- await window.fetch("not-a-valid-url").catch(() => undefined);
668
-
669
- unpatch();
670
- loggerSpy.mockRestore();
681
+ const unpatch = patchFetch({});
682
+ const options = { headers: { Accept: "text/plain" } };
683
+
684
+ try {
685
+ await expect(window.fetch("http://[", options)).rejects.toBe(error);
686
+ expect(originalFetch).toHaveBeenCalledExactlyOnceWith(
687
+ "http://[",
688
+ options,
689
+ );
690
+ expect(loggerSpy).toHaveBeenCalledWith(
691
+ "Error parsing URL",
692
+ expect.any(TypeError),
693
+ );
694
+ } finally {
695
+ unpatch();
696
+ originalFetch.mockRestore();
697
+ loggerSpy.mockRestore();
698
+ }
671
699
  });
672
700
  });
@@ -0,0 +1,104 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+
3
+ import { describe, expect, it } from "vitest";
4
+ import { DataEditorPlugin } from "../impl/DataEditorPlugin";
5
+ import { DataTablePlugin } from "../impl/DataTablePlugin";
6
+ import { DataFramePlugin } from "../impl/data-frames/DataFramePlugin";
7
+ import { PlotlyPlugin } from "../impl/plotly/PlotlyPlugin";
8
+ import { VegaPlugin } from "../impl/vega/VegaPlugin";
9
+
10
+ describe("plugin object schemas", () => {
11
+ const rows = [{ value: 1, extra: { nested: true } }];
12
+ const styles = { "0": { value: { color: "red", customProperty: 1 } } };
13
+ const tableData = {
14
+ initialValue: [],
15
+ label: null,
16
+ data: rows,
17
+ totalRows: 1,
18
+ totalColumns: 1,
19
+ rowHeaders: [],
20
+ cellStyles: styles,
21
+ };
22
+
23
+ it("preserves table rows and arbitrary cell style fields", () => {
24
+ expect(DataTablePlugin.validator.parse(tableData)).toMatchObject({
25
+ data: rows,
26
+ cellStyles: styles,
27
+ pageSize: 10,
28
+ });
29
+ });
30
+
31
+ it("preserves data editor row fields", () => {
32
+ expect(
33
+ DataEditorPlugin.validator.parse({
34
+ initialValue: { edits: [] },
35
+ label: null,
36
+ data: rows,
37
+ editableColumns: "all",
38
+ }),
39
+ ).toMatchObject({ data: rows, columnNames: [] });
40
+ });
41
+
42
+ it("preserves table RPC payload fields", () => {
43
+ const functions = DataTablePlugin.functions!;
44
+ const searchResult = {
45
+ data: rows,
46
+ total_rows: 1,
47
+ cell_styles: styles,
48
+ cell_hover_texts: null,
49
+ };
50
+ expect(functions.search.output.parse(searchResult)).toEqual(searchResult);
51
+ expect(functions.get_row_ids!.input.parse({ custom: true })).toEqual({
52
+ custom: true,
53
+ });
54
+ expect(functions.get_data_url!.input.parse({ custom: true })).toEqual({
55
+ custom: true,
56
+ });
57
+ expect(
58
+ functions.get_data_url!.output.parse({ data_url: rows, format: "json" }),
59
+ ).toEqual({ data_url: rows, format: "json" });
60
+ });
61
+
62
+ it("preserves dataframe search row fields", () => {
63
+ const result = { data: rows, total_rows: 1 };
64
+ expect(DataFramePlugin.functions!.search.output.parse(result)).toEqual(
65
+ result,
66
+ );
67
+ });
68
+
69
+ it("preserves Plotly figure and config fields", () => {
70
+ const data = {
71
+ figure: { data: [], layout: { title: "Chart" } },
72
+ config: { responsive: true },
73
+ };
74
+ expect(new PlotlyPlugin().validator.parse(data)).toEqual(data);
75
+ });
76
+
77
+ it("preserves Vega spec and embed options fields and defaults", () => {
78
+ const spec = { mark: "bar", encoding: { x: { field: "value" } } };
79
+ const validator = new VegaPlugin().validator;
80
+ expect(validator.parse({ spec })).toEqual({
81
+ spec,
82
+ chartSelection: true,
83
+ fieldSelection: true,
84
+ embedOptions: {},
85
+ });
86
+ expect(
87
+ validator.parse({ spec, embedOptions: { actions: false } }).embedOptions,
88
+ ).toEqual({ actions: false });
89
+ });
90
+
91
+ it("rejects scalar values where plugin objects are required", () => {
92
+ expect(
93
+ DataTablePlugin.validator.safeParse({ ...tableData, data: [42] }).success,
94
+ ).toBe(false);
95
+ expect(
96
+ new PlotlyPlugin().validator.safeParse({ figure: 42, config: {} })
97
+ .success,
98
+ ).toBe(false);
99
+ expect(
100
+ new VegaPlugin().validator.safeParse({ spec: {}, embedOptions: 42 })
101
+ .success,
102
+ ).toBe(false);
103
+ });
104
+ });
@@ -44,7 +44,7 @@ export const DataEditorPlugin = createPlugin<Edits>("marimo-data-editor", {
44
44
  ),
45
45
  }),
46
46
  label: z.string().nullable(),
47
- data: z.union([z.string(), z.array(z.object({}).passthrough())]),
47
+ data: z.union([z.string(), z.array(z.looseObject({}))]),
48
48
  fieldTypes: columnToFieldTypesSchema.nullish(),
49
49
  columnNames: z.array(z.string()).default([]),
50
50
  editableColumns: z.union([z.array(z.string()), z.literal("all")]),
@@ -258,7 +258,7 @@ export const DataTablePlugin = createPlugin<S>("marimo-table")
258
258
  z.array(z.object({ rowId: z.string(), columnName: z.string() })),
259
259
  ]),
260
260
  label: z.string().nullable(),
261
- data: z.union([z.string(), z.array(z.object({}).passthrough())]),
261
+ data: z.union([z.string(), z.array(z.looseObject({}))]),
262
262
  rawData: z.union([z.string(), z.array(z.looseObject({}))]).nullish(),
263
263
  totalRows: z.union([z.number(), z.literal(TOO_MANY_ROWS)]),
264
264
  pagination: z.boolean().default(false),
@@ -296,7 +296,7 @@ export const DataTablePlugin = createPlugin<S>("marimo-table")
296
296
  hasStableRowId: z.boolean().default(false),
297
297
  maxHeight: z.number().optional(),
298
298
  cellStyles: z
299
- .record(z.string(), z.record(z.string(), z.object({}).passthrough()))
299
+ .record(z.string(), z.record(z.string(), z.looseObject({})))
300
300
  .optional(),
301
301
  hoverTemplate: z.string().optional(),
302
302
  cellHoverTexts: cellHoverTextSchema,
@@ -341,28 +341,25 @@ export const DataTablePlugin = createPlugin<S>("marimo-table")
341
341
  )
342
342
  .output(
343
343
  z.object({
344
- data: z.union([z.string(), z.array(z.object({}).passthrough())]),
344
+ data: z.union([z.string(), z.array(z.looseObject({}))]),
345
345
  total_rows: z.union([z.number(), z.literal(TOO_MANY_ROWS)]),
346
346
  cell_styles: z
347
- .record(
348
- z.string(),
349
- z.record(z.string(), z.object({}).passthrough()),
350
- )
347
+ .record(z.string(), z.record(z.string(), z.looseObject({})))
351
348
  .nullable(),
352
349
  cell_hover_texts: cellHoverTextSchema.nullable(),
353
350
  raw_data: z.union([z.string(), z.array(z.looseObject({}))]).nullish(),
354
351
  }),
355
352
  ),
356
- get_row_ids: rpc.input(z.object({}).passthrough()).output(
353
+ get_row_ids: rpc.input(z.looseObject({})).output(
357
354
  z.object({
358
355
  row_ids: z.array(z.number()),
359
356
  all_rows: z.boolean(),
360
357
  error: z.string().nullable(),
361
358
  }),
362
359
  ),
363
- get_data_url: rpc.input(z.object({}).passthrough()).output(
360
+ get_data_url: rpc.input(z.looseObject({})).output(
364
361
  z.object({
365
- data_url: z.union([z.string(), z.array(z.object({}).passthrough())]),
362
+ data_url: z.union([z.string(), z.array(z.looseObject({}))]),
366
363
  format: z.enum(["csv", "json", "arrow"]),
367
364
  }),
368
365
  ),
@@ -919,16 +916,16 @@ const DataTableComponent = ({
919
916
  }
920
917
  const fieldTypesWithoutExternalTypes = toFieldTypes(fieldTypes);
921
918
 
922
- return new ColumnChartSpecModel(
923
- columnSummaries.data || [],
924
- fieldTypesWithoutExternalTypes,
925
- columnSummaries.stats,
926
- columnSummaries.bin_values,
927
- columnSummaries.value_counts,
928
- {
919
+ return new ColumnChartSpecModel({
920
+ data: columnSummaries.data || [],
921
+ fieldTypes: fieldTypesWithoutExternalTypes,
922
+ stats: columnSummaries.stats,
923
+ binValues: columnSummaries.bin_values,
924
+ valueCounts: columnSummaries.value_counts,
925
+ opts: {
929
926
  includeCharts: columnSummaries.show_charts,
930
927
  },
931
- );
928
+ });
932
929
  }, [fieldTypes, columnSummaries]);
933
930
 
934
931
  const rowHeaderNames = new Set(rowHeaders.map(([name]) => name));
@@ -154,7 +154,7 @@ export const DataFramePlugin = createPlugin<S>("marimo-dataframe")
154
154
  )
155
155
  .output(
156
156
  z.object({
157
- data: z.union([z.string(), z.array(z.object({}).passthrough())]),
157
+ data: z.union([z.string(), z.array(z.looseObject({}))]),
158
158
  total_rows: z.number(),
159
159
  }),
160
160
  ),