@marimo-team/islands 0.23.14-dev5 → 0.23.14-dev51

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 (128) hide show
  1. package/dist/{ConnectedDataExplorerComponent-Du3_nUzI.js → ConnectedDataExplorerComponent-DXBx_nQg.js} +4 -4
  2. package/dist/{chat-ui-BZxLHwyD.js → chat-ui-DXPRhRO2.js} +3248 -3044
  3. package/dist/{click-outside-container-BDd67_1U.js → click-outside-container-BLPjMamz.js} +141 -113
  4. package/dist/{code-visibility-rxZi4Phe.js → code-visibility-BA5VF477.js} +1153 -946
  5. package/dist/data-grid-overlay-editor-C6lxUJp-.js +136 -0
  6. package/dist/{dist-D_bzzWBm.js → dist-Bf9f8MuT.js} +3 -3
  7. package/dist/{formats-d6MhLuQ9.js → formats-WsOgyW_K.js} +1 -1
  8. package/dist/{glide-data-editor-DkzAInWG.js → glide-data-editor-7wxMGXjG.js} +2084 -1889
  9. package/dist/{html-to-image-CGp_08St.js → html-to-image-DOqQBSQc.js} +2367 -2301
  10. package/dist/{input-CbEz_aj_.js → input-BSdZp5Ng.js} +1 -1
  11. package/dist/main.js +2707 -1186
  12. package/dist/{mermaid-CJW9vIyO.js → mermaid-D-HYBMEV.js} +2 -2
  13. package/dist/{number-overlay-editor-D-a0qCT8.js → number-overlay-editor-BLJXvX9c.js} +1 -1
  14. package/dist/{process-output-R6JsYrv3.js → process-output-kGk2Jc2x.js} +1 -1
  15. package/dist/{reveal-component-sEb3Wd1x.js → reveal-component-BOL6pLBN.js} +600 -596
  16. package/dist/{spec-Bv-XlYiv.js → spec-CnTgI25l.js} +1 -1
  17. package/dist/style.css +1 -1
  18. package/dist/{toDate-D-l5s8nn.js → toDate-D1Z7ZXWh.js} +1 -1
  19. package/dist/{useAsyncData-1Dhzjfwf.js → useAsyncData-BMc8itk2.js} +1 -1
  20. package/dist/{useDeepCompareMemoize-CDWT3BDz.js → useDeepCompareMemoize-ZwmDBRDY.js} +1 -1
  21. package/dist/{useLifecycle-AHlswLw-.js → useLifecycle-CxffarYV.js} +1 -1
  22. package/dist/{useTheme-BrYvK-_A.js → useTheme-yGsGEk82.js} +26 -24
  23. package/dist/{vega-component-Pk6lyc_a.js → vega-component-BFJTyykA.js} +5 -5
  24. package/package.json +3 -3
  25. package/src/__mocks__/requests.ts +1 -0
  26. package/src/components/app-config/user-config-form.tsx +26 -0
  27. package/src/components/chat/acp/agent-panel.tsx +35 -1
  28. package/src/components/chat/chat-panel.tsx +68 -29
  29. package/src/components/data-table/__tests__/column-explorer.test.tsx +25 -0
  30. package/src/components/data-table/__tests__/column-visibility-dropdown.test.tsx +60 -3
  31. package/src/components/data-table/charts/__tests__/altair-generator.test.ts +24 -0
  32. package/src/components/data-table/charts/__tests__/merge-index-fields.test.ts +33 -0
  33. package/src/components/data-table/charts/chart-spec/altair-generator.ts +4 -1
  34. package/src/components/data-table/charts/charts.tsx +23 -1
  35. package/src/components/data-table/column-explorer-panel/column-explorer.tsx +33 -12
  36. package/src/components/data-table/column-visibility-dropdown.tsx +15 -0
  37. package/src/components/debugger/debugger-code.tsx +7 -2
  38. package/src/components/editor/actions/useNotebookActions.tsx +2 -2
  39. package/src/components/editor/cell/cell-context-menu.tsx +2 -2
  40. package/src/components/editor/chrome/panels/snippets-panel.tsx +3 -3
  41. package/src/components/editor/chrome/wrapper/__tests__/useOpenAiAssistant.test.ts +36 -0
  42. package/src/components/editor/chrome/wrapper/footer-items/pyodide-status.tsx +6 -36
  43. package/src/components/editor/chrome/wrapper/useAiPanel.ts +3 -1
  44. package/src/components/editor/chrome/wrapper/useOpenAiAssistant.ts +88 -0
  45. package/src/components/editor/code/__tests__/readonly-python-code.test.tsx +79 -0
  46. package/src/components/editor/code/readonly-python-code.tsx +60 -33
  47. package/src/components/editor/errors/__tests__/auto-fix.test.ts +23 -0
  48. package/src/components/editor/errors/auto-fix.tsx +88 -34
  49. package/src/components/editor/errors/fix-mode.ts +1 -1
  50. package/src/components/editor/notebook-cell.tsx +7 -0
  51. package/src/components/editor/output/MarimoTracebackOutput.tsx +28 -2
  52. package/src/components/editor/output/__tests__/traceback.test.tsx +14 -6
  53. package/src/components/editor/renderers/grid-layout/grid-layout.tsx +7 -2
  54. package/src/components/editor/renderers/vertical-layout/vertical-layout.tsx +7 -3
  55. package/src/components/lifecycle/ProgressiveBoundary.tsx +42 -0
  56. package/src/components/lifecycle/RuntimeStatusBadge.tsx +59 -0
  57. package/src/components/lifecycle/__tests__/ProgressiveBoundary.test.tsx +147 -0
  58. package/src/components/lifecycle/__tests__/RuntimeStatusBadge.test.tsx +72 -0
  59. package/src/components/slides/__tests__/slide.test.tsx +33 -0
  60. package/src/components/slides/minimap.tsx +7 -1
  61. package/src/components/slides/reveal-component.tsx +3 -0
  62. package/src/components/slides/slide-cell-view.tsx +10 -6
  63. package/src/components/slides/slide.tsx +16 -13
  64. package/src/core/ai/config.ts +2 -2
  65. package/src/core/ai/context/__tests__/registry.test.ts +133 -3
  66. package/src/core/ai/context/providers/__tests__/datasource.test.ts +3 -1
  67. package/src/core/ai/context/providers/__tests__/error.test.ts +200 -15
  68. package/src/core/ai/context/providers/datasource.ts +27 -2
  69. package/src/core/ai/context/providers/error.ts +226 -36
  70. package/src/core/ai/context/registry.ts +77 -43
  71. package/src/core/ai/state.ts +11 -0
  72. package/src/core/cells/__tests__/cell.test.ts +39 -0
  73. package/src/core/cells/__tests__/readonly-code-display.test.ts +46 -0
  74. package/src/core/cells/cell.ts +5 -3
  75. package/src/core/cells/readonly-code-display.ts +35 -0
  76. package/src/core/codemirror/__tests__/setup.test.ts +33 -1
  77. package/src/core/codemirror/ai/resources.ts +15 -10
  78. package/src/core/codemirror/cells/__tests__/debugger-decorations.test.ts +185 -0
  79. package/src/core/codemirror/cells/__tests__/debugger-state.test.ts +139 -0
  80. package/src/core/codemirror/cells/debugger-decorations.ts +188 -0
  81. package/src/core/codemirror/cells/debugger-state.ts +100 -0
  82. package/src/core/codemirror/cells/extensions.ts +23 -4
  83. package/src/core/codemirror/go-to-definition/__tests__/utils.test.ts +75 -3
  84. package/src/core/codemirror/go-to-definition/extension.ts +3 -3
  85. package/src/core/codemirror/go-to-definition/underline.ts +5 -14
  86. package/src/core/codemirror/go-to-definition/utils.ts +49 -1
  87. package/src/core/codemirror/utils.ts +15 -0
  88. package/src/core/config/feature-flag.tsx +2 -0
  89. package/src/core/edit-app.tsx +8 -5
  90. package/src/core/errors/errors.ts +2 -1
  91. package/src/core/islands/bootstrap.ts +1 -0
  92. package/src/core/islands/bridge.ts +1 -0
  93. package/src/core/lifecycle/__tests__/render-policy.test.ts +247 -0
  94. package/src/core/lifecycle/render-policy.ts +125 -0
  95. package/src/core/mime.ts +11 -4
  96. package/src/core/network/__tests__/api.test.ts +17 -0
  97. package/src/core/network/__tests__/requests-toasting.test.tsx +46 -0
  98. package/src/core/network/api.ts +17 -6
  99. package/src/core/network/requests-lazy.ts +1 -0
  100. package/src/core/network/requests-network.ts +8 -0
  101. package/src/core/network/requests-static.ts +1 -0
  102. package/src/core/network/requests-toasting.tsx +10 -1
  103. package/src/core/network/types.ts +2 -0
  104. package/src/core/run-app.tsx +27 -23
  105. package/src/core/runtime/__tests__/adapter.test.ts +160 -0
  106. package/src/core/runtime/adapter.ts +182 -0
  107. package/src/core/wasm/PyodideLoader.tsx +20 -62
  108. package/src/core/wasm/bridge.ts +13 -4
  109. package/src/core/wasm/state.ts +8 -19
  110. package/src/core/websocket/useMarimoKernelConnection.tsx +17 -0
  111. package/src/css/app/codemirror.css +14 -0
  112. package/src/hooks/__tests__/useDelayElapsed.test.tsx +55 -0
  113. package/src/hooks/useDelayElapsed.ts +27 -0
  114. package/src/plugins/core/RenderHTML.tsx +35 -1
  115. package/src/plugins/core/__test__/RenderHTML.test.ts +44 -0
  116. package/src/plugins/impl/DataTablePlugin.tsx +1 -0
  117. package/src/plugins/impl/anywidget/AnyWidgetPlugin.tsx +53 -2
  118. package/src/plugins/impl/anywidget/__tests__/AnyWidgetPlugin.test.tsx +52 -2
  119. package/src/plugins/impl/data-editor/__tests__/glide-data-editor.test.tsx +187 -0
  120. package/src/plugins/impl/data-editor/glide-data-editor.tsx +6 -0
  121. package/src/plugins/impl/data-editor/glide-portal.tsx +73 -0
  122. package/src/utils/errors.ts +15 -0
  123. package/dist/data-grid-overlay-editor-mfEJ5475.js +0 -128
  124. package/src/components/editor/chrome/panels/__tests__/snippet-display.test.ts +0 -22
  125. package/src/components/editor/chrome/panels/snippet-display.ts +0 -27
  126. package/src/core/ai/context/providers/__tests__/__snapshots__/error.test.ts.snap +0 -3
  127. package/src/core/wasm/__tests__/PyodideLoader.test.ts +0 -72
  128. package/src/core/wasm/__tests__/state.test.ts +0 -124
@@ -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?: "python" | "sql";
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 && !hideCode && (
78
- <EyeCloseButton onClick={() => setHideCode(true)} />
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={theme === "dark" ? "dark" : "light"}
110
+ theme="none"
85
111
  height="100%"
86
- editable={!hideCode}
87
- extensions={language === "python" ? pythonExtensions : sqlExtensions}
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 onClick={copy} size="xs" className="py-0" variant="secondary">
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 EyeCloseButton = (props: { onClick: () => void }) => {
144
+ const ToggleCodeButton = (props: { hidden: boolean; onClick: () => void }) => {
113
145
  return (
114
- <Tooltip content="Hide code" usePortal={false}>
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
- <EyeOffIcon size={14} strokeWidth={1.5} />
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 { ChevronDownIcon, SparklesIcon, WrenchIcon } from "lucide-react";
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
- const PromptIcon = SparklesIcon;
96
- const AutofixIcon = WrenchIcon;
125
+ interface FixModeConfig {
126
+ Icon: LucideIcon;
127
+ title: string;
128
+ description: string;
129
+ }
97
130
 
98
- const PromptTitle = "Suggest a prompt";
99
- const AutofixTitle = "Fix with AI";
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={fixMode === "prompt" ? openPrompt : applyAutofix}
179
+ onClick={onAction}
120
180
  >
121
- {fixMode === "prompt" ? (
122
- <PromptIcon className="h-3 w-3 mr-2 mb-0.5" />
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
- <DropdownMenuItem
142
- className="flex items-center gap-2"
143
- onClick={() => {
144
- setFixMode(fixMode === "prompt" ? "autofix" : "prompt");
145
- }}
146
- >
147
- <AiModeItem mode={fixMode === "prompt" ? "autofix" : "prompt"} />
148
- </DropdownMenuItem>
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 = ({ mode }: { mode: FixMode }) => {
156
- const icon =
157
- mode === "prompt" ? (
158
- <PromptIcon className="h-4 w-4" />
159
- ) : (
160
- <AutofixIcon className="h-4 w-4" />
161
- );
162
- const title = mode === "prompt" ? PromptTitle : AutofixTitle;
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
- {icon}
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({
@@ -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 { AIFixButton } from "../errors/auto-fix";
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
- const tooltipContent = <InsertBreakpointContent />;
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
+ };
@@ -1,11 +1,14 @@
1
1
  /* Copyright 2026 Marimo. All rights reserved. */
2
2
 
3
3
  import { render } from "@testing-library/react";
4
+ import { Provider } from "jotai";
4
5
  import { beforeAll, beforeEach, describe, expect, test, vi } from "vitest";
6
+ import { MockRequestClient } from "@/__mocks__/requests";
5
7
  import { Tracebacks } from "@/__mocks__/tracebacks";
6
8
  import { cellId } from "@/__tests__/branded";
7
9
  import { TooltipProvider } from "@/components/ui/tooltip";
8
10
  import { initialModeAtom } from "@/core/mode";
11
+ import { requestClientAtom } from "@/core/network/requests";
9
12
  import { store } from "@/core/state/jotai";
10
13
  import { renderHTML } from "@/plugins/core/RenderHTML";
11
14
  import {
@@ -20,13 +23,16 @@ describe("traceback component", () => {
20
23
  beforeEach(() => {
21
24
  vi.resetAllMocks();
22
25
  store.set(initialModeAtom, "edit");
26
+ store.set(requestClientAtom, MockRequestClient.create());
23
27
  });
24
28
 
25
29
  test("extracts cell-link", () => {
26
30
  const traceback = (
27
- <TooltipProvider>
28
- <MarimoTracebackOutput traceback={Tracebacks.raw} cellId={cid} />
29
- </TooltipProvider>
31
+ <Provider store={store}>
32
+ <TooltipProvider>
33
+ <MarimoTracebackOutput traceback={Tracebacks.raw} cellId={cid} />
34
+ </TooltipProvider>
35
+ </Provider>
30
36
  );
31
37
  const { unmount, getAllByRole } = render(traceback);
32
38
 
@@ -44,9 +50,11 @@ describe("traceback component", () => {
44
50
 
45
51
  test("renames File to Cell for relevant lines", () => {
46
52
  const traceback = (
47
- <TooltipProvider>
48
- <MarimoTracebackOutput traceback={Tracebacks.raw} cellId={cid} />
49
- </TooltipProvider>
53
+ <Provider store={store}>
54
+ <TooltipProvider>
55
+ <MarimoTracebackOutput traceback={Tracebacks.raw} cellId={cid} />
56
+ </TooltipProvider>
57
+ </Provider>
50
58
  );
51
59
  const { unmount, container } = render(traceback);
52
60
 
@@ -35,7 +35,7 @@ import {
35
35
  import { Label } from "@/components/ui/label";
36
36
  import { NumberField } from "@/components/ui/number-field";
37
37
  import { Switch } from "@/components/ui/switch";
38
- import { outputIsLoading } from "@/core/cells/cell";
38
+ import { outputIsLoading, outputIsStale } from "@/core/cells/cell";
39
39
  import type { CellId } from "@/core/cells/ids";
40
40
  import type { AppMode } from "@/core/mode";
41
41
  import { useIsDragging } from "@/hooks/useIsDragging";
@@ -222,6 +222,7 @@ export const GridLayoutRenderer: React.FC<Props> = ({
222
222
  cellId={cell.id}
223
223
  output={cell.output}
224
224
  status={cell.status}
225
+ stale={outputIsStale(cell, false)}
225
226
  isScrollable={isScrollable}
226
227
  side={side}
227
228
  hidden={cell.errored || cell.interrupted || cell.stopped}
@@ -288,6 +289,7 @@ export const GridLayoutRenderer: React.FC<Props> = ({
288
289
  cellId={cell.id}
289
290
  output={cell.output}
290
291
  status={cell.status}
292
+ stale={outputIsStale(cell, false)}
291
293
  isScrollable={false}
292
294
  hidden={false}
293
295
  />
@@ -358,6 +360,7 @@ export const GridLayoutRenderer: React.FC<Props> = ({
358
360
  output={cell.output}
359
361
  isScrollable={false}
360
362
  status={cell.status}
363
+ stale={outputIsStale(cell, false)}
361
364
  hidden={false}
362
365
  />
363
366
  </div>
@@ -376,6 +379,7 @@ interface GridCellProps extends Pick<CellRuntimeState, "output" | "status"> {
376
379
  hidden: boolean;
377
380
  isScrollable: boolean;
378
381
  side?: GridLayoutCellSide;
382
+ stale: boolean;
379
383
  }
380
384
 
381
385
  const GridCell = memo(
@@ -389,6 +393,7 @@ const GridCell = memo(
389
393
  isScrollable,
390
394
  side,
391
395
  className,
396
+ stale,
392
397
  }: GridCellProps) => {
393
398
  const loading = outputIsLoading(status);
394
399
 
@@ -415,7 +420,7 @@ const GridCell = memo(
415
420
  allowExpand={false}
416
421
  output={output}
417
422
  cellId={cellId}
418
- stale={loading}
423
+ stale={stale}
419
424
  loading={loading}
420
425
  />
421
426
  </div>
@@ -29,6 +29,7 @@ import { outputIsLoading, outputIsStale } from "@/core/cells/cell";
29
29
  import type { CellId } from "@/core/cells/ids";
30
30
  import { isOutputEmpty } from "@/core/cells/outputs";
31
31
  import type { CellData, CellRuntimeState } from "@/core/cells/types";
32
+ import { getReadonlyCodeDisplay } from "@/core/cells/readonly-code-display";
32
33
  import { MarkdownLanguageAdapter } from "@/core/codemirror/language/languages/markdown";
33
34
  import { useResolvedMarimoConfig } from "@/core/config/config";
34
35
  import { CSSClasses, KnownQueryParams } from "@/core/constants";
@@ -384,6 +385,8 @@ const VerticalCell = memo(
384
385
 
385
386
  // Hide the code if it's pure markdown and there's an output, or if the code is empty
386
387
  const hideCode = shouldHideCode(code, output);
388
+ // Only unwrap SQL when the code will actually be rendered.
389
+ const display = hideCode ? null : getReadonlyCodeDisplay(code);
387
390
 
388
391
  return (
389
392
  <div
@@ -393,11 +396,12 @@ const VerticalCell = memo(
393
396
  {...cellDomProps(cellId, name)}
394
397
  >
395
398
  {cellOutputArea === "above" && outputArea}
396
- {!hideCode && (
399
+ {display && (
397
400
  <div className="tray">
398
401
  <ReadonlyCode
399
- initiallyHideCode={config.hide_code || kiosk}
400
- code={code}
402
+ initiallyHideCode={config.hide_code}
403
+ code={display.code}
404
+ language={display.language}
401
405
  />
402
406
  </div>
403
407
  )}