feat: session snapshot history + programa snapshot restore - #228
Merged
Conversation
Review of this PR surfaced two defects in the new history path. rotateIntoHistory deleted an existing archive before copying its replacement. Archive filenames carry the live file's modification date at second resolution, so two launches can collide on one name, and a copy that failed after the delete (full disk) left neither the old archive nor the new one -- the exact loss this file exists to prevent. It now stages the copy beside the destination and swaps it into place. snapshot.restore reconstructed every window in the archive, while startup restore clamps to maxWindowsPerSnapshot. A corrupt or hand-edited history file could freeze the app on the one command you reach for when recovering. Both paths now go through SessionPersistenceStore.windowsToRestore. Tests cover the clamp and the same-filename rotation (replace, don't duplicate; no staging files left behind). The failure-injection half of the swap -- copy failing mid-rotation -- has no seam to test through and is not covered.
This was referenced Jul 31, 2026
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.
Closes #227
What this does
After a reboot or crash, programa can silently lose the whole window/workspace layout: one file holds it, and the next launch overwrites that file with no backup. This adds a rolling history of the session file, records whether the previous run quit cleanly, and adds
programa snapshot list/restoreto bring a lost layout back as new windows. It also addsprograma worktree open --allto reopen every worktree of a repo in one call.Summary
SessionPersistenceStore.rotateIntoHistory(): on every launch, before load and before any save can clobber, copysession-<bundleId>.jsonintosession-history/<yyyyMMdd-HHmmss>-<bundleId>.json. Keep the 10 newest, skip byte-identical duplicates, best-effort throughout (never blocks launch).AppSessionSnapshot.cleanShutdown: Bool?(optional, so old files still decode). Normal saves writefalse; theapplicationShouldTerminate/applicationWillTerminate/willPowerOffNotificationsaves writetrue.snapshot.list/snapshot.restoreinSources/TerminalController+Snapshot.swift, mirroring theworktree.*handler shape. Restore creates new windows via the samecreateMainWindow(sessionWindowSnapshot:)path startup restore uses; it never closes or mutates existing windows. Wire keys are snake_case, matching the v2 convention.programa snapshot list [--json],programa snapshot restore [<id>|latest], andworktree open --all(mutually exclusive with a positional target and with--focus; reuses the existing idempotentworktree.list+worktree.openRPCs, no app-side changes).docs/plans/snapshot-restore.md, parity-table rows indocs/v2-api-migration.md, CHANGELOG entries.Test plan
cleanShutdown, flag round-trip, corrupt-data decode (8 new tests inprogramaTests/SessionPersistenceTests.swift)snapshot.listshape +snapshot.restoreunknown-id rejection,worktree open --allflag validation (2 new files intests_v2/)programaandprograma-clischemes build clean locally