Skip to content

(restore): save the open sessions when the app closes - #479

Open
abate wants to merge 1 commit into
devsuitup:mainfrom
abate:fix/save-state-on-exit
Open

abate wants to merge 1 commit into
devsuitup:mainfrom
abate:fix/save-state-on-exit

Conversation

@abate

@abate abate commented Oct 5, 2026

Copy link
Copy Markdown
Collaborator

Problem

The open working set (global.openWorkingSet, what restore reopens) is saved on a 500 ms debounce, so:

  1. the last open/close before a quit could be lost;
  2. before-quit kills every PTY while the renderer is alive; each kill arrives as process-exited → entry.closed → a scheduled persist that, if the app lingers past 500 ms (e.g. the ActivityWatch flush, up to 1.5 s), saves an empty set.

Fix

  • Renderer: once a close or quit is confirmed, the unsaved-check handler awaits flushStateForExit() (bounded to 2 s) before answering main. It cancels the debounce, writes the set at once, and blocks further working-set writes for 10 s (restored afterwards in case the exit does not happen, e.g. an installer that fails). A reload does neither; an exit during a restore writes nothing, keeping the previous run's set.
  • Main: appQuitting is set before before-quit kills the PTYs, and process-exited is no longer sent after it.

Docs: docs/session-restore.md → "Closing the app".

Tests

  • New test/exit-flush.test.js loads the shipped app.js functions: immediate write keeps other global keys; shutdown exits cannot empty the set; persistence resumes after the grace period; an exit mid-restore writes nothing.
  • test/dom-file-panel-unsaved-guard.test.js: confirmed quit flushes before answering, reload does not, cancel does not.
  • test/restore-live-elsewhere.test.js harness declares exitingApp.
  • The main-side appQuitting guard has no unit test.
  • Lint 0 errors.

🤖 Generated with Claude Code

The working set was written on a 500 ms debounce, so the last change before
a quit could be lost, and the shutdown's own PTY kills arrived as session
exits that could save an empty set. A confirmed close or quit now writes the
set at once before answering main, then stops saving it; main stops sending
process-exited once before-quit kills the sessions.

@devsuitup devsuitup left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Review at 82cf905. Three points block; each was reproduced by the reviewer against the shipped code and I re-read the paths.

Blocking

  1. Quitting while the index is cold, or before the Restore prompt is answered, overwrites the previous saved set with [] (public/app.js flushStateForExit, persistWorkingSet({ final: true })). Nothing is open yet at that point, so the final write replaces a good set with an empty one. Fix: skip the final write when the restore has not resolved, or keep the saved entries that are not open yet. Test: quit with the saved set unrestored, then check the stored set.
  2. A session stopped on purpose during the restore comes back at the next start. If the user stops a session while the restore is still running and quits before it ends, the unchanged previous snapshot is what stays, so the stopped session is restored again.
  3. Changes made during the ten-second grace period of an aborted exit are never saved. schedulePersistWorkingSet returns early while exitingApp is set, and when exitingAppTimer expires nothing persists again. Fix: persist once when the grace period ends.

Conflicts with #441 (lazy restore)

  • The final serializer must keep the dormant entries (dormantWorkingSet), as #441's persistWorkingSet does; otherwise a quit drops them.
  • Entries the planner has not resolved yet need the same protection as in the #441 review.
  • A dormant entry dismissed during an aborted exit must not be lost. The branch also needs a rebase on current main.

Non-blocking

  • Windows logoff and shutdown bypass the new flush (unsaved-guard.js), so a pending debounce can still be lost.
  • The CHANGELOG entry must end with (#479).

79 tests in the seven related files pass locally on Node 24. Not verified: Node 20/22 with c8.

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.

2 participants