Skip to content

Make proposed AI edits UI consistent with editor styling - #10595

Open
akshayka wants to merge 2 commits into
mainfrom
aka/ai-proposed-edit-ui
Open

Make proposed AI edits UI consistent with editor styling#10595
akshayka wants to merge 2 commits into
mainfrom
aka/ai-proposed-edit-ui

Conversation

@akshayka

Copy link
Copy Markdown
Contributor

This pull request makes AI-generated notebook cells look consistent with the rest of the editor and clarifies how users review proposed changes.

It also updates the inline AI refactoring flow that opens with Cmd+Shift+E on macOS or the equivalent shortcut on other platforms.

What changed

  • AI-added cells no longer use a bright glow or reduced opacity. They use the existing cell border and background styles.
  • Added cells show the label AI-added cell without adding separate Accept and Reject buttons to every new cell.
    • Running a proposed cell clears its proposal state.
    • Editing a proposed cell clears its proposal state.
  • Changed cells show Keep change and Revert change actions.
  • Deletion proposals show Delete cell and Keep cell actions.
  • The global proposal tray uses Keep all and Discard all labels.
  • The global run action does not try to run cells that the user accepted for deletion.
  • Deleting a proposed cell, including through a multi-cell delete, clears its proposal state.
  • The inline AI refactoring banner uses the same bordered button styling and the Keep change and Revert change labels.

Why

AI proposals are already inserted into the notebook document before the user reviews them. The review state only indicates that the proposal still needs attention. The updated UI uses the existing cell styling and reserves explicit review actions for changes that need a keep or revert decision.

Running or editing a proposed cell indicates that the user has reviewed the current code. Clearing the proposal state in those cases prevents the review marker from remaining after the user takes ownership of the cell.

With changes (this PR)

image

Without changes (before this PR)

image

AI-generated notebook cells should look like part of the editor and make the available actions clear. Replace the distracting proposal glows with the existing cell styling, keep the review controls inside each cell, and use explicit labels for proposed deletions. Do not offer to run a cell after the user accepts its deletion.
AI-generated cells and inline refactoring need the same review language and button treatment. Keep added-cell status concise, use explicit keep and revert actions for changes, and make normal edits, runs, and deletes clear the AI review state so the proposal UI does not persist after the user takes ownership of a cell.
@akshayka akshayka added the enhancement New feature or request label Aug 19, 2026
Copilot AI lite review requested due to automatic review settings August 19, 2026 21:25
@vercel

vercel Bot commented Aug 19, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
marimo-docs Ready Ready Preview Aug 19, 2026 9:25pm

Request Review

@github-actions

Copy link
Copy Markdown
Contributor

All contributors have signed the CLA ✍️ ✅
Posted by the CLA Assistant Lite bot.

@akshayka akshayka changed the title Make proposed AI edits user interface consistent with marimo's styling Make proposed AI edits UI consistent with editor styling Aug 19, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the frontend “staged AI cell” review experience so AI-proposed edits visually match standard editor cells, and so proposal review actions/labels better reflect “keep vs revert” semantics across add/change/delete proposals (including the inline refactor banner flow).

Changes:

  • Replaces AI proposal “glow/opacity” styling with standard cell styling plus border-color cues, and introduces a consistent AI cell footer style.
  • Updates per-cell and global proposal review labels/actions (e.g., “Keep change” / “Revert change”, “Keep all” / “Discard all”), and adjusts run-all behavior to skip deletion proposals.
  • Clears staged/proposal state in more user-ownership flows (running, editing, deleting cells) so review markers don’t linger.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
frontend/src/css/app/Cell.css Switches AI proposal visuals to border-based styling and adds .mo-ai-cell-footer styling.
frontend/src/components/editor/notebook-cell.tsx Moves staged AI footer rendering into the main cell container so it participates in cell layout/styling.
frontend/src/components/editor/chrome/wrapper/pending-ai-cells.tsx Updates global tray labels and prevents running staged deletion proposals.
frontend/src/components/editor/cell/useRunCells.ts Clears staged AI state on run to reflect user ownership after execution.
frontend/src/components/editor/cell/useDeleteCell.tsx Clears staged AI state when deleting one or many cells.
frontend/src/components/editor/cell/StagedAICell.tsx Revises staged AI footer messaging and conditionally shows review actions based on proposal type.
frontend/src/components/editor/cell/code/cell-editor.tsx Clears staged AI state on direct user edits in CodeMirror.
frontend/src/components/editor/ai/completion-handlers.tsx Adds label customization support to completion action buttons.
frontend/src/components/editor/ai/ai-completion-editor.tsx Aligns inline refactor banner styling/copy with the new “keep vs revert” terminology.
Suppressed comments (1)

frontend/src/components/editor/cell/useDeleteCell.tsx:77

  • Same as the single-cell delete: this get + set pattern can drop concurrent staged AI updates. Use a functional store.set updater to remove all deleted cellIds from the latest stagedAICellsAtom value atomically.
    const stagedAICells = store.get(stagedAICellsAtom);
    if (stagedAICells.size > 0) {
      const nextStagedAICells = new Map(stagedAICells);
      for (const cellId of cellIds) {
        nextStagedAICells.delete(cellId);

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +53 to +57
}> = ({
isLoading,
onAccept,
onDecline,
runCell,
Comment on lines +27 to +32
const stagedAICells = store.get(stagedAICellsAtom);
if (stagedAICells.has(cellId)) {
const nextStagedAICells = new Map(stagedAICells);
nextStagedAICells.delete(cellId);
store.set(stagedAICellsAtom, nextStagedAICells);
}

<p className="transition-opacity duration-200 text-muted-foreground">
{isLoading ? "Generating fix..." : "Showing fix"}
{isLoading ? "Generating change..." : "AI changed this cell"}
Comment on lines +74 to +82
<CompletionActionsCellFooter
isLoading={false}
onAccept={() => handleCompletion("accept")}
onDecline={() => handleCompletion("reject")}
size="xs"
runCell={isDeletion ? undefined : runCell}
acceptLabel={isDeletion ? "Delete cell" : "Keep change"}
declineLabel={isDeletion ? "Keep cell" : "Revert change"}
/>
@github-actions

Copy link
Copy Markdown
Contributor

Coverage Report for ./frontend

Status Category Percentage Covered / Total
🔵 Lines 78.74% 82094 / 104249
🔵 Statements 78.74% 82094 / 104249
🔵 Functions 71.67% 706 / 985
🔵 Branches 79.41% 4969 / 6257
File Coverage
File Stmts Branches Functions Lines Uncovered Lines
Changed Files
frontend/src/components/editor/notebook-cell.tsx 88.86% 64.81% 0% 88.86% 106-113, 122-143, 186-199, 202-205, 525-542, 601-602, 608-615, 758-813, 845-849, 924, 928
frontend/src/components/editor/ai/ai-completion-editor.tsx 94.47% 11.11% 0% 94.47% 131, 133, 156-158, 167-174, 217, 362, 428-433
frontend/src/components/editor/ai/completion-handlers.tsx 97.6% 0% 100% 97.6% 128, 134, 197
frontend/src/components/editor/cell/StagedAICell.tsx 85.1% 0% 0% 85.1% 32, 67-69, 73-83
frontend/src/components/editor/cell/useDeleteCell.tsx 22.72% 100% 100% 22.72% 22-59, 68-103
frontend/src/components/editor/cell/useRunCells.ts 82.08% 100% 100% 82.08% 37-41, 61-77
frontend/src/components/editor/cell/code/cell-editor.tsx 57.8% 42.1% 0% 57.8% 137-148, 151-162, 166-171, 176-191, 194-198, 287-297, 300-330, 334-336, 371-399, 402-428, 431-465, 484-489, 529-530, 539, 553-555, 557-564, 566-575, 585-591, 612-657, 700
frontend/src/components/editor/chrome/wrapper/pending-ai-cells.tsx 0% 100% 100% 0% 3-112
Generated in workflow #20617 for commit 42d7620 by the Vitest Coverage Report Action

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

5 issues found across 9 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="frontend/src/components/editor/ai/completion-handlers.tsx">

<violation number="1" location="frontend/src/components/editor/ai/completion-handlers.tsx:109">
P3: When a custom label is supplied ("Keep change", "Keep all"), the run-cell play button's tooltip still hardcodes "Accept and run cell"/"Accept and run all cells". Derive the tooltip text from the label (or a related custom tooltip prop) so it matches the surfaced button label.</violation>
</file>

<file name="frontend/src/components/editor/ai/ai-completion-editor.tsx">

<violation number="1" location="frontend/src/components/editor/ai/ai-completion-editor.tsx:444">
P3: The visible text now says "Generating change..."/"AI changed this cell", but the Loading/CircleCheck icon `aria-label`s right above still say "Generating fix" and "Fix generated". Since this PR is about replacing the "fix" wording with "change", update both aria-labels to match so screen readers stay consistent.</violation>
</file>

<file name="frontend/src/components/editor/cell/useDeleteCell.tsx">

<violation number="1" location="frontend/src/components/editor/cell/useDeleteCell.tsx:31">
P2: The staged AI state is removed optimistically before `sendDeleteCell` completes. If the delete fails (the `.catch(() => undoDeleteCell())` path) or the user clicks the Undo button, the cell is restored via `undoDeleteCell()` but its entry in `stagedAICellsAtom` is already gone, so the restored cell no longer shows the "AI-added cell" label or appears in the proposal tray. Clear the staged entry only after the delete succeeds, or restore it alongside `undoDeleteCell()`.</violation>
</file>

<file name="frontend/src/components/editor/cell/useRunCells.ts">

<violation number="1" location="frontend/src/components/editor/cell/useRunCells.ts:73">
P2: Proposal state is cleared before the run is dispatched, and since sendRun's errors are caught inside runCells and swallowed, a failed run still discards the proposal. For an update_cell proposal this loses the staged previousCode and the user's ability to revert the failed change. Consider clearing the staged cell only on successful execution (or passing execution success back from runCells) so a failed run retains the review state.</violation>
</file>

<file name="frontend/src/components/editor/cell/StagedAICell.tsx">

<violation number="1" location="frontend/src/components/editor/cell/StagedAICell.tsx:80">
P3: When `isDeletion` is true, the destructive `Delete cell` action uses the green accept styling while the non-destructive `Keep cell` action uses the red reject styling. Give deletion actions explicit destructive and non-destructive variants so their colors match their consequences.</violation>
</file>
Architecture diagram
sequenceDiagram
    participant UI as Notebook UI
    participant Cell as Cell Component
    participant CellEditor as Cell Editor (CodeMirror)
    participant CellFooter as StagedAICellFooter
    participant ActionBar as CompletionActions (Accept/Reject)
    participant Store as Jotai Store (stagedAICellsAtom)
    participant DeleteHook as useDeleteCell
    participant RunHook as useRunCells
    participant AIBanner as Inline AI Banner
    participant CSS as CSS Styles

    Note over UI,CSS: AI Proposal Cell Styling Flow

    UI->>CSS: Apply mo-ai-generated-cell class
    CSS->>UI: CHANGED: No glow/opacity, blue border instead
    CSS->>UI: Apply mo-ai-deleted-cell class
    CSS->>UI: CHANGED: No opacity/glow, red border + strikethrough

    Note over CellFooter,ActionBar: Cell Footer (new layout)

    Cell->>CellFooter: Render for staged AI cell
    CellFooter->>CellFooter: Determine type (add/change/delete)
    alt isAddition
        CellFooter->>CellFooter: Show "AI-added cell" label, no action buttons
    else isChange or isDeletion
        CellFooter->>CellFooter: Show "AI changed this cell" or "AI suggests deleting this cell"
        CellFooter->>ActionBar: Pass acceptLabel/declineLabel
        alt isDeletion
            ActionBar->>ActionBar: Labels: "Delete cell" / "Keep cell"
        else isChange
            ActionBar->>ActionBar: Labels: "Keep change" / "Revert change"
        end
    end

    Note over CellEditor,Store: Editing clears proposal state

    CellEditor->>CellEditor: Detect user input/delete/undo/redo event
    CellEditor->>Store: CHANGED: Remove cellId from stagedAICellsAtom
    Store-->>CellEditor: Updated state

    Note over DeleteHook,Store: Deleting a proposed cell clears state

    DeleteHook->>Store: Check if cellId is in stagedAICells
    Store-->>DeleteHook: Cell found
    DeleteHook->>Store: CHANGED: Remove cellId from stagedAICellsAtom
    DeleteHook->>Store: CHANGED: For multi-delete, remove all cellIds

    Note over RunHook,Store: Running a proposed cell clears state

    RunHook->>Store: CHANGED: Remove each cellId from stagedAICellsAtom
    RunHook->>RunHook: Run cells logic continues

    Note over RunHook,PendingAICells: Global proposal tray

    PendingAICells->>ActionBar: Pass "Keep all" / "Discard all" labels
    PendingAICells->>RunHook: Run all cells (exclude deletion proposals)
    RunHook->>Store: CHANGED: Remove user-accepted cells from staged state

    Note over ActionBar,Store: Inline AI refactoring

    ActionBar->>AIBanner: CHANGED: Border style + updated labels ("Keep change"/"Revert change")
    AIBanner->>AIBanner: Updated loading text ("Generating change...")
    AIBanner->>ActionBar: Accept/reject completion

    Note over Store,CellFooter: State-driven re-render

    Store-->>CellFooter: stagedAICellsAtom update
    CellFooter->>CellFooter: Re-render based on current state
Loading

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

if (stagedAICells.has(cellId)) {
const nextStagedAICells = new Map(stagedAICells);
nextStagedAICells.delete(cellId);
store.set(stagedAICellsAtom, nextStagedAICells);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The staged AI state is removed optimistically before sendDeleteCell completes. If the delete fails (the .catch(() => undoDeleteCell()) path) or the user clicks the Undo button, the cell is restored via undoDeleteCell() but its entry in stagedAICellsAtom is already gone, so the restored cell no longer shows the "AI-added cell" label or appears in the proposal tray. Clear the staged entry only after the delete succeeds, or restore it alongside undoDeleteCell().

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At frontend/src/components/editor/cell/useDeleteCell.tsx, line 31:

<comment>The staged AI state is removed optimistically before `sendDeleteCell` completes. If the delete fails (the `.catch(() => undoDeleteCell())` path) or the user clicks the Undo button, the cell is restored via `undoDeleteCell()` but its entry in `stagedAICellsAtom` is already gone, so the restored cell no longer shows the "AI-added cell" label or appears in the proposal tray. Clear the staged entry only after the delete succeeds, or restore it alongside `undoDeleteCell()`.</comment>

<file context>
@@ -23,6 +24,12 @@ export function useDeleteCellCallback() {
+    if (stagedAICells.has(cellId)) {
+      const nextStagedAICells = new Map(stagedAICells);
+      nextStagedAICells.delete(cellId);
+      store.set(stagedAICellsAtom, nextStagedAICells);
+    }
     const notebook = store.get(notebookAtom);
</file context>

// Running a proposed cell means the user has reviewed and kept the
// current code, including when the proposal was a deletion.
for (const cellId of cellIds) {
removeStagedCell(cellId);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Proposal state is cleared before the run is dispatched, and since sendRun's errors are caught inside runCells and swallowed, a failed run still discards the proposal. For an update_cell proposal this loses the staged previousCode and the user's ability to revert the failed change. Consider clearing the staged cell only on successful execution (or passing execution success back from runCells) so a failed run retains the review state.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At frontend/src/components/editor/cell/useRunCells.ts, line 73:

<comment>Proposal state is cleared before the run is dispatched, and since sendRun's errors are caught inside runCells and swallowed, a failed run still discards the proposal. For an update_cell proposal this loses the staged previousCode and the user's ability to revert the failed change. Consider clearing the staged cell only on successful execution (or passing execution success back from runCells) so a failed run retains the review state.</comment>

<file context>
@@ -65,6 +67,12 @@ export function useRunCells() {
+    // Running a proposed cell means the user has reviewed and kept the
+    // current code, including when the proposal was a deletion.
+    for (const cellId of cellIds) {
+      removeStagedCell(cellId);
+    }
+
</file context>

};

const text = multipleCompletions ? "Accept all" : "Accept";
const text = label ?? (multipleCompletions ? "Accept all" : "Accept");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: When a custom label is supplied ("Keep change", "Keep all"), the run-cell play button's tooltip still hardcodes "Accept and run cell"/"Accept and run all cells". Derive the tooltip text from the label (or a related custom tooltip prop) so it matches the surfaced button label.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At frontend/src/components/editor/ai/completion-handlers.tsx, line 109:

<comment>When a custom label is supplied ("Keep change", "Keep all"), the run-cell play button's tooltip still hardcodes "Accept and run cell"/"Accept and run all cells". Derive the tooltip text from the label (or a related custom tooltip prop) so it matches the surfaced button label.</comment>

<file context>
@@ -90,7 +106,7 @@ export const AcceptCompletionButton: React.FC<{
   };
 
-  const text = multipleCompletions ? "Accept all" : "Accept";
+  const text = label ?? (multipleCompletions ? "Accept all" : "Accept");
 
   const baseClasses = `h-6 text-(--grass-11) bg-(--grass-3)/60
</file context>


<p className="transition-opacity duration-200 text-muted-foreground">
{isLoading ? "Generating fix..." : "Showing fix"}
{isLoading ? "Generating change..." : "AI changed this cell"}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: The visible text now says "Generating change..."/"AI changed this cell", but the Loading/CircleCheck icon aria-labels right above still say "Generating fix" and "Fix generated". Since this PR is about replacing the "fix" wording with "change", update both aria-labels to match so screen readers stay consistent.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At frontend/src/components/editor/ai/ai-completion-editor.tsx, line 444:

<comment>The visible text now says "Generating change..."/"AI changed this cell", but the Loading/CircleCheck icon `aria-label`s right above still say "Generating fix" and "Fix generated". Since this PR is about replacing the "fix" wording with "change", update both aria-labels to match so screen readers stay consistent.</comment>

<file context>
@@ -445,7 +441,7 @@ const CompletionBanner: React.FC<CompletionBannerProps> = ({
 
         <p className="transition-opacity duration-200 text-muted-foreground">
-          {isLoading ? "Generating fix..." : "Showing fix"}
+          {isLoading ? "Generating change..." : "AI changed this cell"}
         </p>
       </div>
</file context>

onDecline={() => handleCompletion("reject")}
size="xs"
runCell={isDeletion ? undefined : runCell}
acceptLabel={isDeletion ? "Delete cell" : "Keep change"}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: When isDeletion is true, the destructive Delete cell action uses the green accept styling while the non-destructive Keep cell action uses the red reject styling. Give deletion actions explicit destructive and non-destructive variants so their colors match their consequences.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At frontend/src/components/editor/cell/StagedAICell.tsx, line 80:

<comment>When `isDeletion` is true, the destructive `Delete cell` action uses the green accept styling while the non-destructive `Keep cell` action uses the red reject styling. Give deletion actions explicit destructive and non-destructive variants so their colors match their consequences.</comment>

<file context>
@@ -47,21 +48,40 @@ export const StagedAICellFooter: React.FC<{ cellId: CellId }> = ({
+            onDecline={() => handleCompletion("reject")}
+            size="xs"
+            runCell={isDeletion ? undefined : runCell}
+            acceptLabel={isDeletion ? "Delete cell" : "Keep change"}
+            declineLabel={isDeletion ? "Keep cell" : "Revert change"}
+          />
</file context>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants