@marimo-team/frontend 0.24.1-dev53 → 0.24.1-dev54
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/assets/{CellStatus-B6CWKORY.js → CellStatus-D6_pLs0B.js} +1 -1
- package/dist/assets/JsonOutput-D0JlHn1Z.js +50 -0
- package/dist/assets/{MarimoErrorOutput-BXh29HCp.js → MarimoErrorOutput-CYOWXi9k.js} +2 -2
- package/dist/assets/{RenderHTML-COTZYPO7.js → RenderHTML-qnAqp9Q0.js} +1 -1
- package/dist/assets/{RunButton-DTUzITFm.js → RunButton-BDkLTxbY.js} +1 -1
- package/dist/assets/add-cell-with-ai-0dqk0f7O.js +77 -0
- package/dist/assets/{add-connection-dialog-CcnJ2LkX.js → add-connection-dialog-CnRoNzHS.js} +1 -1
- package/dist/assets/{agent-panel-B9BFcufg.js → agent-panel-D7mo8YLQ.js} +3 -3
- package/dist/assets/{ai-model-dropdown-a4n3JDbf.js → ai-model-dropdown-D2q0Cm0R.js} +1 -1
- package/dist/assets/{app-config-button-BQAVAUpN.js → app-config-button-CB6s4qUy.js} +1 -1
- package/dist/assets/{blob-BBiZd3vX.js → blob-Dza7uUlT.js} +1 -1
- package/dist/assets/cell-editor-e7_o4mEh.js +24 -0
- package/dist/assets/cell-link-DqJmxVu3.js +1 -0
- package/dist/assets/{cells-B1S_6FG3.js → cells-DfJw5iuV.js} +1 -1
- package/dist/assets/{chat-display-C7EsuQmC.js → chat-display-Cgg0xLir.js} +1 -1
- package/dist/assets/{chat-panel-DCSD5g-j.js → chat-panel-BaG-5LiN.js} +4 -4
- package/dist/assets/{chat-ui-C3ay3Rdi.js → chat-ui-DYLpA8l4.js} +4 -4
- package/dist/assets/{column-preview-DKbMmEB5.js → column-preview-BdNXfBsJ.js} +1 -1
- package/dist/assets/{command-palette-D1IoMI0F.js → command-palette-DiT0E_K7.js} +1 -1
- package/dist/assets/{common-BGLbVHCS.js → common-D_BAMDHd.js} +1 -1
- package/dist/assets/{components-BAvTsivu.js → components-D3x9DfK0.js} +1 -1
- package/dist/assets/{datasource-BrSYgi6C.js → datasource-B9lSU_L0.js} +1 -1
- package/dist/assets/{dependency-graph-panel-C13q-nga.js → dependency-graph-panel-3-8ygeBR.js} +1 -1
- package/dist/assets/{documentation-panel-CtQqNEQw.js → documentation-panel-BzJcTCN3.js} +1 -1
- package/dist/assets/{download-CIdXYNHv.js → download-4OuOU5F0.js} +1 -1
- package/dist/assets/{edit-page-B1g7AdLD.js → edit-page-DAC4cNKe.js} +6 -6
- package/dist/assets/{error-entries-DEjvzkum.js → error-entries-BCuZmxgk.js} +1 -1
- package/dist/assets/{error-panel-BvrDwUv8.js → error-panel-Dk-wFymb.js} +1 -1
- package/dist/assets/{file-explorer-panel-z-rke195.js → file-explorer-panel-Bprnrp3k.js} +1 -1
- package/dist/assets/{file-icons-Bo1Ewf1W.js → file-icons-BnE-lUIr.js} +1 -1
- package/dist/assets/{file-name-input-BeYyD2dj.js → file-name-input-B-KJyr-U.js} +1 -1
- package/dist/assets/{floating-outline-CNsG18LK.js → floating-outline-BebYvSvL.js} +1 -1
- package/dist/assets/{home-page-P1nAvpLK.js → home-page-zNwFmOPQ.js} +1 -1
- package/dist/assets/{index-Bn9RxQAN.css → index-BrmH4-pI.css} +1 -1
- package/dist/assets/index-DTQ4RS9q.js +29 -0
- package/dist/assets/{layout-vg4c6imf.js → layout-D3zwsTvR.js} +4 -4
- package/dist/assets/{logs-panel-BSD1GETR.js → logs-panel-CXu4QQwD.js} +1 -1
- package/dist/assets/{markdown-renderer-DIXiKP4m.js → markdown-renderer-BUBFaUWx.js} +1 -1
- package/dist/assets/{name-cell-input-zqypwqoZ.js → name-cell-input-DbRrAlCe.js} +1 -1
- package/dist/assets/{outline-panel-C5GAyqII.js → outline-panel-16ugwsMh.js} +1 -1
- package/dist/assets/{packages-panel-DMZtOQYl.js → packages-panel-8OASpr5S.js} +1 -1
- package/dist/assets/{panel-accordion-state-DmV3OgfX.js → panel-accordion-state-786YBa3I.js} +1 -1
- package/dist/assets/panels-ChFSBlsU.js +4 -0
- package/dist/assets/{process-output-DNhz9LLW.js → process-output-NwNkpkfY.js} +1 -1
- package/dist/assets/{readonly-code-display-CKT-gv1m.js → readonly-code-display-Ce9Vb5Wf.js} +1 -1
- package/dist/assets/{readonly-python-code-De0vvuWh.js → readonly-python-code-CQd3Bopx.js} +1 -1
- package/dist/assets/{reveal-component-D9uCWzLS.js → reveal-component-BFRmOcQr.js} +1 -1
- package/dist/assets/{run-page-ah4ptdoG.js → run-page-MZCCEXct.js} +1 -1
- package/dist/assets/{scratchpad-panel-YOarJKLy.js → scratchpad-panel-BqYT2vj3.js} +1 -1
- package/dist/assets/{session-panel-1RH7Zh0c.js → session-panel-BdXUO63q.js} +1 -1
- package/dist/assets/{skeleton-BX017Wzj.js → skeleton-gmxAkHfa.js} +1 -1
- package/dist/assets/{snippets-panel-BZ254lxN.js → snippets-panel-BI6SMTkf.js} +1 -1
- package/dist/assets/{state-llVv0lIh.js → state-CZxxcsV7.js} +1 -1
- package/dist/assets/{state-MlBpc9qi.js → state-DmlDv6kf.js} +3 -3
- package/dist/assets/{tracing-C0FGWieO.js → tracing-Bs9V1VRy.js} +1 -1
- package/dist/assets/{tracing-panel-LeIOi1Tv.js → tracing-panel-CMnYFlDr.js} +2 -2
- package/dist/assets/{tree-actions-CrWpuExP.js → tree-actions-A2eVufTn.js} +1 -1
- package/dist/assets/{useAddCell-DIdbaU70.js → useAddCell-BBv-60Hs.js} +1 -1
- package/dist/assets/{useCellActionButton-DXyUqFdJ.js → useCellActionButton-DgA6C9_m.js} +1 -1
- package/dist/assets/{useDeleteCell-CSCMg7Up.js → useDeleteCell-CqYCNkza.js} +1 -1
- package/dist/assets/{useDependencyPanelTab-Bt7aBDpS.js → useDependencyPanelTab-DhGw5Y2R.js} +1 -1
- package/dist/assets/{useNotebookActions-DKMS8mUE.js → useNotebookActions-BoFJmccb.js} +3 -3
- package/dist/assets/{useRunCells-DHnbXALT.js → useRunCells-BwYUZxV6.js} +1 -1
- package/dist/assets/{useSplitCell-CxlsGCB_.js → useSplitCell-D0ws6n0g.js} +1 -1
- package/dist/index.html +20 -20
- package/package.json +1 -1
- package/src/components/editor/ai/ai-completion-editor.tsx +9 -15
- package/src/components/editor/ai/completion-handlers.tsx +30 -10
- package/src/components/editor/cell/StagedAICell.tsx +53 -15
- package/src/components/editor/chrome/wrapper/pending-ai-cells.tsx +6 -1
- package/src/components/editor/notebook-cell.tsx +2 -2
- package/src/core/ai/__tests__/staged-cells.test.ts +45 -2
- package/src/core/ai/staged-cells.ts +15 -1
- package/src/css/app/Cell.css +25 -26
- package/dist/assets/JsonOutput--lW6GQQ9.js +0 -50
- package/dist/assets/add-cell-with-ai-hxrzwECc.js +0 -77
- package/dist/assets/cell-editor-DfhJQCKm.js +0 -24
- package/dist/assets/cell-link-BbAJokoX.js +0 -1
- package/dist/assets/index-BMQJCzfh.js +0 -29
- package/dist/assets/panels-DllVd27E.js +0 -4
|
@@ -7,6 +7,11 @@ import { Button } from "@/components/ui/button";
|
|
|
7
7
|
import { Tooltip } from "@/components/ui/tooltip";
|
|
8
8
|
import { isPlatformMac } from "@/core/hotkeys/shortcuts";
|
|
9
9
|
|
|
10
|
+
export const AI_EDIT_ACTION_LABELS = {
|
|
11
|
+
accept: "Keep change",
|
|
12
|
+
decline: "Revert change",
|
|
13
|
+
} as const;
|
|
14
|
+
|
|
10
15
|
/**
|
|
11
16
|
* Common keyboard shortcut handlers for AI completions
|
|
12
17
|
*/
|
|
@@ -48,16 +53,31 @@ export const CompletionActionsCellFooter: React.FC<{
|
|
|
48
53
|
size?: "xs" | "sm";
|
|
49
54
|
multipleCompletions?: boolean;
|
|
50
55
|
runCell?: () => void;
|
|
51
|
-
|
|
56
|
+
acceptLabel?: string;
|
|
57
|
+
declineLabel?: string;
|
|
58
|
+
}> = ({
|
|
59
|
+
isLoading,
|
|
60
|
+
onAccept,
|
|
61
|
+
onDecline,
|
|
62
|
+
runCell,
|
|
63
|
+
acceptLabel,
|
|
64
|
+
declineLabel,
|
|
65
|
+
size,
|
|
66
|
+
}) => {
|
|
52
67
|
return (
|
|
53
68
|
<>
|
|
54
69
|
<AcceptCompletionButton
|
|
55
70
|
isLoading={isLoading}
|
|
56
71
|
onAccept={onAccept}
|
|
57
|
-
size=
|
|
72
|
+
size={size}
|
|
58
73
|
runCell={runCell}
|
|
74
|
+
label={acceptLabel}
|
|
75
|
+
/>
|
|
76
|
+
<RejectCompletionButton
|
|
77
|
+
onDecline={onDecline}
|
|
78
|
+
size={size}
|
|
79
|
+
label={declineLabel}
|
|
59
80
|
/>
|
|
60
|
-
<RejectCompletionButton onDecline={onDecline} size="xs" />
|
|
61
81
|
</>
|
|
62
82
|
);
|
|
63
83
|
};
|
|
@@ -72,6 +92,7 @@ export const AcceptCompletionButton: React.FC<{
|
|
|
72
92
|
borderless?: boolean;
|
|
73
93
|
acceptShortcut?: string;
|
|
74
94
|
runCell?: () => void;
|
|
95
|
+
label?: string;
|
|
75
96
|
}> = ({
|
|
76
97
|
isLoading,
|
|
77
98
|
onAccept,
|
|
@@ -82,6 +103,7 @@ export const AcceptCompletionButton: React.FC<{
|
|
|
82
103
|
runCell,
|
|
83
104
|
playButtonStyles,
|
|
84
105
|
borderless = false,
|
|
106
|
+
label,
|
|
85
107
|
}) => {
|
|
86
108
|
const handleAcceptAndRun = () => {
|
|
87
109
|
onAccept();
|
|
@@ -90,7 +112,7 @@ export const AcceptCompletionButton: React.FC<{
|
|
|
90
112
|
}
|
|
91
113
|
};
|
|
92
114
|
|
|
93
|
-
const text = multipleCompletions ? "Accept all" : "Accept";
|
|
115
|
+
const text = label ?? (multipleCompletions ? "Accept all" : "Accept");
|
|
94
116
|
|
|
95
117
|
const baseClasses = `h-6 text-(--grass-11) bg-(--grass-3)/60
|
|
96
118
|
hover:bg-(--grass-3) dark:bg-(--grass-4)/80 dark:hover:bg-(--grass-3) font-semibold
|
|
@@ -113,11 +135,7 @@ export const AcceptCompletionButton: React.FC<{
|
|
|
113
135
|
)}
|
|
114
136
|
</Button>
|
|
115
137
|
<Tooltip
|
|
116
|
-
content={
|
|
117
|
-
multipleCompletions
|
|
118
|
-
? "Accept and run all cells"
|
|
119
|
-
: "Accept and run cell"
|
|
120
|
-
}
|
|
138
|
+
content={`${text} and run ${multipleCompletions ? "all cells" : "cell"}`}
|
|
121
139
|
>
|
|
122
140
|
<Button
|
|
123
141
|
variant="text"
|
|
@@ -156,6 +174,7 @@ export const RejectCompletionButton: React.FC<{
|
|
|
156
174
|
className?: string;
|
|
157
175
|
borderless?: boolean;
|
|
158
176
|
declineShortcut?: string;
|
|
177
|
+
label?: string;
|
|
159
178
|
}> = ({
|
|
160
179
|
onDecline,
|
|
161
180
|
multipleCompletions = false,
|
|
@@ -163,6 +182,7 @@ export const RejectCompletionButton: React.FC<{
|
|
|
163
182
|
className,
|
|
164
183
|
declineShortcut,
|
|
165
184
|
borderless = false,
|
|
185
|
+
label,
|
|
166
186
|
}) => {
|
|
167
187
|
return (
|
|
168
188
|
<Button
|
|
@@ -174,7 +194,7 @@ export const RejectCompletionButton: React.FC<{
|
|
|
174
194
|
active:bg-(--red-5) dark:active:bg-(--red-4)
|
|
175
195
|
border-(--red-6) border hover:shadow-xs ${borderless && "border-none rounded-md"} ${className}`}
|
|
176
196
|
>
|
|
177
|
-
Reject{multipleCompletions
|
|
197
|
+
{label ?? `Reject${multipleCompletions ? " all" : ""}`}
|
|
178
198
|
{declineShortcut && (
|
|
179
199
|
<MinimalHotkeys className="ml-1 inline" shortcut={declineShortcut} />
|
|
180
200
|
)}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { useStore } from "jotai";
|
|
4
|
+
import { SparklesIcon } from "lucide-react";
|
|
5
|
+
import { Tooltip } from "@/components/ui/tooltip";
|
|
4
6
|
import {
|
|
5
7
|
type Edit,
|
|
6
|
-
|
|
8
|
+
useStagedAICell,
|
|
7
9
|
useStagedCells,
|
|
8
10
|
} from "@/core/ai/staged-cells";
|
|
9
11
|
import { getCellEditorView } from "@/core/cells/cells";
|
|
@@ -11,15 +13,24 @@ import type { CellId } from "@/core/cells/ids";
|
|
|
11
13
|
import { updateEditorCodeFromPython } from "@/core/codemirror/language/utils";
|
|
12
14
|
import { cn } from "@/utils/cn";
|
|
13
15
|
import { Logger } from "@/utils/Logger";
|
|
14
|
-
import {
|
|
16
|
+
import {
|
|
17
|
+
AI_EDIT_ACTION_LABELS,
|
|
18
|
+
CompletionActionsCellFooter,
|
|
19
|
+
} from "../ai/completion-handlers";
|
|
15
20
|
import { useRunCell } from "./useRunCells";
|
|
16
21
|
|
|
22
|
+
const actionLabels: Record<Edit["type"], { accept: string; decline: string }> =
|
|
23
|
+
{
|
|
24
|
+
add_cell: { accept: "Keep cell", decline: "Discard cell" },
|
|
25
|
+
update_cell: AI_EDIT_ACTION_LABELS,
|
|
26
|
+
delete_cell: { accept: "Delete cell", decline: "Keep cell" },
|
|
27
|
+
};
|
|
28
|
+
|
|
17
29
|
export const StagedAICellBackground: React.FC<{
|
|
18
30
|
cellId: CellId;
|
|
19
31
|
className?: string;
|
|
20
32
|
}> = ({ cellId, className }) => {
|
|
21
|
-
const
|
|
22
|
-
const stagedCell = stagedAICells.get(cellId);
|
|
33
|
+
const stagedCell = useStagedAICell(cellId);
|
|
23
34
|
|
|
24
35
|
if (!stagedCell) {
|
|
25
36
|
return null;
|
|
@@ -37,8 +48,7 @@ export const StagedAICellFooter: React.FC<{ cellId: CellId }> = ({
|
|
|
37
48
|
cellId,
|
|
38
49
|
}) => {
|
|
39
50
|
const store = useStore();
|
|
40
|
-
const
|
|
41
|
-
const stagedAiCell = stagedAICells.get(cellId);
|
|
51
|
+
const stagedAiCell = useStagedAICell(cellId);
|
|
42
52
|
const runCell = useRunCell(cellId);
|
|
43
53
|
|
|
44
54
|
const { deleteStagedCell, removeStagedCell } = useStagedCells(store);
|
|
@@ -47,21 +57,49 @@ export const StagedAICellFooter: React.FC<{ cellId: CellId }> = ({
|
|
|
47
57
|
return null;
|
|
48
58
|
}
|
|
49
59
|
|
|
60
|
+
const isDeletion = stagedAiCell.type === "delete_cell";
|
|
61
|
+
const labels = actionLabels[stagedAiCell.type];
|
|
62
|
+
|
|
50
63
|
const handleCompletion = (type: "accept" | "reject") => {
|
|
51
64
|
const completionFunc =
|
|
52
65
|
type === "accept" ? acceptStagedCell : rejectStagedCell;
|
|
53
66
|
completionFunc(cellId, stagedAiCell, removeStagedCell, deleteStagedCell);
|
|
54
67
|
};
|
|
55
68
|
|
|
69
|
+
const tooltipContent =
|
|
70
|
+
stagedAiCell.type === "add_cell"
|
|
71
|
+
? "AI-generated cell"
|
|
72
|
+
: isDeletion
|
|
73
|
+
? "AI suggests deleting this cell"
|
|
74
|
+
: "AI changed this cell";
|
|
75
|
+
|
|
56
76
|
return (
|
|
57
|
-
<div className="
|
|
58
|
-
<
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
77
|
+
<div className="mo-ai-cell-footer">
|
|
78
|
+
<div className="flex items-center gap-1.5 text-xs text-muted-foreground">
|
|
79
|
+
<Tooltip content={tooltipContent}>
|
|
80
|
+
<span
|
|
81
|
+
className="inline-flex items-center gap-1.5"
|
|
82
|
+
aria-label={tooltipContent}
|
|
83
|
+
>
|
|
84
|
+
<SparklesIcon
|
|
85
|
+
aria-hidden="true"
|
|
86
|
+
className="size-3.5 text-(--blue-11)"
|
|
87
|
+
/>
|
|
88
|
+
<span>AI</span>
|
|
89
|
+
</span>
|
|
90
|
+
</Tooltip>
|
|
91
|
+
</div>
|
|
92
|
+
<div className="flex items-center gap-1.5">
|
|
93
|
+
<CompletionActionsCellFooter
|
|
94
|
+
isLoading={false}
|
|
95
|
+
onAccept={() => handleCompletion("accept")}
|
|
96
|
+
onDecline={() => handleCompletion("reject")}
|
|
97
|
+
size="xs"
|
|
98
|
+
runCell={isDeletion ? undefined : runCell}
|
|
99
|
+
acceptLabel={labels.accept}
|
|
100
|
+
declineLabel={labels.decline}
|
|
101
|
+
/>
|
|
102
|
+
</div>
|
|
65
103
|
</div>
|
|
66
104
|
);
|
|
67
105
|
};
|
|
@@ -56,7 +56,10 @@ export const PendingAICells: React.FC = () => {
|
|
|
56
56
|
};
|
|
57
57
|
|
|
58
58
|
const runAllCells = () => {
|
|
59
|
-
|
|
59
|
+
const cellsToRun = listStagedCells.filter(
|
|
60
|
+
(cellId) => stagedAiCells.get(cellId)?.type !== "delete_cell",
|
|
61
|
+
);
|
|
62
|
+
runCell(cellsToRun);
|
|
60
63
|
};
|
|
61
64
|
|
|
62
65
|
const cyanShadow = "shadow-[0_0_6px_0_#00A2C733]";
|
|
@@ -91,6 +94,7 @@ export const PendingAICells: React.FC = () => {
|
|
|
91
94
|
multipleCompletions={true}
|
|
92
95
|
onAccept={acceptAllCompletions}
|
|
93
96
|
isLoading={false}
|
|
97
|
+
label="Keep all"
|
|
94
98
|
size="xs"
|
|
95
99
|
buttonStyles="h-6.5"
|
|
96
100
|
playButtonStyles="h-6.5"
|
|
@@ -100,6 +104,7 @@ export const PendingAICells: React.FC = () => {
|
|
|
100
104
|
multipleCompletions={true}
|
|
101
105
|
onDecline={rejectAllCompletions}
|
|
102
106
|
size="xs"
|
|
107
|
+
label="Discard all"
|
|
103
108
|
className="h-6.5"
|
|
104
109
|
/>
|
|
105
110
|
</div>
|
|
@@ -839,8 +839,8 @@ const EditableCellComponent = ({
|
|
|
839
839
|
/>
|
|
840
840
|
)}
|
|
841
841
|
<PendingDeleteConfirmation cellId={cellId} />
|
|
842
|
+
{!isPresenting && <StagedAICellFooter cellId={cellId} />}
|
|
842
843
|
</div>
|
|
843
|
-
{!isPresenting && <StagedAICellFooter cellId={cellId} />}
|
|
844
844
|
{isCollapsed && !isPresenting && (
|
|
845
845
|
<CollapsedCellBanner
|
|
846
846
|
onClick={() => actions.expandCell({ cellId })}
|
|
@@ -1327,8 +1327,8 @@ const SetupCellComponent = ({
|
|
|
1327
1327
|
cellId={cellId}
|
|
1328
1328
|
debuggerActive={cellRuntime.debuggerActive}
|
|
1329
1329
|
/>
|
|
1330
|
+
<StagedAICellFooter cellId={cellId} />
|
|
1330
1331
|
</div>
|
|
1331
|
-
<StagedAICellFooter cellId={cellId} />
|
|
1332
1332
|
</div>
|
|
1333
1333
|
</CellActionsContextMenu>
|
|
1334
1334
|
</TooltipProvider>
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
2
|
|
|
3
|
-
import { renderHook } from "@testing-library/react";
|
|
4
|
-
import { getDefaultStore } from "jotai";
|
|
3
|
+
import { act, renderHook } from "@testing-library/react";
|
|
4
|
+
import { getDefaultStore, Provider } from "jotai";
|
|
5
|
+
import { createElement, type ReactNode } from "react";
|
|
5
6
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
6
7
|
import { cellId } from "@/__tests__/branded";
|
|
7
8
|
import { CellId } from "@/core/cells/ids";
|
|
@@ -9,6 +10,7 @@ import { updateEditorCodeFromPython } from "../../codemirror/language/utils";
|
|
|
9
10
|
import {
|
|
10
11
|
type StagedAICells,
|
|
11
12
|
stagedAICellsAtom,
|
|
13
|
+
useStagedAICell,
|
|
12
14
|
useStagedCells,
|
|
13
15
|
visibleForTesting,
|
|
14
16
|
} from "../staged-cells";
|
|
@@ -149,6 +151,16 @@ describe("staged-cells", () => {
|
|
|
149
151
|
expect(newState.has(cellId2)).toBe(true);
|
|
150
152
|
});
|
|
151
153
|
|
|
154
|
+
it("should preserve state when removing a cell that is not staged", () => {
|
|
155
|
+
const state = new Map([[cellId1, { type: "add_cell" as const }]]);
|
|
156
|
+
const newState = reducer(state, {
|
|
157
|
+
type: "removeStagedCell",
|
|
158
|
+
payload: cellId2,
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
expect(newState).toBe(state);
|
|
162
|
+
});
|
|
163
|
+
|
|
152
164
|
it("should clear all cells", () => {
|
|
153
165
|
const state = new Map([
|
|
154
166
|
[cellId1, { type: "add_cell" as const }],
|
|
@@ -206,6 +218,37 @@ describe("staged-cells", () => {
|
|
|
206
218
|
const state = store.get(stagedAICellsAtom);
|
|
207
219
|
expect(state).toEqual(new Map());
|
|
208
220
|
});
|
|
221
|
+
|
|
222
|
+
it("should not rerender a cell when another cell's staging changes", () => {
|
|
223
|
+
let renderCount = 0;
|
|
224
|
+
const wrapper = ({ children }: { children: ReactNode }) =>
|
|
225
|
+
createElement(Provider, { store }, children);
|
|
226
|
+
|
|
227
|
+
renderHook(
|
|
228
|
+
() => {
|
|
229
|
+
renderCount++;
|
|
230
|
+
return useStagedAICell(cellId2);
|
|
231
|
+
},
|
|
232
|
+
{ wrapper },
|
|
233
|
+
);
|
|
234
|
+
const initialRenderCount = renderCount;
|
|
235
|
+
|
|
236
|
+
act(() => {
|
|
237
|
+
store.set(
|
|
238
|
+
stagedAICellsAtom,
|
|
239
|
+
new Map([[cellId1, { type: "add_cell" }]]),
|
|
240
|
+
);
|
|
241
|
+
});
|
|
242
|
+
expect(renderCount).toBe(initialRenderCount);
|
|
243
|
+
|
|
244
|
+
act(() => {
|
|
245
|
+
store.set(
|
|
246
|
+
stagedAICellsAtom,
|
|
247
|
+
new Map([[cellId2, { type: "add_cell" }]]),
|
|
248
|
+
);
|
|
249
|
+
});
|
|
250
|
+
expect(renderCount).toBeGreaterThan(initialRenderCount);
|
|
251
|
+
});
|
|
209
252
|
});
|
|
210
253
|
|
|
211
254
|
describe("useStagedCells hook", () => {
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
2
|
|
|
3
3
|
import type { UIMessageChunk } from "ai";
|
|
4
|
-
import {
|
|
4
|
+
import { useAtomValue } from "jotai";
|
|
5
|
+
import { selectAtom } from "jotai/utils";
|
|
6
|
+
import { useMemo, useRef } from "react";
|
|
5
7
|
import {
|
|
6
8
|
type AiCompletion,
|
|
7
9
|
codeToCells,
|
|
@@ -51,6 +53,9 @@ const {
|
|
|
51
53
|
return new Map([...state, [cellId, edit]]);
|
|
52
54
|
},
|
|
53
55
|
removeStagedCell: (state, cellId: CellId) => {
|
|
56
|
+
if (!state.has(cellId)) {
|
|
57
|
+
return state;
|
|
58
|
+
}
|
|
54
59
|
const newState = new Map(state);
|
|
55
60
|
newState.delete(cellId);
|
|
56
61
|
return newState;
|
|
@@ -66,6 +71,15 @@ export {
|
|
|
66
71
|
reducer as stagedAICellsReducer,
|
|
67
72
|
};
|
|
68
73
|
|
|
74
|
+
export function useStagedAICell(cellId: CellId): Edit | undefined {
|
|
75
|
+
const stagedCellAtom = useMemo(
|
|
76
|
+
() =>
|
|
77
|
+
selectAtom(stagedAICellsAtom, (cells) => cells.get(cellId), Object.is),
|
|
78
|
+
[cellId],
|
|
79
|
+
);
|
|
80
|
+
return useAtomValue(stagedCellAtom);
|
|
81
|
+
}
|
|
82
|
+
|
|
69
83
|
interface UpdateStagedCellAction {
|
|
70
84
|
cellId: CellId;
|
|
71
85
|
code: string;
|
package/src/css/app/Cell.css
CHANGED
|
@@ -27,25 +27,14 @@
|
|
|
27
27
|
outline: none;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
&:has(.mo-ai-generated-cell)
|
|
31
|
-
|
|
32
|
-
position: absolute;
|
|
33
|
-
inset: 0;
|
|
34
|
-
border-radius: 10px;
|
|
35
|
-
pointer-events: none;
|
|
36
|
-
opacity: 0.5;
|
|
37
|
-
box-shadow: 0px 0px 10px 0px var(--cyan-9);
|
|
38
|
-
z-index: 1;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
&:has(.mo-ai-generated-cell.mo-ai-setup-cell)::before {
|
|
42
|
-
border-radius: 2px;
|
|
30
|
+
&:has(.mo-ai-generated-cell) {
|
|
31
|
+
border-color: var(--blue-7);
|
|
43
32
|
}
|
|
44
33
|
|
|
45
34
|
/* Styling for cells marked for deletion by AI */
|
|
46
35
|
&:has(.mo-ai-deleted-cell) {
|
|
47
|
-
opacity: 0.7;
|
|
48
36
|
position: relative;
|
|
37
|
+
border-color: var(--red-7);
|
|
49
38
|
|
|
50
39
|
.output-area,
|
|
51
40
|
.cm-gutters,
|
|
@@ -53,18 +42,6 @@
|
|
|
53
42
|
background-color: var(--red-2);
|
|
54
43
|
}
|
|
55
44
|
|
|
56
|
-
/* Add a red glow to indicate deletion */
|
|
57
|
-
&::before {
|
|
58
|
-
content: "";
|
|
59
|
-
position: absolute;
|
|
60
|
-
inset: 0;
|
|
61
|
-
border-radius: 10px;
|
|
62
|
-
pointer-events: none;
|
|
63
|
-
opacity: 0.6;
|
|
64
|
-
box-shadow: 0px 0px 10px 0px var(--red-9);
|
|
65
|
-
z-index: 1;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
45
|
/* Add strikethrough effect to the code */
|
|
69
46
|
.cm-content {
|
|
70
47
|
text-decoration: line-through;
|
|
@@ -326,6 +303,20 @@
|
|
|
326
303
|
@apply bg-background;
|
|
327
304
|
}
|
|
328
305
|
|
|
306
|
+
.mo-ai-cell-footer {
|
|
307
|
+
display: flex;
|
|
308
|
+
align-items: center;
|
|
309
|
+
justify-content: space-between;
|
|
310
|
+
gap: 0.75rem;
|
|
311
|
+
width: 100%;
|
|
312
|
+
padding: 0.375rem 0.5rem;
|
|
313
|
+
background: var(--gray-2);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
.dark .mo-ai-cell-footer {
|
|
317
|
+
background: var(--gray-3);
|
|
318
|
+
}
|
|
319
|
+
|
|
329
320
|
.dark .marimo-cell {
|
|
330
321
|
@apply border-border;
|
|
331
322
|
|
|
@@ -364,6 +355,14 @@
|
|
|
364
355
|
}
|
|
365
356
|
}
|
|
366
357
|
|
|
358
|
+
.dark .marimo-cell:has(.mo-ai-generated-cell) {
|
|
359
|
+
border-color: var(--blue-8);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
.dark .marimo-cell:has(.mo-ai-deleted-cell) {
|
|
363
|
+
border-color: var(--red-8);
|
|
364
|
+
}
|
|
365
|
+
|
|
367
366
|
#cell-setup,
|
|
368
367
|
#cell-setup .cm-editor,
|
|
369
368
|
#cell-setup .cm-gutter {
|