Skip to content

fix(standalone): stop the browser-dev harness persisting sessions the app drops - #477

Merged
nedtwigg merged 3 commits into
mainfrom
fix/issue-475
Aug 29, 2026
Merged

fix(standalone): stop the browser-dev harness persisting sessions the app drops#477
nedtwigg merged 3 commits into
mainfrom
fix/issue-475

Conversation

@dormouse-bot

@dormouse-bot dormouse-bot commented Aug 28, 2026

Copy link
Copy Markdown
Collaborator

BrowserSidecarAdapter routed saveState/getState straight to localStorage while TauriAdapter gates both behind PERSIST_SESSION = false, so pnpm dev:standalone:ab restored panes across a reload that production standalone deliberately drops. This mirrors the gate in the harness, reports persistsSession: false, and deletes any pre-gate blob on init(). Verified by four new tests in standalone/src/browser-sidecar-adapter.test.ts that fail on main and pass here.

Why mirror rather than document the divergence

#475 left the choice open. Three facts in the repo point the same way:

  • The gate is load-bearing, not cosmetic. docs/specs/standalone.md spells out why: persistsSession: false makes saveSession skip building the record at all, which otherwise costs a getCwd round trip per pane on every debounced save and every 30s heartbeat. In the harness that round trip goes over the HTTP bridge into the sidecar's synchronous lsof. Production never pays it; the harness did.
  • Cross-run carryover contradicts the harness's own design. standalone/scripts/dev-agent-browser.mjs gives each run its own temp state dir explicitly "so a dev run never touches the installed app's state" — localStorage was the one slot that leaked state between runs, and PersistedSession carries transcripts.
  • Absent persistsSession reads true (docs/specs/transport.md), so the harness was also contradicting the documented standalone answer rather than merely diverging in behavior.

This is the same shape as #439, which removed the harness's false claim of native file-drop support; the new tests live in the file that PR created.

The lost dev convenience — panes surviving a browser reload — is recoverable the same way it is for TauriAdapter: flip the flag locally. The spec now says to flip both together when workspaces-rollout turns persistence on.

Deleting rather than ignoring the key

Ignoring the key would leave a blob written before this gate sitting in the developer's browser profile indefinitely, transcripts included — localStorage is keyed by profile, not by the per-run temp dir. init() removes it, matching TauriAdapter.init's clear_session cleanup and its stated reason.

Spec updates

  • docs/specs/transport.md → "Standalone browser-dev harness": the contract paragraph now names Session-persistence parity alongside the PTY/clipboard/remote-Host contracts it already listed.
  • docs/specs/standalone.md → "Standalone persists no Session state": two new paragraphs — the harness's gate and the delete-on-init rule, plus what the gate actually costs on reload.
  • docs/specs/transport.md → "Standalone persists no Session state": corrected a pre-existing sentence. "Live resume within a running app is unaffected — it reads the sidecar's live PTY list, not disk" was true of the Sessions and false of the layout: resumeLiveSessions reads getState() for the saved resume plan, so with persistsSession: false every live PTY lands in one tab group with doors and saved titles dropped. This was already the behavior of production standalone before this PR; only the spec text changed.

Both spec corrections came out of the review on 61f4a7ae — the first draft framed the reload cost as panes not surviving, which is wrong in both directions. Nothing wires shutdown() to beforeunload, so the sidecar and its PTYs outlive a page reload and the Sessions do resume; it is the layout that does not.

Testing

Four tests added to standalone/src/browser-sidecar-adapter.test.ts, all failing on main before the fix:

  • reports the same persistsSession as TauriAdapter — was undefined vs false
  • does not write session state to localStorage — the blob was written
  • does not restore a stale blob left by an earlier run — the blob was returned
  • deletes a pre-gate blob on init — the key survived

After the fix: vitest run in standalone/ is 62 passed (7 files), tsc -b clean, pnpm lint:specs OK (24 specs, 25 files).


Closes #475 — automated triage

… app drops

BrowserSidecarAdapter routed saveState/getState straight to localStorage while
TauriAdapter gates both behind PERSIST_SESSION = false, so the harness restored
panes across a reload that production standalone deliberately drops.

Closes #475
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Aug 28, 2026

Copy link
Copy Markdown

Deploying mouseterm with  Cloudflare Pages  Cloudflare Pages

Latest commit: 8f7c092
Status: ✅  Deploy successful!
Preview URL: https://618f2e8f.mouseterm.pages.dev
Branch Preview URL: https://fix-issue-475.mouseterm.pages.dev

View logs

@dormouse-bot dormouse-bot left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

The gate itself is right and mirrors TauriAdapter cleanly — saveSession gates on persistsSession === false before getPreviousPaneMap, so the claimed getCwd saving is real, and clearPersistedState runs first in init(), ahead of main.tsx's reconnect. One accuracy gap in the new spec text, and one test-isolation trap.

The spec paragraph frames the cost as panes not surviving a reload, but that isn't what happens here. shutdown() is never wired to beforeunload (it's only called from tauri-adapter.test.ts), so the sidecar and its PTYs outlive a page reload and resumeLiveSessions takes the live path. It reads platform.getState() for getSavedResumePlan, which now returns null — so the Sessions do resume, but every live PTY lands in one tab group with doors and saved titles dropped, which is exactly what the comment above that call ("instead of stacking every live PTY into one tab group") warns about. Real standalone has always done this across a WebView reload, so parity is intact and I don't think it changes the verdict — but it makes the reload in .claude/skills/debug-standalone-agent-browser/SKILL.md ("set dormouse.flags.abDebugLogs, then reload") noticeably more disruptive than the paragraph reads, and docs/specs/transport.md's existing "Live resume within a running app is unaffected — it reads the sidecar's live PTY list, not disk" is now the sentence a reader would check against.

Comment thread docs/specs/standalone.md
Comment thread standalone/src/browser-sidecar-adapter.test.ts
…init test

The new spec paragraph read as "panes are gone after a reload", which is wrong in
both directions. Nothing wires `shutdown()` to `beforeunload`, so the sidecar and
its PTYs outlive a page reload and `reconnect.ts` resumes over them — but it reads
`getState()` for the saved resume plan, which the gate makes null, so what is lost
is the layout: every live PTY lands in one tab group with doors and saved titles
dropped. Real standalone has always done this across a WebView reload, so parity
holds; the paragraph now says so.

transport.md's "Live resume within a running app is unaffected — it reads the
sidecar's live PTY list, not disk" had the same gap and predates this PR. Split it
into the Sessions (unaffected) and the layout (not).

The `deletes a pre-gate blob on init` test called the real `init()`, which runs
`installConsoleForwarder()` — it replaces `console.log/warn/error` on the shared
jsdom window with versions that POST to the dev host, behind a window flag nothing
clears. Nothing observes it today since this is the last test in the file, but any
test added below would inherit it. Claim the flag first so the forwarder no-ops.
@nedtwigg
nedtwigg merged commit 1bc3536 into main Aug 29, 2026
8 checks passed
@nedtwigg
nedtwigg deleted the fix/issue-475 branch August 29, 2026 16:53
注册 for free to join this conversation on GitHub. Already have an account? 登录 to comment

标签

None yet

项目

None yet

Development

Successfully merging this pull request may close these issues.

BrowserSidecarAdapter persists sessions that production standalone deliberately drops

2 participants