@marimo-team/islands 0.23.14-dev6 → 0.23.14-dev61
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-Du3_nUzI.js → ConnectedDataExplorerComponent-CU4fZJzG.js} +4 -4
- package/dist/assets/__vite-browser-external-BQCLNwri.js +1 -0
- package/dist/assets/{worker-DEDLIQQV.js → worker-DAWRHcPq.js} +2 -2
- package/dist/{chat-ui-BZxLHwyD.js → chat-ui-DinOvbWu.js} +3248 -3044
- package/dist/{click-outside-container-BDd67_1U.js → click-outside-container-BLPjMamz.js} +141 -113
- package/dist/{code-visibility-C90Am97q.js → code-visibility-EvXXzjfW.js} +1169 -960
- package/dist/data-grid-overlay-editor-C6lxUJp-.js +136 -0
- package/dist/{dist-D_bzzWBm.js → dist-Bf9f8MuT.js} +3 -3
- package/dist/{formats-d6MhLuQ9.js → formats-Dzx4J_z1.js} +1 -1
- package/dist/{glide-data-editor-DkzAInWG.js → glide-data-editor-CjTu7ukN.js} +2084 -1889
- package/dist/{html-to-image-CGp_08St.js → html-to-image-_wGfk8V-.js} +2389 -2322
- package/dist/{input-CbEz_aj_.js → input-DtsN7xm-.js} +1 -1
- package/dist/main.js +3630 -1801
- package/dist/{mermaid-CJW9vIyO.js → mermaid-BYqXy_NE.js} +2 -2
- package/dist/{number-overlay-editor-D-a0qCT8.js → number-overlay-editor-BLJXvX9c.js} +1 -1
- package/dist/{process-output-R6JsYrv3.js → process-output-Mh4UrjwM.js} +1 -1
- package/dist/{reveal-component-C_u9FY4_.js → reveal-component-CKfV5wlk.js} +600 -596
- package/dist/{spec-Bv-XlYiv.js → spec-Cz-Bj1JI.js} +1 -1
- package/dist/style.css +1 -1
- package/dist/{toDate-D-l5s8nn.js → toDate-CWNNlFEX.js} +15 -7
- package/dist/{useAsyncData-1Dhzjfwf.js → useAsyncData-KfHB8wQR.js} +1 -1
- package/dist/{useDeepCompareMemoize-CDWT3BDz.js → useDeepCompareMemoize-nJMtxhm4.js} +1 -1
- package/dist/{useLifecycle-AHlswLw-.js → useLifecycle-DegSo0lV.js} +1 -1
- package/dist/{useTheme-BrYvK-_A.js → useTheme-6eZ3GOTS.js} +31 -26
- package/dist/{vega-component-Pk6lyc_a.js → vega-component-DzyyM9fc.js} +5 -5
- package/package.json +4 -4
- package/src/__mocks__/requests.ts +1 -0
- package/src/__tests__/CellStatus.test.tsx +1 -2
- package/src/components/app-config/user-config-form.tsx +52 -0
- package/src/components/chat/acp/agent-panel.tsx +35 -1
- package/src/components/chat/chat-panel.tsx +68 -29
- package/src/components/data-table/__tests__/column-explorer.test.tsx +25 -0
- package/src/components/data-table/__tests__/column-visibility-dropdown.test.tsx +60 -3
- package/src/components/data-table/charts/__tests__/altair-generator.test.ts +24 -0
- package/src/components/data-table/charts/__tests__/merge-index-fields.test.ts +33 -0
- package/src/components/data-table/charts/chart-spec/altair-generator.ts +4 -1
- package/src/components/data-table/charts/charts.tsx +23 -1
- package/src/components/data-table/column-explorer-panel/column-explorer.tsx +33 -12
- package/src/components/data-table/column-visibility-dropdown.tsx +15 -0
- package/src/components/debugger/debugger-code.tsx +7 -2
- package/src/components/editor/actions/useNotebookActions.tsx +2 -2
- package/src/components/editor/cell/CellStatus.tsx +1 -20
- package/src/components/editor/cell/PendingDeleteConfirmation.tsx +1 -1
- package/src/components/editor/cell/cell-context-menu.tsx +2 -2
- package/src/components/editor/chrome/panels/snippets-panel.tsx +3 -3
- package/src/components/editor/chrome/wrapper/__tests__/useOpenAiAssistant.test.ts +36 -0
- package/src/components/editor/chrome/wrapper/footer-items/pyodide-status.tsx +6 -36
- package/src/components/editor/chrome/wrapper/useAiPanel.ts +3 -1
- package/src/components/editor/chrome/wrapper/useOpenAiAssistant.ts +88 -0
- package/src/components/editor/code/__tests__/readonly-python-code.test.tsx +79 -0
- package/src/components/editor/code/readonly-python-code.tsx +60 -33
- package/src/components/editor/errors/__tests__/auto-fix.test.ts +23 -0
- package/src/components/editor/errors/auto-fix.tsx +88 -34
- package/src/components/editor/errors/fix-mode.ts +1 -1
- package/src/components/editor/notebook-cell.tsx +7 -0
- package/src/components/editor/output/ImageOutput.tsx +20 -6
- package/src/components/editor/output/MarimoTracebackOutput.tsx +28 -2
- package/src/components/editor/output/__tests__/ImageOutput.test.tsx +53 -0
- package/src/components/editor/output/__tests__/traceback.test.tsx +14 -6
- package/src/components/editor/renderers/grid-layout/grid-layout.tsx +7 -2
- package/src/components/editor/renderers/vertical-layout/vertical-layout.tsx +7 -3
- package/src/components/lifecycle/ProgressiveBoundary.tsx +42 -0
- package/src/components/lifecycle/RuntimeStatusBadge.tsx +59 -0
- package/src/components/lifecycle/__tests__/ProgressiveBoundary.test.tsx +147 -0
- package/src/components/lifecycle/__tests__/RuntimeStatusBadge.test.tsx +72 -0
- package/src/components/slides/__tests__/slide.test.tsx +33 -0
- package/src/components/slides/minimap.tsx +7 -1
- package/src/components/slides/reveal-component.tsx +3 -0
- package/src/components/slides/slide-cell-view.tsx +10 -6
- package/src/components/slides/slide.tsx +16 -13
- package/src/components/tracing/tracing.tsx +2 -1
- package/src/core/ai/config.ts +2 -2
- package/src/core/ai/context/__tests__/registry.test.ts +133 -3
- package/src/core/ai/context/providers/__tests__/datasource.test.ts +3 -1
- package/src/core/ai/context/providers/__tests__/error.test.ts +200 -15
- package/src/core/ai/context/providers/datasource.ts +27 -2
- package/src/core/ai/context/providers/error.ts +226 -36
- package/src/core/ai/context/registry.ts +77 -43
- package/src/core/ai/state.ts +11 -0
- package/src/core/cells/__tests__/cell.test.ts +39 -0
- package/src/core/cells/__tests__/readonly-code-display.test.ts +46 -0
- package/src/core/cells/cell.ts +5 -3
- package/src/core/cells/readonly-code-display.ts +35 -0
- package/src/core/codemirror/__tests__/setup.test.ts +33 -1
- package/src/core/codemirror/ai/resources.ts +15 -10
- package/src/core/codemirror/cells/__tests__/debugger-decorations.test.ts +185 -0
- package/src/core/codemirror/cells/__tests__/debugger-state.test.ts +224 -0
- package/src/core/codemirror/cells/__tests__/line-timing-decorations.test.ts +126 -0
- package/src/core/codemirror/cells/debugger-decorations.ts +188 -0
- package/src/core/codemirror/cells/debugger-state.ts +133 -0
- package/src/core/codemirror/cells/extensions.ts +32 -4
- package/src/core/codemirror/cells/line-timing-decorations.ts +165 -0
- package/src/core/codemirror/go-to-definition/__tests__/utils.test.ts +75 -3
- package/src/core/codemirror/go-to-definition/extension.ts +3 -3
- package/src/core/codemirror/go-to-definition/underline.ts +5 -14
- package/src/core/codemirror/go-to-definition/utils.ts +49 -1
- package/src/core/codemirror/utils.ts +15 -0
- package/src/core/config/__tests__/config-schema.test.ts +17 -0
- package/src/core/config/config-schema.ts +1 -0
- package/src/core/config/config.ts +10 -0
- package/src/core/config/feature-flag.tsx +4 -0
- package/src/core/edit-app.tsx +8 -5
- package/src/core/errors/errors.ts +2 -1
- package/src/core/islands/bootstrap.ts +4 -3
- package/src/core/islands/bridge.ts +1 -0
- package/src/core/lifecycle/__tests__/render-policy.test.ts +247 -0
- package/src/core/lifecycle/render-policy.ts +125 -0
- package/src/core/mime.ts +11 -4
- package/src/core/network/__tests__/api.test.ts +17 -0
- package/src/core/network/__tests__/requests-toasting.test.tsx +46 -0
- package/src/core/network/api.ts +17 -6
- package/src/core/network/requests-lazy.ts +1 -0
- package/src/core/network/requests-network.ts +8 -0
- package/src/core/network/requests-static.ts +1 -0
- package/src/core/network/requests-toasting.tsx +10 -1
- package/src/core/network/types.ts +2 -0
- package/src/core/run-app.tsx +27 -23
- package/src/core/runtime/__tests__/adapter.test.ts +160 -0
- package/src/core/runtime/__tests__/runtime.test.ts +45 -0
- package/src/core/runtime/adapter.ts +182 -0
- package/src/core/runtime/runtime.ts +30 -16
- package/src/core/wasm/PyodideLoader.tsx +20 -62
- package/src/core/wasm/bridge.ts +13 -4
- package/src/core/wasm/state.ts +8 -19
- package/src/core/websocket/__tests__/useMarimoKernelConnection.test.ts +4 -0
- package/src/core/websocket/__tests__/useWebSocket.test.ts +49 -0
- package/src/core/websocket/transports/__tests__/sse.test.ts +338 -0
- package/src/core/websocket/transports/sse.ts +308 -0
- package/src/core/websocket/useMarimoKernelConnection.tsx +58 -7
- package/src/core/websocket/useWebSocket.tsx +12 -2
- package/src/css/app/codemirror.css +14 -0
- package/src/hooks/__tests__/useDelayElapsed.test.tsx +55 -0
- package/src/hooks/useDelayElapsed.ts +27 -0
- package/src/mount.tsx +3 -3
- package/src/plugins/core/RenderHTML.tsx +35 -1
- package/src/plugins/core/__test__/RenderHTML.test.ts +44 -0
- package/src/plugins/impl/DataTablePlugin.tsx +1 -0
- package/src/plugins/impl/anywidget/AnyWidgetPlugin.tsx +33 -254
- package/src/plugins/impl/anywidget/__tests__/AnyWidgetPlugin.test.tsx +116 -174
- package/src/plugins/impl/anywidget/__tests__/host.test.ts +313 -0
- package/src/plugins/impl/anywidget/__tests__/model-proxy.test.ts +158 -0
- package/src/plugins/impl/anywidget/__tests__/model.test.ts +8 -177
- package/src/plugins/impl/anywidget/__tests__/registry.test.ts +708 -0
- package/src/plugins/impl/anywidget/__tests__/resolve-widget.test.ts +132 -0
- package/src/plugins/impl/anywidget/__tests__/widget-binding.test.ts +305 -133
- package/src/plugins/impl/anywidget/__tests__/widget-ref.test.ts +28 -0
- package/src/plugins/impl/anywidget/host.ts +54 -0
- package/src/plugins/impl/anywidget/model-proxy.ts +70 -0
- package/src/plugins/impl/anywidget/model.ts +59 -239
- package/src/plugins/impl/anywidget/registry.ts +268 -0
- package/src/plugins/impl/anywidget/resolve-widget.ts +138 -0
- package/src/plugins/impl/anywidget/runtime.ts +422 -0
- package/src/plugins/impl/anywidget/types.ts +14 -0
- package/src/plugins/impl/anywidget/widget-binding.ts +280 -119
- package/src/plugins/impl/anywidget/widget-ref.ts +29 -0
- package/src/plugins/impl/data-editor/__tests__/glide-data-editor.test.tsx +187 -0
- package/src/plugins/impl/data-editor/glide-data-editor.tsx +6 -0
- package/src/plugins/impl/data-editor/glide-portal.tsx +73 -0
- package/src/plugins/impl/mpl-interactive/MplInteractivePlugin.tsx +3 -2
- package/src/plugins/impl/mpl-interactive/__tests__/MplInteractivePlugin.test.tsx +16 -13
- package/src/plugins/impl/mpl-interactive/mpl-websocket-shim.ts +1 -1
- package/src/utils/errors.ts +15 -0
- package/src/utils/time.ts +20 -0
- package/dist/assets/__vite-browser-external-DAm_YW43.js +0 -1
- package/dist/data-grid-overlay-editor-mfEJ5475.js +0 -128
- package/src/components/editor/chrome/panels/__tests__/snippet-display.test.ts +0 -22
- package/src/components/editor/chrome/panels/snippet-display.ts +0 -27
- package/src/core/ai/context/providers/__tests__/__snapshots__/error.test.ts.snap +0 -3
- package/src/core/wasm/__tests__/PyodideLoader.test.ts +0 -72
- package/src/core/wasm/__tests__/state.test.ts +0 -124
- package/src/plugins/impl/anywidget/schemas.ts +0 -32
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import { fireEvent, render, screen } from "@testing-library/react";
|
|
4
|
+
import { beforeAll, describe, expect, it } from "vitest";
|
|
5
|
+
import { SetupMocks } from "@/__mocks__/common";
|
|
6
|
+
import { TooltipProvider } from "@/components/ui/tooltip";
|
|
7
|
+
import { ReadonlyCode } from "../readonly-python-code";
|
|
8
|
+
|
|
9
|
+
beforeAll(() => {
|
|
10
|
+
SetupMocks.resizeObserver();
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* The only button is the show/hide toggle: copy is disabled and the
|
|
15
|
+
* insert-cell button is off by default.
|
|
16
|
+
*/
|
|
17
|
+
function renderReadonly(props: { initiallyHideCode?: boolean }) {
|
|
18
|
+
return render(
|
|
19
|
+
<TooltipProvider>
|
|
20
|
+
<ReadonlyCode code="x = 1" showCopyCode={false} {...props} />
|
|
21
|
+
</TooltipProvider>,
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function isCollapsed(root: ParentNode) {
|
|
26
|
+
return root.querySelector(".cm")?.classList.contains("opacity-20") ?? false;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
describe("ReadonlyCode", () => {
|
|
30
|
+
it("starts collapsed when initiallyHideCode is true", () => {
|
|
31
|
+
const { container } = renderReadonly({ initiallyHideCode: true });
|
|
32
|
+
expect(isCollapsed(container)).toBe(true);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("starts expanded when initiallyHideCode is false", () => {
|
|
36
|
+
const { container } = renderReadonly({ initiallyHideCode: false });
|
|
37
|
+
expect(isCollapsed(container)).toBe(false);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("starts expanded when initiallyHideCode is unset", () => {
|
|
41
|
+
const { container } = renderReadonly({});
|
|
42
|
+
expect(isCollapsed(container)).toBe(false);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("toggles visibility locally on click", () => {
|
|
46
|
+
const { container } = renderReadonly({ initiallyHideCode: true });
|
|
47
|
+
const toggle = screen.getByRole("button");
|
|
48
|
+
|
|
49
|
+
fireEvent.click(toggle);
|
|
50
|
+
expect(isCollapsed(container)).toBe(false);
|
|
51
|
+
|
|
52
|
+
fireEvent.click(toggle);
|
|
53
|
+
expect(isCollapsed(container)).toBe(true);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("keeps each instance's visibility independent", () => {
|
|
57
|
+
const { container } = render(
|
|
58
|
+
<TooltipProvider>
|
|
59
|
+
<ReadonlyCode
|
|
60
|
+
code="a = 1"
|
|
61
|
+
showCopyCode={false}
|
|
62
|
+
initiallyHideCode={true}
|
|
63
|
+
/>
|
|
64
|
+
<ReadonlyCode
|
|
65
|
+
code="b = 2"
|
|
66
|
+
showCopyCode={false}
|
|
67
|
+
initiallyHideCode={true}
|
|
68
|
+
/>
|
|
69
|
+
</TooltipProvider>,
|
|
70
|
+
);
|
|
71
|
+
const [firstToggle] = screen.getAllByRole("button");
|
|
72
|
+
const [first, second] = container.querySelectorAll(".cm");
|
|
73
|
+
|
|
74
|
+
fireEvent.click(firstToggle);
|
|
75
|
+
|
|
76
|
+
expect(first.classList.contains("opacity-20")).toBe(false);
|
|
77
|
+
expect(second.classList.contains("opacity-20")).toBe(true);
|
|
78
|
+
});
|
|
79
|
+
});
|
|
@@ -1,17 +1,25 @@
|
|
|
1
1
|
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
2
|
|
|
3
|
+
import { markdown } from "@codemirror/lang-markdown";
|
|
3
4
|
import { sql } from "@codemirror/lang-sql";
|
|
5
|
+
import {
|
|
6
|
+
defaultHighlightStyle,
|
|
7
|
+
syntaxHighlighting,
|
|
8
|
+
} from "@codemirror/language";
|
|
4
9
|
import CodeMirror, {
|
|
5
10
|
EditorView,
|
|
6
11
|
type ReactCodeMirrorProps,
|
|
7
12
|
} from "@uiw/react-codemirror";
|
|
8
13
|
import { CopyIcon, EyeIcon, EyeOffIcon, PlusIcon } from "lucide-react";
|
|
9
|
-
import { memo, useState } from "react";
|
|
14
|
+
import { memo, useMemo, useState } from "react";
|
|
10
15
|
import { useAddCodeToNewCell } from "@/components/editor/cell/useAddCell";
|
|
11
16
|
import { Button } from "@/components/ui/button";
|
|
12
17
|
import { Tooltip } from "@/components/ui/tooltip";
|
|
13
18
|
import { toast } from "@/components/ui/use-toast";
|
|
19
|
+
import type { LanguageAdapterType } from "@/core/codemirror/language/types";
|
|
14
20
|
import { customPythonLanguageSupport } from "@/core/codemirror/language/languages/python";
|
|
21
|
+
import { darkTheme } from "@/core/codemirror/theme/dark";
|
|
22
|
+
import { lightTheme } from "@/core/codemirror/theme/light";
|
|
15
23
|
import { useTheme } from "@/theme/useTheme";
|
|
16
24
|
import { cn } from "@/utils/cn";
|
|
17
25
|
import { copyToClipboard } from "@/utils/copy";
|
|
@@ -22,6 +30,18 @@ const pythonExtensions = [
|
|
|
22
30
|
EditorView.lineWrapping,
|
|
23
31
|
];
|
|
24
32
|
const sqlExtensions = [sql(), EditorView.lineWrapping];
|
|
33
|
+
const markdownExtensions = [markdown(), EditorView.lineWrapping];
|
|
34
|
+
|
|
35
|
+
function readonlyCodeExtensions(language: LanguageAdapterType) {
|
|
36
|
+
switch (language) {
|
|
37
|
+
case "sql":
|
|
38
|
+
return sqlExtensions;
|
|
39
|
+
case "markdown":
|
|
40
|
+
return markdownExtensions;
|
|
41
|
+
default:
|
|
42
|
+
return pythonExtensions;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
25
45
|
|
|
26
46
|
/**
|
|
27
47
|
* A readonly code component that can be used to display code in a readonly state.
|
|
@@ -42,7 +62,7 @@ export const ReadonlyCode = memo(
|
|
|
42
62
|
showHideCode?: boolean;
|
|
43
63
|
showCopyCode?: boolean;
|
|
44
64
|
insertNewCell?: boolean;
|
|
45
|
-
language?:
|
|
65
|
+
language?: LanguageAdapterType;
|
|
46
66
|
} & ReactCodeMirrorProps,
|
|
47
67
|
) => {
|
|
48
68
|
const { theme } = useTheme();
|
|
@@ -56,35 +76,41 @@ export const ReadonlyCode = memo(
|
|
|
56
76
|
language = "python",
|
|
57
77
|
...rest
|
|
58
78
|
} = props;
|
|
59
|
-
const [hideCode, setHideCode] = useState(initiallyHideCode);
|
|
79
|
+
const [hideCode, setHideCode] = useState(!!initiallyHideCode);
|
|
80
|
+
|
|
81
|
+
const extensions = useMemo(
|
|
82
|
+
() => [
|
|
83
|
+
theme === "dark" ? darkTheme : lightTheme,
|
|
84
|
+
syntaxHighlighting(defaultHighlightStyle, { fallback: true }),
|
|
85
|
+
...readonlyCodeExtensions(language),
|
|
86
|
+
],
|
|
87
|
+
[theme, language],
|
|
88
|
+
);
|
|
60
89
|
|
|
61
90
|
return (
|
|
62
91
|
<div
|
|
63
92
|
className={cn(
|
|
64
|
-
"relative hover-actions-parent w-full overflow-hidden",
|
|
93
|
+
"relative hover-actions-parent w-full overflow-hidden pb-1",
|
|
65
94
|
className,
|
|
66
95
|
)}
|
|
67
96
|
>
|
|
68
|
-
{showHideCode && hideCode && (
|
|
69
|
-
<HideCodeButton
|
|
70
|
-
tooltip="Show code"
|
|
71
|
-
onClick={() => setHideCode(false)}
|
|
72
|
-
/>
|
|
73
|
-
)}
|
|
74
97
|
<div className="absolute top-0 right-0 my-1 mx-2 z-10 hover-action flex gap-2">
|
|
75
98
|
{showCopyCode && <CopyButton text={code} />}
|
|
76
99
|
{insertNewCell && <InsertNewCell code={code} />}
|
|
77
|
-
{showHideCode &&
|
|
78
|
-
<
|
|
100
|
+
{showHideCode && (
|
|
101
|
+
<ToggleCodeButton
|
|
102
|
+
hidden={hideCode}
|
|
103
|
+
onClick={() => setHideCode((prev) => !prev)}
|
|
104
|
+
/>
|
|
79
105
|
)}
|
|
80
106
|
</div>
|
|
81
107
|
<CodeMirror
|
|
82
108
|
{...rest}
|
|
83
109
|
className={cn("cm", hideCode && "opacity-20 h-8 overflow-hidden")}
|
|
84
|
-
theme=
|
|
110
|
+
theme="none"
|
|
85
111
|
height="100%"
|
|
86
|
-
editable={
|
|
87
|
-
extensions={
|
|
112
|
+
editable={false}
|
|
113
|
+
extensions={extensions}
|
|
88
114
|
value={code}
|
|
89
115
|
readOnly={true}
|
|
90
116
|
/>
|
|
@@ -102,42 +128,42 @@ const CopyButton = (props: { text: string }) => {
|
|
|
102
128
|
|
|
103
129
|
return (
|
|
104
130
|
<Tooltip content="Copy code" usePortal={false}>
|
|
105
|
-
<Button
|
|
131
|
+
<Button
|
|
132
|
+
onClick={copy}
|
|
133
|
+
size="xs"
|
|
134
|
+
className="py-0"
|
|
135
|
+
variant="secondary"
|
|
136
|
+
aria-label="Copy code"
|
|
137
|
+
>
|
|
106
138
|
<CopyIcon size={14} strokeWidth={1.5} />
|
|
107
139
|
</Button>
|
|
108
140
|
</Tooltip>
|
|
109
141
|
);
|
|
110
142
|
};
|
|
111
143
|
|
|
112
|
-
const
|
|
144
|
+
const ToggleCodeButton = (props: { hidden: boolean; onClick: () => void }) => {
|
|
113
145
|
return (
|
|
114
|
-
<Tooltip
|
|
146
|
+
<Tooltip
|
|
147
|
+
content={props.hidden ? "Show code" : "Hide code"}
|
|
148
|
+
usePortal={false}
|
|
149
|
+
>
|
|
115
150
|
<Button
|
|
116
151
|
onClick={props.onClick}
|
|
152
|
+
aria-label={props.hidden ? "Show code" : "Hide code"}
|
|
117
153
|
size="xs"
|
|
118
154
|
className="py-0"
|
|
119
155
|
variant="secondary"
|
|
120
156
|
>
|
|
121
|
-
|
|
157
|
+
{props.hidden ? (
|
|
158
|
+
<EyeIcon size={14} strokeWidth={1.5} />
|
|
159
|
+
) : (
|
|
160
|
+
<EyeOffIcon size={14} strokeWidth={1.5} />
|
|
161
|
+
)}
|
|
122
162
|
</Button>
|
|
123
163
|
</Tooltip>
|
|
124
164
|
);
|
|
125
165
|
};
|
|
126
166
|
|
|
127
|
-
export const HideCodeButton = (props: {
|
|
128
|
-
tooltip?: string;
|
|
129
|
-
className?: string;
|
|
130
|
-
onClick: () => void;
|
|
131
|
-
}) => {
|
|
132
|
-
return (
|
|
133
|
-
<div className={props.className} onClick={props.onClick}>
|
|
134
|
-
<Tooltip usePortal={false} content={props.tooltip}>
|
|
135
|
-
<EyeIcon className="hover-action w-5 h-5 text-muted-foreground cursor-pointer absolute left-1/2 top-1/2 transform -translate-x-1/2 -translate-y-1/2 opacity-80 hover:opacity-100 z-20" />
|
|
136
|
-
</Tooltip>
|
|
137
|
-
</div>
|
|
138
|
-
);
|
|
139
|
-
};
|
|
140
|
-
|
|
141
167
|
const InsertNewCell = (props: { code: string }) => {
|
|
142
168
|
const addCodeToNewCell = useAddCodeToNewCell();
|
|
143
169
|
|
|
@@ -152,6 +178,7 @@ const InsertNewCell = (props: { code: string }) => {
|
|
|
152
178
|
size="xs"
|
|
153
179
|
className="py-0"
|
|
154
180
|
variant="secondary"
|
|
181
|
+
aria-label="Add code to notebook"
|
|
155
182
|
>
|
|
156
183
|
<PlusIcon size={14} strokeWidth={1.5} />
|
|
157
184
|
</Button>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import { describe, expect, it } from "vitest";
|
|
4
|
+
import { cellId } from "@/__tests__/branded";
|
|
5
|
+
import { buildFixInChatPrompt } from "../auto-fix";
|
|
6
|
+
|
|
7
|
+
describe("buildFixInChatPrompt", () => {
|
|
8
|
+
it("references the per-cell error context URI", () => {
|
|
9
|
+
expect(buildFixInChatPrompt(cellId("cell-1"))).toBe(
|
|
10
|
+
"@error://cell-1\n\nPlease fix this error.",
|
|
11
|
+
);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it("falls back to inline error text when no cell id", () => {
|
|
15
|
+
expect(buildFixInChatPrompt(undefined, "ValueError: boom")).toBe(
|
|
16
|
+
"My code gives the following error. Please fix it:\n\nValueError: boom",
|
|
17
|
+
);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("uses a generic prompt when no cell id and no error text", () => {
|
|
21
|
+
expect(buildFixInChatPrompt(undefined)).toBe("Please fix this error.");
|
|
22
|
+
});
|
|
23
|
+
});
|
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
2
|
|
|
3
3
|
import { useAtomValue, useSetAtom, useStore } from "jotai";
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
CheckIcon,
|
|
6
|
+
ChevronDownIcon,
|
|
7
|
+
HatGlasses,
|
|
8
|
+
type LucideIcon,
|
|
9
|
+
SparklesIcon,
|
|
10
|
+
WrenchIcon,
|
|
11
|
+
} from "lucide-react";
|
|
5
12
|
import { Button } from "@/components/ui/button";
|
|
6
13
|
import {
|
|
7
14
|
DropdownMenu,
|
|
@@ -17,8 +24,23 @@ import { aiFeaturesEnabledAtom } from "@/core/config/config";
|
|
|
17
24
|
import { getAutoFixes } from "@/core/errors/errors";
|
|
18
25
|
import type { MarimoError } from "@/core/kernel/messages";
|
|
19
26
|
import { cn } from "@/utils/cn";
|
|
27
|
+
import { useOpenAiAssistant } from "../chrome/wrapper/useOpenAiAssistant";
|
|
20
28
|
import { type FixMode, useFixMode } from "./fix-mode";
|
|
21
29
|
|
|
30
|
+
export function buildFixInChatPrompt(
|
|
31
|
+
cellId: CellId | undefined,
|
|
32
|
+
fallbackErrorText?: string,
|
|
33
|
+
): string {
|
|
34
|
+
if (cellId != null) {
|
|
35
|
+
return `@error://${cellId}\n\nPlease fix this error.`;
|
|
36
|
+
}
|
|
37
|
+
const errorText = fallbackErrorText?.trim();
|
|
38
|
+
if (errorText) {
|
|
39
|
+
return `My code gives the following error. Please fix it:\n\n${errorText}`;
|
|
40
|
+
}
|
|
41
|
+
return "Please fix this error.";
|
|
42
|
+
}
|
|
43
|
+
|
|
22
44
|
export const AutoFixButton = ({
|
|
23
45
|
errors,
|
|
24
46
|
cellId,
|
|
@@ -35,6 +57,7 @@ export const AutoFixButton = ({
|
|
|
35
57
|
getAutoFixes(error, { aiEnabled: aiFeaturesEnabled }),
|
|
36
58
|
);
|
|
37
59
|
const setAiCompletionCell = useSetAtom(aiCompletionCellAtom);
|
|
60
|
+
const openAiAssistant = useOpenAiAssistant();
|
|
38
61
|
|
|
39
62
|
if (autoFixes.length === 0) {
|
|
40
63
|
return null;
|
|
@@ -67,6 +90,12 @@ export const AutoFixButton = ({
|
|
|
67
90
|
editorView?.focus();
|
|
68
91
|
};
|
|
69
92
|
|
|
93
|
+
const openAISidebar = () => {
|
|
94
|
+
openAiAssistant({
|
|
95
|
+
prompt: buildFixInChatPrompt(cellId),
|
|
96
|
+
});
|
|
97
|
+
};
|
|
98
|
+
|
|
70
99
|
return (
|
|
71
100
|
<div className={cn("my-2", className)}>
|
|
72
101
|
{firstFix.fixType === "ai" ? (
|
|
@@ -74,6 +103,7 @@ export const AutoFixButton = ({
|
|
|
74
103
|
tooltip={firstFix.description}
|
|
75
104
|
openPrompt={() => handleFix(false)}
|
|
76
105
|
applyAutofix={() => handleFix(true)}
|
|
106
|
+
openChat={openAISidebar}
|
|
77
107
|
/>
|
|
78
108
|
) : (
|
|
79
109
|
<Tooltip content={firstFix.description} align="start">
|
|
@@ -92,23 +122,53 @@ export const AutoFixButton = ({
|
|
|
92
122
|
);
|
|
93
123
|
};
|
|
94
124
|
|
|
95
|
-
|
|
96
|
-
|
|
125
|
+
interface FixModeConfig {
|
|
126
|
+
Icon: LucideIcon;
|
|
127
|
+
title: string;
|
|
128
|
+
description: string;
|
|
129
|
+
}
|
|
97
130
|
|
|
98
|
-
const
|
|
99
|
-
|
|
131
|
+
const MODE_CONFIG: Record<FixMode, FixModeConfig> = {
|
|
132
|
+
autofix: {
|
|
133
|
+
Icon: WrenchIcon,
|
|
134
|
+
title: "Inline AI Fix",
|
|
135
|
+
description: "Apply AI fixes inline in the cell",
|
|
136
|
+
},
|
|
137
|
+
chat: {
|
|
138
|
+
Icon: HatGlasses,
|
|
139
|
+
title: "Fix with AI assistant",
|
|
140
|
+
description: "Open the AI sidebar to fix",
|
|
141
|
+
},
|
|
142
|
+
prompt: {
|
|
143
|
+
Icon: SparklesIcon,
|
|
144
|
+
title: "Suggest a prompt",
|
|
145
|
+
description: "Edit the prompt before applying",
|
|
146
|
+
},
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
const FIX_MODES: FixMode[] = ["autofix", "prompt", "chat"];
|
|
100
150
|
|
|
101
151
|
export const AIFixButton = ({
|
|
102
152
|
tooltip,
|
|
103
153
|
openPrompt,
|
|
104
154
|
applyAutofix,
|
|
155
|
+
openChat,
|
|
105
156
|
}: {
|
|
106
157
|
tooltip: string;
|
|
107
158
|
openPrompt: () => void;
|
|
108
159
|
applyAutofix: () => void;
|
|
160
|
+
openChat: () => void;
|
|
109
161
|
}) => {
|
|
110
162
|
const { fixMode, setFixMode } = useFixMode();
|
|
111
163
|
|
|
164
|
+
let onAction = openPrompt;
|
|
165
|
+
if (fixMode === "chat") {
|
|
166
|
+
onAction = openChat;
|
|
167
|
+
} else if (fixMode === "autofix") {
|
|
168
|
+
onAction = applyAutofix;
|
|
169
|
+
}
|
|
170
|
+
const { Icon, title } = MODE_CONFIG[fixMode];
|
|
171
|
+
|
|
112
172
|
return (
|
|
113
173
|
<div className="flex">
|
|
114
174
|
<Tooltip content={tooltip} align="start">
|
|
@@ -116,14 +176,10 @@ export const AIFixButton = ({
|
|
|
116
176
|
size="xs"
|
|
117
177
|
variant="outline"
|
|
118
178
|
className="font-normal rounded-r-none border-r-0"
|
|
119
|
-
onClick={
|
|
179
|
+
onClick={onAction}
|
|
120
180
|
>
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
) : (
|
|
124
|
-
<AutofixIcon className="h-3 w-3 mr-2 mb-0.5" />
|
|
125
|
-
)}
|
|
126
|
-
{fixMode === "prompt" ? PromptTitle : AutofixTitle}
|
|
181
|
+
<Icon className="h-3 w-3 mr-2 mb-0.5" />
|
|
182
|
+
{title}
|
|
127
183
|
</Button>
|
|
128
184
|
</Tooltip>
|
|
129
185
|
<DropdownMenu>
|
|
@@ -138,40 +194,38 @@ export const AIFixButton = ({
|
|
|
138
194
|
</Button>
|
|
139
195
|
</DropdownMenuTrigger>
|
|
140
196
|
<DropdownMenuContent align="end" className="w-56">
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
197
|
+
{FIX_MODES.map((mode) => (
|
|
198
|
+
<DropdownMenuItem
|
|
199
|
+
key={mode}
|
|
200
|
+
className="flex items-center gap-2"
|
|
201
|
+
onClick={() => setFixMode(mode)}
|
|
202
|
+
>
|
|
203
|
+
<AiModeItem mode={mode} selected={mode === fixMode} />
|
|
204
|
+
</DropdownMenuItem>
|
|
205
|
+
))}
|
|
149
206
|
</DropdownMenuContent>
|
|
150
207
|
</DropdownMenu>
|
|
151
208
|
</div>
|
|
152
209
|
);
|
|
153
210
|
};
|
|
154
211
|
|
|
155
|
-
const AiModeItem = ({
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
const
|
|
163
|
-
const description =
|
|
164
|
-
mode === "prompt"
|
|
165
|
-
? "Edit the prompt before applying"
|
|
166
|
-
: "Apply AI fixes automatically";
|
|
212
|
+
const AiModeItem = ({
|
|
213
|
+
mode,
|
|
214
|
+
selected,
|
|
215
|
+
}: {
|
|
216
|
+
mode: FixMode;
|
|
217
|
+
selected: boolean;
|
|
218
|
+
}) => {
|
|
219
|
+
const { Icon, title, description } = MODE_CONFIG[mode];
|
|
167
220
|
|
|
168
221
|
return (
|
|
169
|
-
<div className="flex items-center gap-2">
|
|
170
|
-
|
|
222
|
+
<div className="flex items-center gap-2 w-full">
|
|
223
|
+
<Icon className="h-4 w-4 shrink-0" />
|
|
171
224
|
<div className="flex flex-col">
|
|
172
225
|
<span className="font-medium">{title}</span>
|
|
173
226
|
<span className="text-xs text-muted-foreground">{description}</span>
|
|
174
227
|
</div>
|
|
228
|
+
{selected && <CheckIcon className="h-4 w-4 ml-auto shrink-0" />}
|
|
175
229
|
</div>
|
|
176
230
|
);
|
|
177
231
|
};
|
|
@@ -4,7 +4,7 @@ import { useAtom } from "jotai";
|
|
|
4
4
|
import { atomWithStorage } from "jotai/utils";
|
|
5
5
|
import { jotaiJsonStorage } from "@/utils/storage/jotai";
|
|
6
6
|
|
|
7
|
-
export type FixMode = "prompt" | "autofix";
|
|
7
|
+
export type FixMode = "prompt" | "autofix" | "chat";
|
|
8
8
|
|
|
9
9
|
const BASE_KEY = "marimo:ai-autofix-mode";
|
|
10
10
|
|
|
@@ -29,6 +29,7 @@ import { outputIsLoading, outputIsStale } from "@/core/cells/cell";
|
|
|
29
29
|
import { isOutputEmpty } from "@/core/cells/outputs";
|
|
30
30
|
import { useIsPendingCut } from "@/core/cells/pending-cut-service";
|
|
31
31
|
import { autocompletionKeymap } from "@/core/codemirror/cm";
|
|
32
|
+
import { clearCellBreakpoints } from "@/core/codemirror/cells/debugger-state";
|
|
32
33
|
import type { LanguageAdapterType } from "@/core/codemirror/language/types";
|
|
33
34
|
import { CSSClasses } from "@/core/constants";
|
|
34
35
|
import { canCollapseOutline } from "@/core/dom/outline";
|
|
@@ -746,6 +747,9 @@ const EditableCellComponent = ({
|
|
|
746
747
|
onRefactorWithAI={handleRefactorWithAI}
|
|
747
748
|
onClear={() => {
|
|
748
749
|
actions.clearCellConsoleOutput({ cellId });
|
|
750
|
+
// The debugger "Clear" (trashcan) also drops this cell's
|
|
751
|
+
// breakpoints; no-op when none are set.
|
|
752
|
+
clearCellBreakpoints(cellId);
|
|
749
753
|
}}
|
|
750
754
|
onSubmitDebugger={(text, index) => {
|
|
751
755
|
actions.setStdinResponse({
|
|
@@ -1211,6 +1215,9 @@ const SetupCellComponent = ({
|
|
|
1211
1215
|
onRefactorWithAI={handleRefactorWithAI}
|
|
1212
1216
|
onClear={() => {
|
|
1213
1217
|
actions.clearCellConsoleOutput({ cellId });
|
|
1218
|
+
// The debugger "Clear" (trashcan) also drops this cell's
|
|
1219
|
+
// breakpoints; no-op when none are set.
|
|
1220
|
+
clearCellBreakpoints(cellId);
|
|
1214
1221
|
}}
|
|
1215
1222
|
onSubmitDebugger={(text, index) => {
|
|
1216
1223
|
actions.setStdinResponse({
|
|
@@ -17,18 +17,32 @@ export const ImageOutput = ({
|
|
|
17
17
|
height,
|
|
18
18
|
className,
|
|
19
19
|
}: Props): JSX.Element => {
|
|
20
|
-
//
|
|
20
|
+
// Numeric dimensions are set as HTML attributes rather than inline styles.
|
|
21
|
+
// Attributes give the browser the image's intrinsic size and aspect ratio,
|
|
22
|
+
// but can still be overridden by stylesheet rules (`max-width: 100%;
|
|
23
|
+
// height: auto` from preflight), so images shrink proportionally in
|
|
24
|
+
// width-constrained containers like mo.hstack. Inline styles would win
|
|
25
|
+
// over `height: auto` and distort the aspect ratio.
|
|
26
|
+
//
|
|
27
|
+
// String values like "100%" are only valid in CSS, so they go in the
|
|
28
|
+
// style attribute.
|
|
21
29
|
const style: React.CSSProperties = {};
|
|
22
|
-
if (width
|
|
23
|
-
style.width =
|
|
30
|
+
if (typeof width === "string") {
|
|
31
|
+
style.width = width;
|
|
24
32
|
}
|
|
25
|
-
if (height
|
|
26
|
-
style.height =
|
|
33
|
+
if (typeof height === "string") {
|
|
34
|
+
style.height = height;
|
|
27
35
|
}
|
|
28
36
|
|
|
29
37
|
return (
|
|
30
38
|
<span className={className}>
|
|
31
|
-
<img
|
|
39
|
+
<img
|
|
40
|
+
src={src}
|
|
41
|
+
alt={alt}
|
|
42
|
+
width={typeof width === "number" ? width : undefined}
|
|
43
|
+
height={typeof height === "number" ? height : undefined}
|
|
44
|
+
style={style}
|
|
45
|
+
/>
|
|
32
46
|
</span>
|
|
33
47
|
);
|
|
34
48
|
};
|
|
@@ -24,8 +24,10 @@ import { Tooltip } from "@/components/ui/tooltip";
|
|
|
24
24
|
import { getCellEditorView } from "@/core/cells/cells";
|
|
25
25
|
import type { CellId } from "@/core/cells/ids";
|
|
26
26
|
import { SCRATCH_CELL_ID } from "@/core/cells/ids";
|
|
27
|
+
import { toggleBreakpoint } from "@/core/codemirror/cells/debugger-state";
|
|
27
28
|
import { insertDebuggerAtLine } from "@/core/codemirror/editing/debugging";
|
|
28
29
|
import { aiFeaturesEnabledAtom } from "@/core/config/config";
|
|
30
|
+
import { getFeatureFlag } from "@/core/config/feature-flag";
|
|
29
31
|
import { getRequestClient } from "@/core/network/requests";
|
|
30
32
|
import { isStaticNotebook } from "@/core/static/static-state";
|
|
31
33
|
import { isWasm } from "@/core/wasm/utils";
|
|
@@ -41,7 +43,8 @@ import {
|
|
|
41
43
|
extractAllTracebackInfo,
|
|
42
44
|
getTracebackInfo,
|
|
43
45
|
} from "@/utils/traceback";
|
|
44
|
-
import {
|
|
46
|
+
import { useOpenAiAssistant } from "../chrome/wrapper/useOpenAiAssistant";
|
|
47
|
+
import { AIFixButton, buildFixInChatPrompt } from "../errors/auto-fix";
|
|
45
48
|
import { MangledSegments } from "../errors/mangled-local-chip";
|
|
46
49
|
import { CellLinkTraceback } from "../links/cell-link";
|
|
47
50
|
import type { OnRefactorWithAI } from "../Output";
|
|
@@ -71,6 +74,7 @@ export const MarimoTracebackOutput = ({
|
|
|
71
74
|
|
|
72
75
|
const lastTracebackLine = lastLine(traceback);
|
|
73
76
|
const aiFeaturesEnabled = useAtomValue(aiFeaturesEnabledAtom);
|
|
77
|
+
const openAiAssistant = useOpenAiAssistant();
|
|
74
78
|
|
|
75
79
|
// Get last traceback info
|
|
76
80
|
const tracebackInfo = extractAllTracebackInfo(traceback)?.at(0);
|
|
@@ -97,6 +101,12 @@ export const MarimoTracebackOutput = ({
|
|
|
97
101
|
});
|
|
98
102
|
};
|
|
99
103
|
|
|
104
|
+
const openAISidebar = () => {
|
|
105
|
+
openAiAssistant({
|
|
106
|
+
prompt: buildFixInChatPrompt(cellId, lastTracebackLine),
|
|
107
|
+
});
|
|
108
|
+
};
|
|
109
|
+
|
|
100
110
|
return (
|
|
101
111
|
<div className="flex flex-col gap-2 min-w-full w-fit">
|
|
102
112
|
<button
|
|
@@ -126,6 +136,7 @@ export const MarimoTracebackOutput = ({
|
|
|
126
136
|
tooltip="Fix with AI"
|
|
127
137
|
openPrompt={() => handleRefactorWithAI(false)}
|
|
128
138
|
applyAutofix={() => handleRefactorWithAI(true)}
|
|
139
|
+
openChat={openAISidebar}
|
|
129
140
|
/>
|
|
130
141
|
)}
|
|
131
142
|
{showDebugger && (
|
|
@@ -203,7 +214,14 @@ function lastLine(text: string): string {
|
|
|
203
214
|
export const replaceTracebackFilenames = (domNode: DOMNode) => {
|
|
204
215
|
const info = getTracebackInfo(domNode);
|
|
205
216
|
if (info?.kind === "cell") {
|
|
206
|
-
|
|
217
|
+
// With the live debugger enabled, toggle a real gutter breakpoint
|
|
218
|
+
// instead of mutating the user's code with a `breakpoint()` statement.
|
|
219
|
+
const debuggerEnabled = getFeatureFlag("debugger");
|
|
220
|
+
const tooltipContent = debuggerEnabled ? (
|
|
221
|
+
<ToggleBreakpointContent />
|
|
222
|
+
) : (
|
|
223
|
+
<InsertBreakpointContent />
|
|
224
|
+
);
|
|
207
225
|
return (
|
|
208
226
|
<span className="nb">
|
|
209
227
|
<span className="inline-flex items-center">
|
|
@@ -219,6 +237,10 @@ export const replaceTracebackFilenames = (domNode: DOMNode) => {
|
|
|
219
237
|
>
|
|
220
238
|
<BugPlayIcon
|
|
221
239
|
onClick={() => {
|
|
240
|
+
if (debuggerEnabled && !isStaticNotebook()) {
|
|
241
|
+
toggleBreakpoint(info.cellId, info.lineNumber);
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
222
244
|
const view = getCellEditorView(info.cellId);
|
|
223
245
|
if (view) {
|
|
224
246
|
insertDebuggerAtLine(view, info.lineNumber);
|
|
@@ -285,3 +307,7 @@ const InsertBreakpointContent = () => {
|
|
|
285
307
|
</>
|
|
286
308
|
);
|
|
287
309
|
};
|
|
310
|
+
|
|
311
|
+
const ToggleBreakpointContent = () => {
|
|
312
|
+
return <>Toggle a breakpoint at this line</>;
|
|
313
|
+
};
|