Persist the session continuously so restore survives the exits it exists for - #495
Merged
Merged
Conversation
) Session restore had two gaps, both from writing the list only at clean close (#468's original scope): any abnormal exit -- crash, task kill, an OS "shut down anyway" past the dirty-tab prompt -- restored zero tabs, and a file-backed plan or query detached into its own window was never written down at all, because SaveOpenPlans walked MainTabControl alone. Membership changes now write the list as they happen, debounced one second so a burst (restore, Close All) lands as one write. The trigger rides the #481 TabContentWatcher rather than the call sites, for exactly that commit's reason: a persist remembered at sixteen call sites gets forgotten at the seventeenth. The two changes the strip cannot show get explicit calls -- the detached register (a window closing changes no tab) and SaveQueryToPath (a scratch gaining its first file changes no membership). Detached windows join the collected set through a second register next to #473's: the prompt register is query-sessions-only because only an edit can be lost, while persistence needs every FILE-backed detached window, plans included. Detached entries append after the docked tabs; on the next start they come back as ordinary docked tabs, deliberately not re-detached windows. The crash-loop defense is kept and sharpened: RestoreOpenPlans clears and saves the empty list BEFORE the first open (it used to clear after the loop, which only defended against crashes after restore finished), and each file that opens successfully re-enters through the debounced writer, which restore flushes synchronously at its end. Net invariant: a file that crashes the app during load never persists -- it died before its own re-add -- while everything that opened does. A crash mid-restore still loses the tabs opened before it (their re-add was pending, the UI thread never flushed it); accepted, and said so in the code. OnClosed keeps its save as the final authoritative write, now with the debounce timer stopped first so no tick lands in a torn-down window. Under the test host no real timer is armed at all -- the suite shares one dispatcher, and a stray tick would write one test's tabs over another's staged state -- so tests drive the flush through a deterministic seam and assert the redirected settings file (#451/#487). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PvAv72Pwb8czsjDWsCCk7n
|
Reviewed the session-persistence changes ( Traced the mechanics closely for the failure modes that matter here — crash/kill mid-session, crash mid-restore, detach/redock ordering, and the scratch-gains-a-path case — and didn't find a correctness bug:
No T-SQL, versioned-project, or Blazor-linked-file changes in this PR, so those repo-convention checks don't apply. Nothing to flag — this looks solid. |
This was referenced Sep 3, 2026
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What does this PR do?
Fixes #490.
Session restore had two gaps: a file-backed tab detached into its own window at exit was never persisted, and the open-tab list was written only at clean close — any abnormal exit (crash, task kill, OS "shut down anyway" past the dirty-tab prompt) restored zero tabs. Restore protection evaporated exactly when it was needed.
AtomicFilepath). The trigger hangs on Notice a plan arriving by every route, not just the one with a test (#447) #481'sTabContentWatcher— the one subscription that sees every open/close/restore/redock/content-swap, per that PR's own sixteen-call-sites rationale — plus exactly three explicit calls for what the strip cannot show: detach registration, detached-window close, and a tab gaining its first file path inSaveQueryToPath. The debounce also quietly does ordering work: a synchronous write at the watcher's moment would land in the gap betweenItems.Removeand the detached-register add and drop the detached file.RestoreOpenPlansstill clears the list — but now BEFORE the first open rather than after the loop, which closes a pre-existing hole: the old ordering meant a crash during restore left the poisoned list intact and looped forever. Each successfully opened tab re-enters via the watcher, and restore ends with a synchronous flush. Net invariant, tested: a file that crashes (or fails) during load never persists; every tab that opened successfully does. A crash mid-restore can lose earlier good tabs for that one start — stated in the doc comment as the accepted cost; the invariant is poison-never-persists.DispatcherTimerunder the harness — the suite shares one dispatcher, and a live timer would tick during a later test and clobber its staged settings. Tests drive an internal flush seam and assert the redirected settings file, not memory.How was this tested?
Eight new tests in
SessionPersistenceTestsplusRestoreQueryTabsTestsupdates: open-persists-without-close, close-removes, detached query/plan persisted with redock-exactly-once and detached-close-removes, post-restore immediate repopulation, the poison-exclusion invariant (a well-formed-but-invalid .sqlplan fails restore and is absent while the good file persists), and scratch-gains-file-joins-list. Existing tests whose "restore does not leak" assumption the new rewrite invalidated were fixed with proper seeding/cleanup rather than weakened. Full suite: 453 tests, 452 passed, 1 platform skip, 0 failed — in the worktree and again in the main checkout.🤖 Generated with Claude Code
https://claude.ai/code/session_01PvAv72Pwb8czsjDWsCCk7n