@marimo-team/islands 0.23.16-dev6 → 0.23.16-dev62
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{ConnectedDataExplorerComponent-CJaUI-S-.js → ConnectedDataExplorerComponent-f9vLjQiF.js} +13 -13
- package/dist/{ErrorBoundary-DXlxsrTF.js → ErrorBoundary-DhLqwSWA.js} +3 -3
- package/dist/{any-language-editor-P9tjkj4n.js → any-language-editor-DifE-fl7.js} +5 -5
- package/dist/{button-BcSml68y.js → button-BSm8IXRU.js} +2 -4
- package/dist/{capabilities-BYLFYTYv.js → capabilities-C6OQ5YBO.js} +1 -1
- package/dist/{chat-ui-BdSDiVjk.js → chat-ui-DsZJj75A.js} +3117 -3120
- package/dist/{check-Cs-KV5XY.js → check-DP7U0H0G.js} +1 -1
- package/dist/{code-visibility-CfBftiVM.js → common-Bpnmu1Yz.js} +1085 -1053
- package/dist/{copy-Cs3yt6Vt.js → copy-DNq8q3zw.js} +2 -2
- package/dist/{dist-B96tMAst.js → dist-DIlxLFXG.js} +2 -2
- package/dist/{error-banner-DR7o3CbX.js → error-banner-B_GQfxxD.js} +2 -2
- package/dist/{esm-k8PIvuZW.js → esm-DzZVaNH4.js} +1 -1
- package/dist/{extends-Cq2JYHKy.js → extends-w2_S44bV.js} +4 -4
- package/dist/{formats-BFhkZJwd.js → formats-Dwq1THhr.js} +3 -3
- package/dist/{glide-data-editor--CaN-f27.js → glide-data-editor-B9K6-MJe.js} +9 -9
- package/dist/{html-to-image-DDENewl2.js → html-to-image-CZ1kLKkq.js} +7350 -6356
- package/dist/{input-DStQIuqD.js → input-C5K4VBvB.js} +7 -7
- package/dist/{label-DIk0QoFe.js → label-DjoYwqmj.js} +2 -2
- package/dist/{loader-CTHCI-qe.js → loader-DX-IDS7n.js} +1 -1
- package/dist/main.js +259 -194
- package/dist/{mermaid-CdSvixW2.js → mermaid-hv6IckVw.js} +5 -5
- package/dist/{node-sql-parser-DLl_zNw0.js → node-sql-parser-v1Vi-Fft.js} +16867 -16665
- package/dist/{process-output-2QPcgBQg.js → process-output-bVfbcx5_.js} +32 -28
- package/dist/{reveal-component-BclLkYnV.js → reveal-component-JIt3oSeU.js} +642 -634
- package/dist/{spec-CnaxwDqy.js → spec-CQKL_D3i.js} +4 -4
- package/dist/{strings-BYAhZnQi.js → strings-DIy9NAmg.js} +4 -4
- package/dist/style.css +1 -1
- package/dist/{swiper-component-DE94711u.js → swiper-component-CEdAYjb7.js} +2 -2
- package/dist/toDate-CqhBWjvO.js +713 -0
- package/dist/{tooltip-B-qh7UPB.js → tooltip-f-qR0iUx.js} +3 -3
- package/dist/{types-DQl7jVYK.js → types-DicH9Oi0.js} +1 -1
- package/dist/{useAsyncData-CvO8WUy3.js → useAsyncData-CYOQb1EW.js} +1 -1
- package/dist/{useDateFormatter-BSoGim-G.js → useDateFormatter-Bt2qr_XS.js} +2 -2
- package/dist/{useDeepCompareMemoize-J3MKRdA4.js → useDeepCompareMemoize-CSHQeQEI.js} +1 -1
- package/dist/{useIframeCapabilities-POcFXHbw.js → useIframeCapabilities-kA09ykNq.js} +1 -1
- package/dist/{useLifecycle-DqPxHgAN.js → useLifecycle-CMJGqBaY.js} +3 -3
- package/dist/{useTheme-XMq9mNf_.js → useTheme-DYfby65G.js} +2 -2
- package/dist/{vega-component-CrFoZT1S.js → vega-component-C7nO5zcf.js} +10 -10
- package/dist/{zod-D0yOqoGe.js → zod-BUcyXSdR.js} +1 -1
- package/package.json +3 -4
- package/src/__mocks__/requests.ts +56 -4
- package/src/components/data-table/__tests__/__snapshots__/chart-spec-model.test.ts.snap +174 -21
- package/src/components/data-table/__tests__/chart-spec-model.test.ts +102 -1
- package/src/components/data-table/__tests__/columns.test.tsx +9 -1
- package/src/components/data-table/column-summary/chart-spec-model.tsx +163 -103
- package/src/components/data-table/column-summary/legacy-chart-spec.ts +15 -7
- package/src/components/data-table/column-summary/utils.ts +26 -7
- package/src/components/data-table/columns.tsx +11 -1
- package/src/components/data-table/types.ts +1 -1
- package/src/components/editor/__tests__/viewer-banner.test.tsx +14 -0
- package/src/components/editor/actions/__tests__/pair-with-agent-commands.test.ts +99 -18
- package/src/components/editor/actions/__tests__/pdf-export.test.ts +32 -13
- package/src/components/editor/actions/pair-with-agent-commands.ts +21 -10
- package/src/components/editor/actions/pair-with-agent-modal.tsx +2 -2
- package/src/components/editor/actions/pdf-export.ts +11 -14
- package/src/components/editor/actions/useNotebookActions.tsx +28 -19
- package/src/components/editor/cell/code/__tests__/cell-editor-activity-lifecycle.test.ts +250 -0
- package/src/components/editor/cell/code/cell-editor.tsx +13 -7
- package/src/components/editor/chrome/__tests__/state.test.ts +7 -1
- package/src/components/editor/chrome/panels/__tests__/logs-panel.test.tsx +25 -0
- package/src/components/editor/chrome/panels/__tests__/panel-accordion-state.test.ts +42 -0
- package/src/components/editor/chrome/panels/file-explorer-panel.tsx +6 -17
- package/src/components/editor/chrome/panels/logs-panel.tsx +12 -2
- package/src/components/editor/chrome/panels/panel-accordion-state.ts +51 -0
- package/src/components/editor/chrome/panels/session-panel.tsx +5 -16
- package/src/components/editor/chrome/panels/write-secret-modal.tsx +40 -12
- package/src/components/editor/chrome/state.ts +10 -0
- package/src/components/editor/connections/__tests__/json-credentials.test.ts +114 -0
- package/src/components/editor/connections/__tests__/paths.test.ts +39 -0
- package/src/components/editor/connections/__tests__/quick-add-data-sources.test.tsx +113 -0
- package/src/components/editor/connections/add-connection-dialog.tsx +2 -0
- package/src/components/editor/connections/components.tsx +6 -2
- package/src/components/editor/connections/database/__tests__/__snapshots__/as-code.test.ts.snap +27 -5
- package/src/components/editor/connections/database/__tests__/as-code.test.ts +33 -0
- package/src/components/editor/connections/database/as-code.ts +37 -13
- package/src/components/editor/connections/database/schemas.ts +6 -5
- package/src/components/editor/connections/form-renderers.tsx +99 -1
- package/src/components/editor/connections/json-credentials.ts +57 -0
- package/src/components/editor/connections/paths.ts +21 -0
- package/src/components/editor/connections/quick-add-data-sources.tsx +106 -0
- package/src/components/editor/connections/secret-combobox.tsx +41 -20
- package/src/components/editor/connections/storage/__tests__/__snapshots__/as-code.test.ts.snap +88 -2
- package/src/components/editor/connections/storage/__tests__/as-code.test.ts +128 -8
- package/src/components/editor/connections/storage/as-code.ts +79 -36
- package/src/components/editor/connections/storage/schemas.ts +85 -44
- package/src/components/editor/file-tree/__tests__/renderers.test.ts +31 -0
- package/src/components/editor/file-tree/file-viewer.tsx +11 -7
- package/src/components/editor/file-tree/renderers.tsx +21 -0
- package/src/components/editor/renderers/vertical-layout/vertical-layout.tsx +2 -2
- package/src/components/editor/viewer-banner.tsx +6 -1
- package/src/components/forms/form.tsx +59 -24
- package/src/components/forms/options.ts +2 -1
- package/src/components/slides/__tests__/cell-output-block.test.tsx +40 -0
- package/src/components/slides/__tests__/slide-cell-view.test.tsx +33 -0
- package/src/components/slides/reveal-component.tsx +22 -17
- package/src/components/slides/slide-cell-view.tsx +1 -1
- package/src/components/slides/slides.css +4 -2
- package/src/components/static-html/share-modal.tsx +1 -1
- package/src/components/ui/input.tsx +1 -1
- package/src/core/ai/context/providers/file.ts +8 -29
- package/src/core/cells/__tests__/cells.test.ts +12 -2
- package/src/core/cells/__tests__/logs.test.ts +5 -5
- package/src/core/cells/cells.ts +17 -4
- package/src/core/cells/document-changes.ts +1 -0
- package/src/core/cells/logs.ts +14 -4
- package/src/core/codemirror/cm.ts +3 -0
- package/src/core/codemirror/code-lens/__tests__/actions.test.ts +68 -0
- package/src/core/codemirror/code-lens/__tests__/analyzer.test.ts +224 -0
- package/src/core/codemirror/code-lens/__tests__/entities.test.ts +175 -0
- package/src/core/codemirror/code-lens/__tests__/extension.test.ts +325 -0
- package/src/core/codemirror/code-lens/__tests__/perf.test.ts +121 -0
- package/src/core/codemirror/code-lens/__tests__/popover.test.ts +93 -0
- package/src/core/codemirror/code-lens/actions.ts +33 -0
- package/src/core/codemirror/code-lens/analyzer.ts +284 -0
- package/src/core/codemirror/code-lens/entities.ts +123 -0
- package/src/core/codemirror/code-lens/extension.ts +298 -0
- package/src/core/codemirror/code-lens/icons.ts +36 -0
- package/src/core/codemirror/code-lens/popover.tsx +240 -0
- package/src/core/codemirror/copilot/__tests__/language-server.test.ts +396 -100
- package/src/core/codemirror/copilot/__tests__/transport.test.ts +128 -540
- package/src/core/codemirror/copilot/language-server.ts +182 -70
- package/src/core/codemirror/copilot/transport.ts +15 -207
- package/src/core/codemirror/copilot/types.ts +9 -2
- package/src/core/codemirror/find-replace/__tests__/navigate.test.ts +42 -30
- package/src/core/codemirror/find-replace/navigate.ts +2 -0
- package/src/core/codemirror/go-to-definition/__tests__/commands.test.ts +24 -3
- package/src/core/codemirror/go-to-definition/commands.ts +93 -90
- package/src/core/codemirror/keymaps/__tests__/keymaps.test.ts +31 -2
- package/src/core/codemirror/keymaps/keymaps.ts +12 -0
- package/src/core/codemirror/language/__tests__/sql.test.ts +627 -61
- package/src/core/codemirror/language/languages/python.ts +23 -11
- package/src/core/codemirror/language/languages/sql/completion-store.ts +5 -1
- package/src/core/codemirror/language/languages/sql/renderers.tsx +3 -3
- package/src/core/codemirror/language/languages/sql/sql.ts +251 -16
- package/src/core/codemirror/lsp/__tests__/federated-lsp.test.ts +161 -0
- package/src/core/codemirror/lsp/__tests__/log-messages.test.ts +53 -0
- package/src/core/codemirror/lsp/__tests__/normalize-markdown-math.test.ts +25 -0
- package/src/core/codemirror/lsp/__tests__/notebook-lsp.test.ts +598 -156
- package/src/core/codemirror/lsp/__tests__/transports.test.ts +112 -0
- package/src/core/codemirror/lsp/federated-lsp.ts +76 -23
- package/src/core/codemirror/lsp/log-messages.ts +37 -0
- package/src/core/codemirror/lsp/normalize-markdown-math.ts +8 -4
- package/src/core/codemirror/lsp/notebook-lsp.ts +220 -56
- package/src/core/codemirror/lsp/transports.ts +8 -1
- package/src/core/codemirror/lsp/types.ts +68 -8
- package/src/core/codemirror/lsp/utils.ts +14 -0
- package/src/core/codemirror/python-node-names.ts +90 -0
- package/src/core/codemirror/reactive-references/analyzer.ts +52 -57
- package/src/core/codemirror/utils.ts +28 -0
- package/src/core/config/feature-flag.tsx +3 -1
- package/src/core/datasets/data-source-discovery.ts +5 -0
- package/src/core/datasets/request-registry.ts +11 -0
- package/src/core/edit-app.tsx +1 -2
- package/src/core/export/__tests__/hooks.test.ts +2 -4
- package/src/core/export/hooks.ts +4 -3
- package/src/core/hotkeys/hotkeys.ts +0 -2
- package/src/core/islands/__tests__/bridge.test.ts +107 -0
- package/src/core/islands/bootstrap.ts +1 -0
- package/src/core/islands/bridge.ts +18 -0
- package/src/core/kernel/messages.ts +2 -0
- package/src/core/lsp/__tests__/transport.test.ts +509 -337
- package/src/core/lsp/transport.ts +304 -132
- package/src/core/mode.ts +10 -0
- package/src/core/network/__tests__/api.test.ts +42 -0
- package/src/core/network/__tests__/connection.test.ts +45 -0
- package/src/core/network/__tests__/requests-lazy.test.ts +47 -1
- package/src/core/network/__tests__/requests-network.test.ts +48 -0
- package/src/core/network/api.ts +27 -0
- package/src/core/network/connection.ts +15 -0
- package/src/core/network/requests-lazy.ts +3 -0
- package/src/core/network/requests-network.ts +31 -8
- package/src/core/network/requests-static.ts +3 -0
- package/src/core/network/requests-toasting.tsx +3 -0
- package/src/core/network/types.ts +27 -6
- package/src/core/rtc/state.ts +9 -0
- package/src/core/static/download-html.ts +5 -14
- package/src/core/wasm/__tests__/bridge.test.ts +99 -5
- package/src/core/wasm/bridge.ts +42 -9
- package/src/core/wasm/worker/types.ts +10 -3
- package/src/core/wasm/worker/worker.ts +7 -8
- package/src/core/websocket/useMarimoKernelConnection.tsx +4 -0
- package/src/css/app/codemirror.css +16 -2
- package/src/hooks/__tests__/useDataSourceDiscovery.test.ts +80 -0
- package/src/hooks/useDataSourceDiscovery.ts +18 -0
- package/src/plugins/impl/DataTablePlugin.tsx +4 -1
- package/src/plugins/impl/FormPlugin.tsx +1 -1
- package/src/plugins/impl/__tests__/DataTablePlugin.test.tsx +158 -0
- package/src/plugins/impl/__tests__/FormPlugin.test.tsx +162 -0
- package/src/plugins/impl/anywidget/__tests__/model.test.ts +55 -0
- package/src/plugins/impl/anywidget/model.ts +20 -5
- package/src/plugins/impl/data-frames/forms/__tests__/__snapshots__/form.test.tsx.snap +33 -21
- package/src/plugins/impl/data-frames/forms/renderers.tsx +11 -8
- package/src/stories/log-viewer.stories.tsx +12 -6
- package/src/utils/__tests__/blob.test.ts +27 -1
- package/src/utils/__tests__/download.test.tsx +44 -4
- package/src/utils/blob.ts +24 -1
- package/src/utils/download.ts +14 -15
- package/dist/toDate-B3IRfHhw.js +0 -692
|
@@ -74,17 +74,24 @@ export const ZodForm = <T extends FieldValues>({
|
|
|
74
74
|
return (
|
|
75
75
|
<FormProvider {...form}>
|
|
76
76
|
{children}
|
|
77
|
-
{renderZodSchema(schema, form, path, renderers)}
|
|
77
|
+
{renderZodSchema({ schema, form, path, renderers })}
|
|
78
78
|
</FormProvider>
|
|
79
79
|
);
|
|
80
80
|
};
|
|
81
81
|
|
|
82
|
-
export
|
|
83
|
-
schema: z.ZodType<S
|
|
84
|
-
form: UseFormReturn<T
|
|
85
|
-
path: Path<T
|
|
86
|
-
renderers: FormRenderer<T>[]
|
|
87
|
-
|
|
82
|
+
export interface RenderZodSchemaOptions<T extends FieldValues, S> {
|
|
83
|
+
schema: z.ZodType<S>;
|
|
84
|
+
form: UseFormReturn<T>;
|
|
85
|
+
path: Path<T>;
|
|
86
|
+
renderers: FormRenderer<T>[];
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function renderZodSchema<T extends FieldValues, S>({
|
|
90
|
+
schema,
|
|
91
|
+
form,
|
|
92
|
+
path,
|
|
93
|
+
renderers,
|
|
94
|
+
}: RenderZodSchemaOptions<T, S>) {
|
|
88
95
|
// Try custom renderers first
|
|
89
96
|
for (const renderer of renderers) {
|
|
90
97
|
const { isMatch, Component } = renderer;
|
|
@@ -107,7 +114,7 @@ export function renderZodSchema<T extends FieldValues, S>(
|
|
|
107
114
|
!inner.description && schema.description
|
|
108
115
|
? inner.describe(schema.description)
|
|
109
116
|
: inner;
|
|
110
|
-
return renderZodSchema(inner, form, path, renderers);
|
|
117
|
+
return renderZodSchema({ schema: inner, form, path, renderers });
|
|
111
118
|
}
|
|
112
119
|
|
|
113
120
|
if (schema instanceof z.ZodOptional) {
|
|
@@ -116,7 +123,7 @@ export function renderZodSchema<T extends FieldValues, S>(
|
|
|
116
123
|
!inner.description && schema.description
|
|
117
124
|
? inner.describe(schema.description)
|
|
118
125
|
: inner;
|
|
119
|
-
return renderZodSchema(inner, form, path, renderers);
|
|
126
|
+
return renderZodSchema({ schema: inner, form, path, renderers });
|
|
120
127
|
}
|
|
121
128
|
|
|
122
129
|
if (schema instanceof z.ZodObject) {
|
|
@@ -134,12 +141,12 @@ export function renderZodSchema<T extends FieldValues, S>(
|
|
|
134
141
|
<FormLabel>{label}</FormLabel>
|
|
135
142
|
{Objects.entries(schema.shape).map(([key, value]) => {
|
|
136
143
|
const isLiteral = value instanceof z.ZodLiteral;
|
|
137
|
-
const childForm = renderZodSchema(
|
|
138
|
-
value as z.ZodType,
|
|
144
|
+
const childForm = renderZodSchema({
|
|
145
|
+
schema: value as z.ZodType,
|
|
139
146
|
form,
|
|
140
|
-
joinPath(path, key),
|
|
147
|
+
path: joinPath(path, key),
|
|
141
148
|
renderers,
|
|
142
|
-
);
|
|
149
|
+
});
|
|
143
150
|
|
|
144
151
|
if (isLiteral) {
|
|
145
152
|
return <React.Fragment key={key}>{childForm}</React.Fragment>;
|
|
@@ -189,16 +196,24 @@ export function renderZodSchema<T extends FieldValues, S>(
|
|
|
189
196
|
name={path}
|
|
190
197
|
render={({ field }) => (
|
|
191
198
|
<FormItem>
|
|
192
|
-
<div
|
|
193
|
-
|
|
194
|
-
|
|
199
|
+
<div
|
|
200
|
+
className={cn(
|
|
201
|
+
"flex flex-row gap-2",
|
|
202
|
+
description ? "items-start" : "items-center",
|
|
203
|
+
)}
|
|
204
|
+
>
|
|
195
205
|
<FormControl>
|
|
196
206
|
<Checkbox
|
|
197
207
|
data-testid="marimo-plugin-data-frames-boolean-checkbox"
|
|
198
208
|
checked={field.value}
|
|
199
209
|
onCheckedChange={field.onChange}
|
|
210
|
+
className={description ? "mt-0.5" : undefined}
|
|
200
211
|
/>
|
|
201
212
|
</FormControl>
|
|
213
|
+
<div className="flex flex-col gap-1">
|
|
214
|
+
<FormLabel>{label}</FormLabel>
|
|
215
|
+
<FormDescription>{description}</FormDescription>
|
|
216
|
+
</div>
|
|
202
217
|
</div>
|
|
203
218
|
<FormMessage />
|
|
204
219
|
</FormItem>
|
|
@@ -363,13 +378,13 @@ export function renderZodSchema<T extends FieldValues, S>(
|
|
|
363
378
|
|
|
364
379
|
return (
|
|
365
380
|
<div className="flex flex-col">
|
|
366
|
-
<FormLabel>{label}</FormLabel>
|
|
367
|
-
<div className="flex border-b mb-4
|
|
381
|
+
<FormLabel className="mb-2">{label}</FormLabel>
|
|
382
|
+
<div className="flex border-b mb-4">
|
|
368
383
|
{types.map((type: string) => (
|
|
369
384
|
<button
|
|
370
385
|
key={type}
|
|
371
386
|
type="button"
|
|
372
|
-
className={`px-4 py-2 ${
|
|
387
|
+
className={`px-4 py-2 text-sm ${
|
|
373
388
|
unionTypeValue === type
|
|
374
389
|
? "border-b-2 border-primary font-medium"
|
|
375
390
|
: "text-muted-foreground"
|
|
@@ -389,7 +404,12 @@ export function renderZodSchema<T extends FieldValues, S>(
|
|
|
389
404
|
</div>
|
|
390
405
|
<div className="flex flex-col" key={unionTypeValue}>
|
|
391
406
|
{selectedOption &&
|
|
392
|
-
renderZodSchema(
|
|
407
|
+
renderZodSchema({
|
|
408
|
+
schema: selectedOption,
|
|
409
|
+
form,
|
|
410
|
+
path,
|
|
411
|
+
renderers,
|
|
412
|
+
})}
|
|
393
413
|
</div>
|
|
394
414
|
</div>
|
|
395
415
|
);
|
|
@@ -434,7 +454,12 @@ export function renderZodSchema<T extends FieldValues, S>(
|
|
|
434
454
|
})}
|
|
435
455
|
</NativeSelect>
|
|
436
456
|
{selectedOption &&
|
|
437
|
-
renderZodSchema(
|
|
457
|
+
renderZodSchema({
|
|
458
|
+
schema: selectedOption,
|
|
459
|
+
form,
|
|
460
|
+
path,
|
|
461
|
+
renderers,
|
|
462
|
+
})}
|
|
438
463
|
</div>
|
|
439
464
|
);
|
|
440
465
|
}}
|
|
@@ -459,10 +484,15 @@ export function renderZodSchema<T extends FieldValues, S>(
|
|
|
459
484
|
}
|
|
460
485
|
if ("unwrap" in schema) {
|
|
461
486
|
// Handle ZodEffects (transforms/refinements)
|
|
462
|
-
return renderZodSchema(
|
|
487
|
+
return renderZodSchema({
|
|
488
|
+
schema: maybeUnwrap(schema),
|
|
489
|
+
form,
|
|
490
|
+
path,
|
|
491
|
+
renderers,
|
|
492
|
+
});
|
|
463
493
|
}
|
|
464
494
|
if (isZodPipe(schema)) {
|
|
465
|
-
return renderZodSchema(schema.in, form, path, renderers);
|
|
495
|
+
return renderZodSchema({ schema: schema.in, form, path, renderers });
|
|
466
496
|
}
|
|
467
497
|
|
|
468
498
|
return (
|
|
@@ -512,7 +542,12 @@ const FormArray = ({
|
|
|
512
542
|
key={field.id}
|
|
513
543
|
onKeyDown={Events.onEnter((e) => e.preventDefault())}
|
|
514
544
|
>
|
|
515
|
-
{renderZodSchema(
|
|
545
|
+
{renderZodSchema({
|
|
546
|
+
schema,
|
|
547
|
+
form,
|
|
548
|
+
path: `${path}[${index}]`,
|
|
549
|
+
renderers,
|
|
550
|
+
})}
|
|
516
551
|
{canRemove && (
|
|
517
552
|
<Trash2Icon
|
|
518
553
|
className="w-4 h-4 ml-2 my-1 text-muted-foreground hover:text-destructive cursor-pointer absolute right-0 top-5"
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import { render } from "@testing-library/react";
|
|
4
|
+
import { describe, expect, it } from "vitest";
|
|
5
|
+
import { cellId } from "@/__tests__/branded";
|
|
6
|
+
import { TooltipProvider } from "@/components/ui/tooltip";
|
|
7
|
+
import { CellOutputId } from "@/core/cells/ids";
|
|
8
|
+
import { createCell, createCellRuntimeState } from "@/core/cells/types";
|
|
9
|
+
import { CellOutputBlock } from "../reveal-component";
|
|
10
|
+
|
|
11
|
+
describe("CellOutputBlock", () => {
|
|
12
|
+
it("renders the cell's data-cell-id and data-cell-name so it can be targeted by custom CSS", () => {
|
|
13
|
+
const id = cellId("test");
|
|
14
|
+
const cell = {
|
|
15
|
+
...createCell({ id, name: "title" }),
|
|
16
|
+
...createCellRuntimeState({
|
|
17
|
+
output: {
|
|
18
|
+
channel: "output",
|
|
19
|
+
mimetype: "text/plain",
|
|
20
|
+
data: "hello",
|
|
21
|
+
timestamp: 0,
|
|
22
|
+
},
|
|
23
|
+
}),
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const { container } = render(
|
|
27
|
+
<TooltipProvider>
|
|
28
|
+
<CellOutputBlock cell={cell} />
|
|
29
|
+
</TooltipProvider>,
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
const cellElement = container.querySelector(`[data-cell-id="${id}"]`);
|
|
33
|
+
expect(cellElement).toBeTruthy();
|
|
34
|
+
expect(cellElement?.getAttribute("data-cell-name")).toBe("title");
|
|
35
|
+
|
|
36
|
+
// The wrapper's `data-cell-id` must coexist with the output's own
|
|
37
|
+
// `CellOutputId`-derived id, without colliding.
|
|
38
|
+
expect(document.getElementById(CellOutputId.create(id))).toBeTruthy();
|
|
39
|
+
});
|
|
40
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import { render } from "@testing-library/react";
|
|
4
|
+
import { beforeAll, describe, expect, it } from "vitest";
|
|
5
|
+
import { SetupMocks } from "@/__mocks__/common";
|
|
6
|
+
import { cellId } from "@/__tests__/branded";
|
|
7
|
+
import { TooltipProvider } from "@/components/ui/tooltip";
|
|
8
|
+
import { createCell, createCellRuntimeState } from "@/core/cells/types";
|
|
9
|
+
import { SlideCellReadOnlyView } from "../slide-cell-view";
|
|
10
|
+
|
|
11
|
+
beforeAll(() => {
|
|
12
|
+
SetupMocks.resizeObserver();
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
describe("SlideCellReadOnlyView", () => {
|
|
16
|
+
it("renders the cell's data-cell-id and data-cell-name so it can be targeted by custom CSS", () => {
|
|
17
|
+
const id = cellId("test");
|
|
18
|
+
const cell = {
|
|
19
|
+
...createCell({ id, name: "title", code: "x = 1" }),
|
|
20
|
+
...createCellRuntimeState(),
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const { container } = render(
|
|
24
|
+
<TooltipProvider>
|
|
25
|
+
<SlideCellReadOnlyView cell={cell} />
|
|
26
|
+
</TooltipProvider>,
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
const cellElement = container.querySelector(`[data-cell-id="${id}"]`);
|
|
30
|
+
expect(cellElement).toBeTruthy();
|
|
31
|
+
expect(cellElement?.getAttribute("data-cell-name")).toBe("title");
|
|
32
|
+
});
|
|
33
|
+
});
|
|
@@ -13,6 +13,7 @@ import useEvent from "react-use-event-hook";
|
|
|
13
13
|
import { CodeIcon, ExpandIcon, EyeOffIcon } from "lucide-react";
|
|
14
14
|
import { Deck, Fragment, Slide, Stack } from "@revealjs/react";
|
|
15
15
|
import { Panel, PanelGroup, PanelResizeHandle } from "react-resizable-panels";
|
|
16
|
+
import { cellDomProps } from "@/components/editor/common";
|
|
16
17
|
import { Slide as CellOutputSlide } from "@/components/slides/slide";
|
|
17
18
|
import { SlideScrollContainer } from "@/components/slides/slide-scroll-hint";
|
|
18
19
|
import { Button } from "@/components/ui/button";
|
|
@@ -304,6 +305,25 @@ function resolveSlideContentStyle(
|
|
|
304
305
|
}
|
|
305
306
|
}
|
|
306
307
|
|
|
308
|
+
/**
|
|
309
|
+
* Renders a cell's output wrapped in a `data-cell-id`/`data-cell-name` div
|
|
310
|
+
* (see `cellDomProps`), so it can be targeted by custom CSS in the slides
|
|
311
|
+
* view just like it can in the vertical layout. The wrapper is required
|
|
312
|
+
* because `CellOutputSlide` sets its own `id` from `CellOutputId` (a
|
|
313
|
+
* different scheme than `HTMLCellId`) and its inner `OutputArea` uses
|
|
314
|
+
* `display: contents`, which can't be targeted for background/border styling.
|
|
315
|
+
*/
|
|
316
|
+
export const CellOutputBlock = ({ cell }: { cell: RuntimeCell }) => (
|
|
317
|
+
<div className="flex flex-col" {...cellDomProps(cell.id, cell.name)}>
|
|
318
|
+
<CellOutputSlide
|
|
319
|
+
cellId={cell.id}
|
|
320
|
+
status={cell.status}
|
|
321
|
+
output={cell.output}
|
|
322
|
+
stale={outputIsStale(cell, false)}
|
|
323
|
+
/>
|
|
324
|
+
</div>
|
|
325
|
+
);
|
|
326
|
+
|
|
307
327
|
const SubslideView = ({
|
|
308
328
|
subslide,
|
|
309
329
|
resolveShowCode,
|
|
@@ -340,15 +360,7 @@ const SubslideView = ({
|
|
|
340
360
|
{subslide.blocks.map((block, i) => {
|
|
341
361
|
const rendered = block.cells.map((cell) => {
|
|
342
362
|
if (!resolveShowCode(cell.id)) {
|
|
343
|
-
return
|
|
344
|
-
<CellOutputSlide
|
|
345
|
-
key={cell.id}
|
|
346
|
-
cellId={cell.id}
|
|
347
|
-
status={cell.status}
|
|
348
|
-
output={cell.output}
|
|
349
|
-
stale={outputIsStale(cell, false)}
|
|
350
|
-
/>
|
|
351
|
-
);
|
|
363
|
+
return <CellOutputBlock key={cell.id} cell={cell} />;
|
|
352
364
|
}
|
|
353
365
|
return isEditable ? (
|
|
354
366
|
<SlideCellView key={cell.id} cell={cell} />
|
|
@@ -406,14 +418,7 @@ const ParkedPreviewContent = ({
|
|
|
406
418
|
<SlideCellReadOnlyView cell={cell} />
|
|
407
419
|
);
|
|
408
420
|
}
|
|
409
|
-
return
|
|
410
|
-
<CellOutputSlide
|
|
411
|
-
cellId={cell.id}
|
|
412
|
-
status={cell.status}
|
|
413
|
-
output={cell.output}
|
|
414
|
-
stale={outputIsStale(cell, false)}
|
|
415
|
-
/>
|
|
416
|
-
);
|
|
421
|
+
return <CellOutputBlock cell={cell} />;
|
|
417
422
|
};
|
|
418
423
|
|
|
419
424
|
// There is an upstream react bug in dev mode (https://github.com/facebook/react/issues/34840)
|
|
@@ -202,7 +202,7 @@ export const SlideCellReadOnlyView = ({ cell }: { cell: RuntimeCell }) => {
|
|
|
202
202
|
);
|
|
203
203
|
|
|
204
204
|
const editor = (
|
|
205
|
-
<div className="marimo-cell">
|
|
205
|
+
<div className="marimo-cell" {...cellDomProps(cell.id, cell.name)}>
|
|
206
206
|
<ReadonlyCode
|
|
207
207
|
code={display.code}
|
|
208
208
|
language={display.language}
|
|
@@ -15,8 +15,10 @@
|
|
|
15
15
|
width: unset;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
/* If the first output is the only child, make it flex 1
|
|
19
|
-
|
|
18
|
+
/* If the first output is the only child, make it flex 1. Cells are wrapped
|
|
19
|
+
in a `data-cell-id`/`data-cell-name` div (see `cellDomProps`), so `.output`
|
|
20
|
+
is a grandchild rather than a direct child here. */
|
|
21
|
+
> *:only-child:has(> .output:only-child, > * > .output:only-child) {
|
|
20
22
|
flex: 1;
|
|
21
23
|
}
|
|
22
24
|
|
|
@@ -40,7 +40,7 @@ export const ShareStaticNotebookModal: React.FC<{
|
|
|
40
40
|
e.preventDefault();
|
|
41
41
|
|
|
42
42
|
onClose();
|
|
43
|
-
const html = await exportAsHTML({
|
|
43
|
+
const { contents: html } = await exportAsHTML({
|
|
44
44
|
download: false,
|
|
45
45
|
includeCode: true,
|
|
46
46
|
files: VirtualFileTracker.INSTANCE.filenames(),
|
|
@@ -195,7 +195,7 @@ export const OnBlurredInput = React.forwardRef<
|
|
|
195
195
|
onChange={(event) => setInternalValue(event.target.value)}
|
|
196
196
|
onBlur={() => setValue(internalValue || "")}
|
|
197
197
|
onKeyDown={(event) => {
|
|
198
|
-
if (event.key !== "Enter") {
|
|
198
|
+
if (event.key !== "Enter" || event.shiftKey) {
|
|
199
199
|
return;
|
|
200
200
|
}
|
|
201
201
|
setValue(internalValue || "");
|
|
@@ -10,8 +10,7 @@ import {
|
|
|
10
10
|
import { toast } from "@/components/ui/use-toast";
|
|
11
11
|
import { contextCallbacks } from "@/core/codemirror/ai/state";
|
|
12
12
|
import type { EditRequests, FileInfo, RunRequests } from "@/core/network/types";
|
|
13
|
-
import {
|
|
14
|
-
import { type Base64String, base64ToDataURL } from "@/utils/json/base64";
|
|
13
|
+
import { fileDetailsToBlob } from "@/utils/blob";
|
|
15
14
|
import { Logger } from "@/utils/Logger";
|
|
16
15
|
import { type AIContextItem, AIContextProvider } from "../registry";
|
|
17
16
|
import { contextToXml } from "../utils";
|
|
@@ -219,33 +218,13 @@ export class FileContextProvider extends AIContextProvider<FileContextItem> {
|
|
|
219
218
|
|
|
220
219
|
const mimeType = fileDetails.mimeType || "text/plain";
|
|
221
220
|
|
|
222
|
-
//
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
mimeType
|
|
227
|
-
|
|
228
|
-
)
|
|
229
|
-
// Text files - create blob directly from contents
|
|
230
|
-
blob = new Blob([fileDetails.contents || ""], { type: mimeType });
|
|
231
|
-
} else {
|
|
232
|
-
// Binary files - use blob utility to decode base64
|
|
233
|
-
if (fileDetails.contents) {
|
|
234
|
-
try {
|
|
235
|
-
// Create data URL using utility and deserialize blob
|
|
236
|
-
const dataURL = base64ToDataURL(
|
|
237
|
-
fileDetails.contents as Base64String,
|
|
238
|
-
mimeType,
|
|
239
|
-
);
|
|
240
|
-
blob = deserializeBlob(dataURL);
|
|
241
|
-
} catch {
|
|
242
|
-
// Fallback to treating as text
|
|
243
|
-
blob = new Blob([fileDetails.contents], { type: mimeType });
|
|
244
|
-
}
|
|
245
|
-
} else {
|
|
246
|
-
blob = new Blob([""], { type: mimeType });
|
|
247
|
-
}
|
|
248
|
-
}
|
|
221
|
+
// The backend's `isBase64` flag authoritatively distinguishes binary
|
|
222
|
+
// (base64-encoded) from UTF-8 text contents.
|
|
223
|
+
const blob = fileDetailsToBlob({
|
|
224
|
+
contents: fileDetails.contents ?? "",
|
|
225
|
+
mimeType,
|
|
226
|
+
isBase64: fileDetails.isBase64 ?? false,
|
|
227
|
+
});
|
|
249
228
|
|
|
250
229
|
const file = new File([blob], name, { type: mimeType });
|
|
251
230
|
addAttachment(file);
|
|
@@ -1870,8 +1870,18 @@ describe("cell reducer", () => {
|
|
|
1870
1870
|
|
|
1871
1871
|
it("can clear logs", () => {
|
|
1872
1872
|
state.cellLogs = [
|
|
1873
|
-
{
|
|
1874
|
-
|
|
1873
|
+
{
|
|
1874
|
+
level: "stderr",
|
|
1875
|
+
message: "log1",
|
|
1876
|
+
timestamp: 0,
|
|
1877
|
+
source: { type: "cell", cellId: firstCellId },
|
|
1878
|
+
},
|
|
1879
|
+
{
|
|
1880
|
+
level: "stderr",
|
|
1881
|
+
message: "log1",
|
|
1882
|
+
timestamp: 0,
|
|
1883
|
+
source: { type: "cell", cellId: firstCellId },
|
|
1884
|
+
},
|
|
1875
1885
|
];
|
|
1876
1886
|
actions.clearLogs();
|
|
1877
1887
|
expect(state.cellLogs).toEqual([]);
|
|
@@ -46,7 +46,7 @@ describe("getCellLogsForMessage", () => {
|
|
|
46
46
|
timestamp: 1_234_567_890,
|
|
47
47
|
level: "stdout",
|
|
48
48
|
message: "Hello, World!",
|
|
49
|
-
cellId: "cell-1",
|
|
49
|
+
source: { type: "cell", cellId: "cell-1" },
|
|
50
50
|
});
|
|
51
51
|
});
|
|
52
52
|
|
|
@@ -74,7 +74,7 @@ describe("getCellLogsForMessage", () => {
|
|
|
74
74
|
timestamp: 1_234_567_890,
|
|
75
75
|
level: "stderr",
|
|
76
76
|
message: "Error occurred",
|
|
77
|
-
cellId: "cell-2",
|
|
77
|
+
source: { type: "cell", cellId: "cell-2" },
|
|
78
78
|
});
|
|
79
79
|
});
|
|
80
80
|
|
|
@@ -102,7 +102,7 @@ describe("getCellLogsForMessage", () => {
|
|
|
102
102
|
timestamp: 1_234_567_890,
|
|
103
103
|
level: "stdout",
|
|
104
104
|
message: "Error: Something went wrong",
|
|
105
|
-
cellId: "cell-3",
|
|
105
|
+
source: { type: "cell", cellId: "cell-3" },
|
|
106
106
|
});
|
|
107
107
|
});
|
|
108
108
|
|
|
@@ -130,7 +130,7 @@ describe("getCellLogsForMessage", () => {
|
|
|
130
130
|
timestamp: 1_234_567_890,
|
|
131
131
|
level: "stderr",
|
|
132
132
|
message: "Critical Error: System failure",
|
|
133
|
-
cellId: "cell-4",
|
|
133
|
+
source: { type: "cell", cellId: "cell-4" },
|
|
134
134
|
});
|
|
135
135
|
});
|
|
136
136
|
|
|
@@ -157,7 +157,7 @@ describe("getCellLogsForMessage", () => {
|
|
|
157
157
|
expect(logs[0].level).toBe("stderr"); // marimo-error should be treated as stderr
|
|
158
158
|
expect(logs[0].message).toContain("Traceback (most recent call last):");
|
|
159
159
|
expect(logs[0].message).toContain('File "test.py", line 1');
|
|
160
|
-
expect(logs[0].
|
|
160
|
+
expect(logs[0].source).toEqual({ type: "cell", cellId: "cell-5" });
|
|
161
161
|
});
|
|
162
162
|
|
|
163
163
|
test("handles multiple console outputs with different MIME types", () => {
|
package/src/core/cells/cells.ts
CHANGED
|
@@ -28,7 +28,7 @@ import { findCollapseRange, mergeOutlines } from "../dom/outline";
|
|
|
28
28
|
import type { CellMessage } from "../kernel/messages";
|
|
29
29
|
import { isErrorMime } from "../mime";
|
|
30
30
|
import type { CellConfig } from "../network/types";
|
|
31
|
-
import { isRtcEnabled } from "../rtc/state";
|
|
31
|
+
import { canUseRtc, isRtcEnabled } from "../rtc/state";
|
|
32
32
|
import { createDeepEqualAtom, store } from "../state/jotai";
|
|
33
33
|
import { isWasm } from "../wasm/utils";
|
|
34
34
|
import { prepareCellForExecution, transitionCell } from "./cell";
|
|
@@ -927,6 +927,12 @@ const {
|
|
|
927
927
|
cellLogs: [...nextState.cellLogs, ...getCellLogsForMessage(message)],
|
|
928
928
|
};
|
|
929
929
|
},
|
|
930
|
+
addLogs: (state, action: { logs: CellLog[] }) => {
|
|
931
|
+
return {
|
|
932
|
+
...state,
|
|
933
|
+
cellLogs: [...state.cellLogs, ...action.logs],
|
|
934
|
+
};
|
|
935
|
+
},
|
|
930
936
|
setCellIds: (state, action: { cellIds: CellId[] }) => {
|
|
931
937
|
const isTheSame = isEqual(state.cellIds.inOrderIds, action.cellIds);
|
|
932
938
|
if (isTheSame) {
|
|
@@ -1037,9 +1043,9 @@ const {
|
|
|
1037
1043
|
};
|
|
1038
1044
|
}
|
|
1039
1045
|
|
|
1040
|
-
// Update codemirror if mounted
|
|
1041
|
-
//
|
|
1042
|
-
if (!isRtcEnabled()) {
|
|
1046
|
+
// Update codemirror if mounted. RTC-backed cells sync via Loro; others
|
|
1047
|
+
// (e.g. scratchpad) still need an explicit editor update.
|
|
1048
|
+
if (!isRtcEnabled() || !canUseRtc(cellId)) {
|
|
1043
1049
|
const cellHandle = nextState.cellHandles[cellId].current;
|
|
1044
1050
|
if (cellHandle?.editorViewOrNull) {
|
|
1045
1051
|
updateEditorCodeFromPython(cellHandle.editorViewOrNull, code);
|
|
@@ -1698,6 +1704,13 @@ export {
|
|
|
1698
1704
|
notebookAtom,
|
|
1699
1705
|
};
|
|
1700
1706
|
|
|
1707
|
+
/** Actions for dispatching outside of React. */
|
|
1708
|
+
const imperativeActions = createActions((action) => {
|
|
1709
|
+
store.set(notebookAtom, (state) => reducer(state, action));
|
|
1710
|
+
});
|
|
1711
|
+
|
|
1712
|
+
export const addLogs = imperativeActions.addLogs;
|
|
1713
|
+
|
|
1701
1714
|
/// ATOMS
|
|
1702
1715
|
|
|
1703
1716
|
export const cellIdsAtom = atom((get) => get(notebookAtom).cellIds);
|
package/src/core/cells/logs.ts
CHANGED
|
@@ -13,11 +13,21 @@ import { tracebackModalAtom } from "../errors/traceback-atom";
|
|
|
13
13
|
import React from "react";
|
|
14
14
|
import { ToastAction } from "@/components/ui/toast";
|
|
15
15
|
|
|
16
|
+
/** Logs come from a cell, or from a language server, which has no cell. */
|
|
17
|
+
export type LogSource =
|
|
18
|
+
| { type: "cell"; cellId: CellId }
|
|
19
|
+
| { type: "lsp"; name: string };
|
|
20
|
+
|
|
16
21
|
export interface CellLog {
|
|
22
|
+
/** Unix timestamp, in seconds. */
|
|
17
23
|
timestamp: number;
|
|
18
24
|
level: "stdout" | "stderr";
|
|
19
25
|
message: string;
|
|
20
|
-
|
|
26
|
+
source: LogSource;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function logSourceLabel(source: LogSource): string {
|
|
30
|
+
return source.type === "cell" ? source.cellId : `lsp:${source.name}`;
|
|
21
31
|
}
|
|
22
32
|
|
|
23
33
|
let didAlreadyToastError = false;
|
|
@@ -48,7 +58,7 @@ export function getCellLogsForMessage(cell: CellMessage): CellLog[] {
|
|
|
48
58
|
timestamp: output.timestamp || Date.now(),
|
|
49
59
|
level: isError ? "stderr" : "stdout",
|
|
50
60
|
message: message,
|
|
51
|
-
cellId: cell.cell_id,
|
|
61
|
+
source: { type: "cell", cellId: cell.cell_id },
|
|
52
62
|
});
|
|
53
63
|
break;
|
|
54
64
|
}
|
|
@@ -71,7 +81,7 @@ export function getCellLogsForMessage(cell: CellMessage): CellLog[] {
|
|
|
71
81
|
cell.output.data.forEach((error) => {
|
|
72
82
|
CellLogLogger.log({
|
|
73
83
|
level: "stderr",
|
|
74
|
-
cellId: cell.cell_id,
|
|
84
|
+
source: { type: "cell", cellId: cell.cell_id },
|
|
75
85
|
timestamp: cell.timestamp ?? 0,
|
|
76
86
|
message: JSON.stringify(error),
|
|
77
87
|
});
|
|
@@ -150,7 +160,7 @@ const CellLogLogger = {
|
|
|
150
160
|
`%c[${status}]`,
|
|
151
161
|
`color:${color}; padding:2px 0; border-radius:2px; font-weight:bold`,
|
|
152
162
|
`[${formatLogTimestamp(payload.timestamp)}]`,
|
|
153
|
-
`(${payload.
|
|
163
|
+
`(${logSourceLabel(payload.source)}) ${payload.message}`,
|
|
154
164
|
);
|
|
155
165
|
},
|
|
156
166
|
};
|
|
@@ -50,6 +50,7 @@ import type { HotkeyProvider } from "../hotkeys/hotkeys";
|
|
|
50
50
|
import { requestEditCompletion } from "./ai/request";
|
|
51
51
|
import { cellBundle } from "./cells/extensions";
|
|
52
52
|
import type { CodemirrorCellActions } from "./cells/state";
|
|
53
|
+
import { codeLensBundle } from "./code-lens/extension";
|
|
53
54
|
import { jupyterHelpExtension } from "./compat/jupyter";
|
|
54
55
|
import { hintTooltip } from "./completion/hints";
|
|
55
56
|
import { completionKeymap } from "./completion/keymap";
|
|
@@ -198,6 +199,8 @@ export const setupCodeMirror = (opts: CodeMirrorSetupOpts): Extension[] => {
|
|
|
198
199
|
cellId,
|
|
199
200
|
displayConfig.reference_highlighting ?? true,
|
|
200
201
|
),
|
|
202
|
+
// Inline icons linking datasources/buckets/caches to their panels
|
|
203
|
+
codeLensBundle(cellId),
|
|
201
204
|
];
|
|
202
205
|
};
|
|
203
206
|
|