Skip to content

feat(windows): reopen every window of the last session - #990

Merged
PathGao merged 2 commits into
masterfrom
feat/restore-all-windows
Oct 20, 2026
Merged

PathGao merged 2 commits into
masterfrom
feat/restore-all-windows

Conversation

@PathGao

@PathGao PathGao commented Oct 20, 2026

Copy link
Copy Markdown
Collaborator

What this is

Quitting with several windows open now brings all of them back on the next launch, each with its own tabs, unsaved changes (#962), position and size. Before, only the main window restored its tabs, and other windows' unsaved changes were merged into it.

Closing one of several windows still forgets it, as VS Code does. Quit, or closing the last window, remembers every window.

Mechanism

Restore-state had one slot, window-state-v2.json, read only by the main window, and secondary windows got a new random label every launch. Nothing a secondary window saved could be claimed by the window that replaced it.

  • A window's label is now kept across launches. A reopened window is recreated with the label it had, by the same factory tear-off uses.
  • Each window has its own snapshot in window-state/<label>.json and its own crash guard in restore-progress/<label>.json. The main window keeps window-state-v2.json and restore-progress-v1.json, so existing data needs no migration and an older build still reads it. Paths are derived in Rust from the calling window, with the same label validation and symlink refusal as feat(editor): keep unsaved changes when quitting #962.
  • Each window writes only its own file, so the quit race where a later-closing secondary overwrote the main window's snapshot (ownsSnapshot) is gone.
  • Remember or forget uses feat(editor): keep unsaved changes when quitting #962's quit/last-window test (keeps_unsaved_on_close). Closing one of several windows, merging a window away, or closing its last tab forgets it. Turning off restore-on-reopen discards every snapshot.
  • Startup: the main window restores itself, then reopen_remembered_windows recreates the others and returns their labels. The main window skips their backups, using that list rather than the window registry, which lags until each frontend registers. Each reopened window restores its own snapshot and its own unsaved-recovery/<label>.json. Backups with no remembered window, for example after a crash, still go to the main window.
  • A reopened pinned-tag window takes its tabs from its snapshot and doesn't load the pinned group a second time.
  • The window-state plugin no longer folds every secondary window into one "secondary" geometry entry, so each window reopens where it was.

Scope

  • After a crash, windows are not rebuilt. The window list is recorded on quit, and orphaned unsaved tabs land in the main window as before. Recording the session continuously, so a crash restores each window, is planned next.
  • On an update restart, only the window that ran the update writes its snapshot. Other windows come back with their tabs as of that launch.
  • If a quit is cancelled partway, windows already closed are still remembered.
  • A newly torn-off window opens at the default 1000×800 instead of the last secondary window's size.
  • The window-state plugin has no delete, so a forgotten window's geometry entry (about 200 bytes) stays in its file.

Tests

  • Rust: path layout and label validation; per-window save, forget, list and clear, including the v2 file as the main window's entry; symlink refusal.
  • claimableRecords decides which backups the main window takes, with a behaviour test.
  • lastWindowWritesSession became perWindowSession: each window writes or forgets only its own snapshot.
  • windowTagPerWindow now checks that a new window doesn't read the main window's snapshot and that a reopened window gets its own tag back. It used to assert that a detached window never restores.

Verification

npm run check 0 errors, npm test 1134 pass, npx vitest run 515 pass, cargo test 200 pass, cargo fmt --check and cargo clippy --all-targets -- -D warnings clean.

macOS 27, release test bundle:

  • Two windows, the secondary moved to 40,60 and resized to 620×460 with an unsaved untitled tab. After Quit, both came back at their positions and sizes, and the untitled tab came back in the secondary window, not the main one.
  • After closing the secondary window (asked, discarded) and quitting, only the main window came back, and the secondary's snapshot and backup files were gone.
  • After kill -9 with an unsaved tab in a secondary window, the main window came back holding that tab.

Not run on Windows or Linux.

Main keeps window-state-v2.json and restore-progress-v1.json, so an
existing session is main's entry with no migration. Every window-* label
gets window-state/<label>.json and restore-progress/<label>.json, each
written only by the window that owns the label, so windows closing at
once (quit) can no longer overwrite one another.

New commands: forget_window_state drops the caller's snapshot and
breadcrumb; reopen_remembered_windows rebuilds each remembered window
under its saved label through the same factory as tear-off windows.
clear_window_state now clears every window's state.

The window-state plugin no longer folds window-* labels into one
"secondary" entry, so each window gets its own size and position back.
Each window restores its own snapshot under the existing crash guard,
now keyed by its label. Main restores first, then reopens the other
remembered windows; a reopened window skips the tab-transfer claim and
the pinned-group load, so its snapshot owns its tabs.

A window is remembered on quit, when it is the last one, and on update.
Closing one of several windows, merging it away, or closing its last
tab forgets it, the same predicate #962 uses to decide hot exit.

Unsaved backups follow their window: a reopened window takes its own
record, and main takes only records no open or just-reopened window
will claim. With restore off nothing is reopened and every backup still
lands in main.

Tests: the single-slot contract is replaced. lastWindowWritesSession
becomes perWindowSession (each window writes or forgets its own entry
regardless of who else is open). windowTagPerWindow now checks that a
new window cannot read main's snapshot, and that a reopened window gets
its own tag back. Source-shape markers follow the new settleForExit
signature.
@PathGao
PathGao merged commit 03fc1e3 into master Oct 20, 2026
4 checks passed
@PathGao
PathGao deleted the feat/restore-all-windows branch October 20, 2026 14:13
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.

1 participant