Skip to content

Open a fresh tab when the final tab closes (APP-5242) - #14826

Open
warp-agent-staging[bot] wants to merge 7 commits into
masterfrom
factory/app-5242-fresh-tab
Open

Open a fresh tab when the final tab closes (APP-5242)#14826
warp-agent-staging[bot] wants to merge 7 commits into
masterfrom
factory/app-5242-fresh-tab

Conversation

@warp-agent-staging

@warp-agent-staging warp-agent-staging Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

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:

  • Pristine final tab → silent no-op. All four close affordances (close button, middle-click, Close tab menu item, 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.
  • Non-pristine final tab → close and replace. A fresh tab is seeded before the old one is detached, using ordinary New Tab semantics (configured default session mode/profile, new-tab placement, working-directory rules). The workspace never exposes or persists a zero-tab state, and the replacement is a normal tab for session restore.

The default-on path is untouched: the window still closes, pristine or not.

How it works

  • Workspace::final_tab_outcome centralizes the decision (CloseWindow / NoOp / Replace) from the preference and ContextFlag::CloseWindow, and both close_tab and remove_tab route through it. Confirmation is no longer deferred to the window-close prompt when the window is going to stay open.
  • Pristine is a one-way runtime marker on TabData, never inferred from visible emptiness. It fails closed: TabData::new produces a non-pristine tab and only TabData::new_pristine opts 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 broad AppStateChanged notification: 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.
  • Undo Close pairs the closed final tab with the exact replacement that took its place and swaps that replacement out only while it is still pristine; otherwise it restores alongside so user work is never discarded.
  • Cross-window drag: a committed handoff out of a one-tab source leaves a fresh tab behind instead of closing the source. The replacement is created only on commit, so a cancelled or returned drag never leaks a tab.
  • 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:

./script/install_channel_config    # once, puts warp-channel-config on PATH
cargo run --bin warp

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:

  • Pristine tracking missed several group mutations. Only individual pin/unpin marked the tab. Group pin/unpin now marks every member (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.
  • The pristine default now fails closed. TabData::new produces a non-pristine tab and TabData::new_pristine is 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.
  • A submitted agent prompt did not break pristine, though the spec makes it a one-way mutation. The whole submission family now emits the session-mutation signal: a shared-session viewer prompt (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.
    • Entering agent view is deliberately left unmarked: the runtime itself enters it when seeding a replacement under a default session mode of Agent, so marking there would make every replacement born non-pristine.
  • Close outcome is now reported honestly. close_tab returns whether a close actually happened, and the local-control close handler says so rather than only blaming a cancelled warning dialog.
  • Replacement identity is derived by diffing the tab list instead of trusting the active index after seeding.
  • Visual proof re-recorded to cover the two paths the previous capture omitted — the touched-replacement restore-alongside undo branch and the default-on window close. The screenshot/video block below is fully replaced with the current capture.

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 InputTerminalViewPaneGroupWorkspace chain, 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:

  • Setting defaults to on and round-trips through the settings store.
  • The default path still closes the window and seeds no replacement, pristine tab included.
  • Three repeated closes on a pristine final tab leave tab, pane group, pristine state, confirmation state and the undo stack untouched.
  • A non-pristine final tab is replaced, leaving exactly one tab whose pane group differs and which starts pristine.
  • A multi-tab close that drains to one tab lands on the same outcome with no stale active index.
  • Both Undo Close branches: swap out a still-pristine replacement, restore alongside a touched one.
  • CloseWindow-disabled hosts: all four affordances available (including the workspace:close_active_tab binding being in context) and both outcomes reachable.
  • Both cross-window handoff branches.
  • The pristine contract itself, driven through the production event chain rather than injected events.

ContextFlag gained a thread-local, RAII test override (mirroring FeatureFlag::override_enabled) so the CloseWindow-disabled host can be exercised without mutating process-wide state.

Repository checks, all from the repo root with the current master merged into this branch:

  • ./script/format --check — clean.
  • cargo clippy --workspace --exclude warp_completer --all-targets --tests -- -D warnings and cargo 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-TAB and closes that tab so the window stays open with a fresh replacement, runs echo SECOND-TAB in that replacement, then runs Reopen closed session — which restores the closed tab alongside the worked-in replacement, leaving both FIRST-TAB and SECOND-TAB intact — 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)

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

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

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.
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).
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.
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.
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 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 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.
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".
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 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.
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.
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.

Co-Authored-By: Warp Agent <agent@warp.dev>
@cla-bot cla-bot Bot added the cla-signed label Aug 7, 2026
@warp-agent-staging warp-agent-staging Bot added the warpy-factory Label associated to the "Wob the Wuilder" factory on staging, also known as Warpy Factory. label Aug 7, 2026
oz-agent and others added 3 commits August 7, 2026 15:08
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>
@warp-agent-staging warp-agent-staging Bot changed the title Spec: APP-5242 open a fresh tab on final close Open a fresh tab when the final tab closes (APP-5242) Aug 7, 2026
@warp-agent-staging
warp-agent-staging Bot marked this pull request as ready for review August 7, 2026 17:10
@warp-agent-staging
warp-agent-staging Bot requested a review from Xavientois August 7, 2026 17:10

@warp-agent-staging warp-agent-staging Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

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>
@warp-agent-staging
warp-agent-staging Bot force-pushed the factory/app-5242-fresh-tab branch from ca16eef to 36ea83d Compare August 7, 2026 18:52
oz-agent and others added 2 commits August 7, 2026 19:22
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>

@warp-agent-staging warp-agent-staging Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

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

⚠️ [IMPORTANT] Visual proof does not demonstrate the default-on final-close acceptance criterion. The refreshed recording shows the setting and the touched-replacement Undo Close branch, but its final default-on sequence ends with the last tab/window still visible; it never shows the window closing after the close action. The committed spec requires proof that closing a pristine only tab with the setting on closes the window. Please re-record that sequence through the visible window-close result and refresh the linked evidence.

Verification

  • cargo nextest run -p warp --lib pristine --no-fail-fast: 14 passed
  • cargo nextest run -p warp --lib agent_prompt --no-fail-fast: 1 passed
  • cargo nextest run -p warp --lib pane_marks --no-fail-fast: 1 passed
  • git 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.

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

Labels

cla-signed warpy-factory Label associated to the "Wob the Wuilder" factory on staging, also known as Warpy Factory.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant