Skip to content

fix(agents): ask before switching or closing drops unsaved edits - #2503

Open
matt2e wants to merge 4 commits into
mainfrom
broken-9
Open

matt2e wants to merge 4 commits into
mainfrom
broken-9

Conversation

@matt2e

@matt2e matt2e commented Nov 4, 2026

Copy link
Copy Markdown
Contributor

Summary

On the Agents page, choosing another agent or closing the agent panel used to drop unsaved edits without asking. It now uses the same "Discard changes?" prompt the pane already shows when you switch section tabs. The prompt reads "Your unsaved changes to {name} will be lost." and offers Keep editing and Discard changes.

  • What asks: these all ask first:

    • in the grid: a card click, the arrow keys, Home and End, Escape, or a second click on the open card
    • a card's Open settings
    • finishing Create
    • marketplace Open and Explore
    • closing the panel with the tab ×, Escape in the panel, or the native close-tab command

    Unsaved section drafts, a staged harness change and an unsaved header name all count.

  • Focus follows the answer: Keep editing returns focus to the card that is still selected. Discard changes moves it to the card you chose, or to the card that closed. A declined × no longer pulls focus into the panel, because a PanelWorkspace item's close may now return false to keep its tab.

  • Delete: deleting an agent closes only its own panel, without asking. Deleting a different agent no longer closes the open one.

  • Import an agent: Import opens as a dialog over the page, so the open agent and its draft stay, without asking.

  • While a save is pending: the selection doesn't change. The grid is marked aria-busy, and the other cards dim (0.5 opacity, progress cursor), as the panel's other section tabs grey out.

Pattern matched

The main model is the Agents pane's own section-switch prompt in AgentTabs. Its alert and reporting hook moved into DiscardChanges and useAgentSaveReports in src/features/agents/AgentSaveButton.tsx, so both prompts render the same component and differ only in their description.

The close guard in Settings → Profile ("Discard changes? Your profile has unsaved changes.") also follows this pattern. I didn't use ChannelDetailsEditor or WorkflowChannel as models: they confirm inside their own dialogs, which doesn't fit a side panel. docs/agents.md describes the new behavior.

Tests

AgentsPage.test.tsx adds 11 cases:

  • switching away from a draft, then Keep editing or Discard changes
  • closing with a draft, and a different agent's deletion leaving that draft open
  • an unsaved name, with the selection held while it saves
  • a staged harness change
  • Create and a card's Open settings over a draft
  • marketplace Open over a draft, with either answer
  • Import keeping the draft, and deleting the open agent closing it without asking
  • grid focus after a click, a second click, Escape and the arrow keys, for both answers
  • a list that reorders behind the prompt
  • an unfinished agent's Delete finishing after a link opened another agent

PanelWorkspace.test.tsx covers native close falling back from the main conversation to a side tab that declines.

These checks passed before the branch was rebased onto current main: biome, lint, typecheck, design checks, the agents, panels and profiles suites, and every PanelWorkspace consumer. They haven't been re-run since the rebase. The patches are unchanged.

Known gap

Links, toasts and Back/Forward can still change the selection without asking, under the navigation-history contract.

Scope

This fixes only the unsaved-edits half of "Agents page: switching agents or closing the panel drops unsaved edits, and approved agent requests show nothing; one PR each, and no open PR fixes either". The approved agent requests issue is not addressed here.

Before / after

Side by side, before on the left and after on the right. With Ada's edit unsaved, the recording clicks Bo, then × on Ada's panel tab. Before, both drop the edit. After, both ask first.

agents-unsaved-edits-before-after.mp4

After: clicking Bo with Ada's Instructions unsaved asks "Discard changes? Your unsaved changes to Ada will be lost."

After: × on Ada's panel tab with an unsaved edit shows the same prompt instead of closing the panel.

Existing prompts this matches

Captured before the change:

Agents pane: switching section tabs with unsaved Instructions already asked "Discard changes?" with Keep editing and Discard changes.

Settings → Profile: Escape with an unsaved Display name shows the same dialog.

The gap: clicking Bo with Ada's edit unsaved opened Bo with no prompt.

🤖 Generated with Claude Code

@matt2e
matt2e marked this pull request as ready for review November 4, 2026 22:05
@matt2e
matt2e requested a review from a team as a code owner November 4, 2026 22:05
@matt2e
matt2e force-pushed the broken-9 branch 2 times, most recently from f782b4f to 301ae1d Compare November 5, 2026 01:17

@kazemek21 kazemek21 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.

Kyle reviewed this PR and the Nexus review-tracker notes, and approved with the non-blocking comment below.

Posted by Codex (gpt-6.1-sol) on behalf of kylez.

Comment thread src/bundled/agents/AgentsPage.tsx Resolved
matt2e and others added 3 commits November 5, 2026 17:20
On the Agents page, choosing another agent (card click, arrow keys,
Escape or a second click in the grid, Open settings, finishing Create,
a marketplace Open), closing the agent panel (tab X, Escape in the
panel, the native close-tab command) and New agent > Import an agent
remounted or unmounted the panel without asking. That silently dropped
unsaved section drafts, a staged harness change and a half-typed header
name.

These paths now use the pane's existing section-switch guard: the same
"Discard changes?" alert with Keep editing and Discard changes, saying
"Your unsaved changes to {name} will be lost." AgentTabs reports its
combined section and staged-harness state upward through the existing
AgentSaveState contract, AgentDetails reports the header name editor,
and the page collects both with the aggregation hook extracted from
AgentTabs. Keep editing leaves the agent selected with its draft;
Discard changes carries out the request without saving. While a save is
pending the selection stays. Deleting an agent closes only its own
panel, without asking, so deleting a different agent no longer closes
the open one.

A PanelWorkspace item's close may now return false when it keeps the
tab, so a declined X does not leave focus recovery armed and pull focus
from the grid into the panel on the next agent change.

Route-driven selection (links, Back/Forward) stays unguarded under the
navigation-history contract.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: mtoohey <mtoohey@squareup.com>
…raft guard

Follow-up to review of the unsaved-edits guard on the Agents page.

Arrow, Home and End in the agents grid now move focus only once the
selection actually changes. Keeping a draft leaves focus on the current
card instead of the one the key pointed at; confirming a discard moves
focus to the newly selected card.

PanelWorkspace types an item's close as returning boolean or nothing.
When the native close-tab command falls back from the permanent main
conversation to a side tab that declines, it no longer pulls focus back
to the main pane, so the prompt the side tab opened keeps it.

Explore the marketplace and a marketplace listing's Open now wait for
the guard before switching tabs or closing the listing; keeping a draft
leaves both as they were. Deleting an unfinished hosted agent from its
peek panel closes only its own panel, as Delete does elsewhere.

Import an agent no longer clears the selection. It opens a modal dialog
over the page that does not read the selection, and its completion only
returns to Your agents, so the open agent and its draft now stay
without asking.

While a save is pending the agents grid is marked aria-busy, matching
the greyed section tabs and the Saving... button inside the panel.

Tests cover a staged harness change asked about at page level, Create
and a card's Open settings with a draft open, Import keeping the draft,
the busy grid, arrow-key focus after both answers, and the declined
fallback close in PanelWorkspace.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: mtoohey <mtoohey@squareup.com>
Follow-up to the second review of the Agents page draft guard.

Escape on the selected card and a second click on the open card now move
focus to that card once a discard closes it. Before, once a closed card
left the tab order, focus could land elsewhere, such as on a card's
actions button, and the arrow keys stopped working. A click on
another card that the guard refuses, while a draft is kept or a save is
pending, puts focus back on the card still selected. A discard then
moves focus to the clicked card. The grid finds these cards by key, so
a list that reorders while the prompt is open cannot send focus to the
wrong one.

While a save is pending the grid is marked aria-busy, as before, and
now also shows it: the other cards grey at half opacity with a progress
cursor, as the panel's other section tabs grey.

Opening one of your agents from a marketplace listing now moves focus
to Your agents once it opens, as importing from the listing does. The
two share one handler.

Tests cover keeping and discarding after a click, a second click, and
Escape with focus on the right card; a reorder behind the prompt;
marketplace Open with a draft, kept and discarded; and an unfinished
agent's Delete finishing after a link opened another agent, which now
leaves that agent open.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: mtoohey <mtoohey@squareup.com>
Addresses review on the Agents page draft guard: closing a dirty panel
with the tab ×, Escape in the panel or native Close Tab reached the
Discard handler with no follow-up. Discard removed the panel and the
element focus would return to, so focus fell to the page body, unlike
closing from the grid.

These closes, and the Close button on an unfinished or remote agent's
peek, now pass the guard a follow-up that focuses the closed agent's
card. The card is found by key when the close goes ahead, so a list
that reorders behind the prompt still gets the right card, and a card
that is gone gets nothing. Keep editing is unchanged.

This also changes a clean close. With no unsaved edits, the same paths
also left focus on the page body, because the whole side panel
unmounts before PanelWorkspace can recover focus. They now focus the
card too. Only focus inside the panel, or on the body, moves. Native
Close Tab from elsewhere, such as the Your agents tab, still closes
the panel and leaves focus where it was. That matches PanelWorkspace's
rule of recovering focus only when a close removes its owner.

The grid's card lookup moves to a module-level helper that the page
and the grid share. Grid Escape, arrow keys and clicks behave as
before.

Tests cover discarding over ×, panel Escape and native Close Tab, with
a reorder behind the × prompt, and a clean close through × and the
peek's Close, plus native Close Tab with focus outside the panel.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: mtoohey <mtoohey@squareup.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants