Open a fresh tab when the final tab closes (APP-5242) - #14826
Open a fresh tab when the final tab closes (APP-5242)#14826warp-agent-staging[bot] wants to merge 7 commits into
Conversation
Co-Authored-By: Warp Agent <agent@warp.dev>
Co-Authored-By: Warp Agent <agent@warp.dev>
Adds the global "Close window when all tabs are closed" preference (Settings -> Features -> General, default on) and the keep-window behavior it unlocks. With the preference off - or in a host that cannot close its window at all - closing the final tab no longer closes the window: - A pristine final tab silently no-ops on all four close affordances. - A non-pristine final tab is closed after a fresh replacement is seeded, so the workspace never exposes a zero-tab state. Pristine state is a one-way runtime marker on TabData, driven by narrow user-mutation signals rather than generic app-state notifications: terminal input edits, command execution, pane-layout actions, and tab rename/color/pin/move. Restored, imported, and transferred tabs start non-pristine. Undo Close pairs a closed final tab with its replacement and swaps that replacement out only while it is still pristine, otherwise restoring alongside it. A committed cross-window handoff out of a one-tab source leaves a fresh tab behind instead of closing the source. Co-Authored-By: Warp <agent@warp.dev>
There was a problem hiding this comment.
Overview
Adds a "Close window when all tabs are closed" preference that keeps the window alive on final-tab close, no-oping a pristine tab and replacing a non-pristine one. The state machine is sound, but the visual proof does not cover the branches the spec asks for, and that needs your call.
Concerns
- The recording proves the non-pristine replace branch and the undo swap-out, but not spec visual criterion 2: it ends at 0:48 with the window still open and the cursor merely hovering the close control, on a tab that had two commands run in it. The PR description nevertheless claims "the setting back on, where closing the last tab closes the whole window" — that claim is not supported by the artifact and should be corrected regardless of what you decide about coverage.
- Criterion 3, the pristine no-op, cannot be established by this capture at all: a silent no-op and a close-and-replace are visually identical, since both leave one empty "New session" tab. Distinguishing them needs a visible signal such as session count, undo depth, or a pane identifier. The PR's own screenshot caption for the earlier artifact in fact reads the opposite way, describing the clicks as having "replaced it with a fresh tab".
- Criteria 6 and 7 have no capture at all, and criterion 5's second half (mutate the replacement, then undo, to show restore-alongside) is also uncaptured. A Dogfood prototype may not need all seven captures — that is your judgment to make — but criterion 6 covers the
CloseWindow-disabled host path, which is where the critical defect now in revision actually bites, so it should not be waived until that fix lands.
Verdict
Checks: build pass, tests pass, CI green, visual proof mismatched
Found: 1 critical, 3 important, 2 suggestions, 2 nits
The critical and the other two important findings are unambiguous and are already going back through a revision pass; they are not reproduced here. This review carries only the finding that needs a human decision.
Invert the pristine default so unobserved tab creation paths fail closed, mark tabs non-pristine on group membership and group-pin changes, report whether a close was actually performed, derive the final-tab replacement's identity by diffing the tab list, and cover the real mutation chain in tests. Co-Authored-By: Warp <agent@warp.dev> Co-Authored-By: Warp Agent <agent@warp.dev>
ca16eef to
36ea83d
Compare
A submitted agent prompt writes into the session, so the spec makes it a one-way pristine mutation, but the input events that carry one only forwarded the prompt. Emit the session-mutation signal for the whole submission family: a shared-session viewer prompt, an agent query from the input box, a committed cloud follow-up, and CLI agent input. Renaming or resetting a pane's name, and resetting a tab's name, are identity mutations the spec also lists and were likewise unmarked. Entering agent view is deliberately left unmarked: the runtime itself enters it when seeding a replacement tab under a default session mode of Agent, so marking there would make every replacement born non-pristine. Co-Authored-By: Warp Agent <agent@warp.dev>
There was a problem hiding this comment.
Review overview
Cumulative re-review of c64caa0e27e21b936281bea358265d4d0df8c8f1 verified that the prior code findings are resolved: tab-group mutations mark affected tabs non-pristine, prompt submission reaches the pristine marker, and the new conservative default preserves explicit pristine seeding for ordinary replacement paths. Focused pristine-state, agent-prompt, and pane-rename regressions pass locally.
Blocking finding
Verification
cargo nextest run -p warp --lib pristine --no-fail-fast: 14 passedcargo nextest run -p warp --lib agent_prompt --no-fail-fast: 1 passedcargo nextest run -p warp --lib pane_marks --no-fail-fast: 1 passedgit diff --check origin/master...HEAD: passed- Linux CI and all formatting/clippy, compile, and CodeQL checks are green; macOS and Windows test jobs were still in progress at review time.
Verdict
Request changes: the implementation is code-clean on this pass, but the required user-facing default-on window-close proof is incomplete.
Summary
Closing the last tab in a window has always closed the window, with no way to opt out. This adds the global preference Close window when all tabs are closed (Settings → Features → General, directly below Quit when all windows are closed, default on) and implements the keep-window behavior it unlocks.
With the preference off — or in a host that cannot close its window at all (web / link-only) — a close that would empty the window resolves to one of two outcomes instead:
workspace:close_active_tab) stay visible and enabled and simply do nothing. No confirmation, no toast, no animation, no undo entry, no session allocation — so repeated Cmd/Ctrl+W cannot accumulate work.The default-on path is untouched: the window still closes, pristine or not.
How it works
Workspace::final_tab_outcomecentralizes the decision (CloseWindow/NoOp/Replace) from the preference andContextFlag::CloseWindow, and bothclose_tabandremove_tabroute through it. Confirmation is no longer deferred to the window-close prompt when the window is going to stay open.TabData, never inferred from visible emptiness. It fails closed:TabData::newproduces a non-pristine tab and onlyTabData::new_pristineopts a tab in, so a creation path added later keeps its close affordances working by default. It is cleared by narrow, user-originated signals rather than the broadAppStateChangednotification: terminal input edits, command execution, every agent-prompt submission, pane-layout actions, tab/pane rename and reset, colour, pin, group membership and group pin. Restored, imported and transferred tabs start non-pristine.CloseWindow-disabled hosts get the four final-tab affordances back; the workspace, not the affordance, decides what a close does there.The approved spec is committed on this branch at
.agents/specs/APP-5242-fresh-tab-on-last-close.md.This is the alternate approach to APP-5230 (empty pane), specced and prototyped in parallel so both variants can be compared hands-on. APP-5230 is untouched by this PR.
Trying the prototype
Check out this branch and run the dogfood/internal channel build:
The toggle ships on, which is today's behavior, so flip Close window when all tabs are closed off in Settings → Features → General to try the new behavior.
Rework changes
Addressing the code-review findings on this PR:
mark_group_members_non_pristine), and creating a group from a tab or selection, moving tabs into a group, removing them from one, and ungrouping all mark the affected tabs. A materially modified final tab can no longer take the silent no-op path.TabData::newproduces a non-pristine tab andTabData::new_pristineis the explicit opt-in, so any tab-creation path added later starts non-pristine and keeps its close affordances working instead of silently swallowing Ctrl+W.SendAgentPrompt), an agent query from the input box (ExecuteAIQuery), a committed cloud follow-up (SubmitCloudFollowup), and CLI agent input (SubmitCLIAgentInput). Sweeping the rest of the spec's mutation list also turned up pane rename/reset and tab-name reset, which are now marked too.close_tabreturns whether a close actually happened, and the local-control close handler says so rather than only blaming a cancelled warning dialog.New regression coverage for the rework: group pinning marks every member, grouping a lone tab stops the no-op, tabs the runtime did not seed start non-pristine, typing reaches the workspace through the real
Input→TerminalView→PaneGroup→Workspacechain, type-then-delete stays non-pristine, navigational interaction preserves pristine state, every agent-prompt submission path marks the tab, and pane rename / tab-name reset mark the tab.Verification
Regression tests in
app/src/workspace/view_tests.rs, covering both preference states and each branch separately:CloseWindow-disabled hosts: all four affordances available (including theworkspace:close_active_tabbinding being in context) and both outcomes reachable.ContextFlaggained a thread-local, RAII test override (mirroringFeatureFlag::override_enabled) so theCloseWindow-disabled host can be exercised without mutating process-wide state.Repository checks, all from the repo root with the current
mastermerged into this branch:./script/format --check— clean.cargo clippy --workspace --exclude warp_completer --all-targets --tests -- -D warningsandcargo clippy -p warp --all-targets --tests -- -D warnings— clean.cargo nextest run --no-fail-fast -p warp -p warp_core(the packages this diff touches) — 6288 passed / 3 failed. The three failures (ambient_agent_headers_for_task_overrides_existing_cloud_agent_header,test_decorations_with_multibyte_chars,test_histignorespace_support_in_zsh) reproduce identically on the unmodified base branch and are environment-dependent, not caused by this change.Visual proof below was captured against this branch's running dogfood build. The current recording walks the setting off, runs
echo FIRST-TABand closes that tab so the window stays open with a fresh replacement, runsecho SECOND-TABin that replacement, then runs Reopen closed session — which restores the closed tab alongside the worked-in replacement, leaving bothFIRST-TABandSECOND-TABintact — and finally switches the setting back on, where closing the last tab closes the whole window.CHANGELOG-NEW-FEATURE: Added a "Close window when all tabs are closed" setting — turn it off and closing the last tab opens a fresh tab instead of closing the window.
Originating thread: https://warpdev.slack.com/archives/C0BDQDW8V5E/p1786112332604639
Computer-use video recordings (3)
Verifying close-window-when-all-tabs-closed setting: Locating the new General setting in Warp Settings → Features, toggling it off, testing close-button behavior on tabs (untouched vs typed-in), undoing a close, then toggling the setting back on and closing the last tab.
Warp close-on-last-tab setting walkthrough: Recording of finding the "close window when all tabs closed" setting in Warp Settings → Features → General, toggling it off, closing an untouched tab three times, running echo APP-5242-MARKER and closing that tab, reopening the closed session, then toggling the setting back on and closing the last tab.
Warp tab close & reopen-session verification: Walkthrough of Warp: turning off the "close window when all tabs closed" setting, closing/reopening tabs via Reopen closed session, then turning the setting back on and closing the last tab so the window closes.
Computer-use screenshots (10)
The Warp window still open with exactly one "New session" tab after clicking the untouched tab's × close button three times (each click replaced it with a fresh tab rather than closing the window).
After typing text into the terminal and clicking the tab's × close button, the Warp window stayed open with one fresh replacement tab titled "bash" and the typed text no longer visible.
Warp Settings → Features → General showing the "Close window when all tabs are closed" toggle (ON), its supporting text, and neighbouring rows.
The Warp window still showing a single "New session" tab and empty terminal pane after clicking the tab's close control three times.
The Warp window immediately after closing the tab that ran echo APP-5242-MARKER: a single fresh "bash" tab with an empty terminal pane, chrome intact.
The Warp window after running "Reopen closed session": a single tab titled "echo APP-5242-MARKER" with the restored command and its APP-5242-MARKER output visible in the pane.
Two-tab state right after running "Reopen Closed Session": tabs "echo FIRST-TAB" (selected, showing FIRST-TAB output) and "echo SECOND-TAB".
Tab "echo FIRST-TAB" selected, its pane showing the command echo FIRST-TAB and its output line FIRST-TAB.
Tab "echo SECOND-TAB" selected, its pane showing the command echo SECOND-TAB and its output line SECOND-TAB.
Screen immediately after closing the final tab with the setting ON: the entire Warp window is gone, leaving only the desktop wallpaper and taskbar.
Conversation: https://staging.warp.dev/conversation/0855efb3-a979-4bc5-94e4-3579ed65bcef
Run: https://oz.staging.warp.dev/runs/019fdccf-a5b0-79cf-9a19-e543ce82c104
This PR was generated with Oz.