fix(extension): persist and serialize per-tab runtime state#8
fix(extension): persist and serialize per-tab runtime state#8llmxtfai wants to merge 2 commits intoonllm-dev:mainfrom
Conversation
Persist per-tab runtime state in chrome.storage.session so hard reloads and MV3 worker restarts do not reset This Tab toggle. Convert background state accessors to async and await them in message handlers. Serialize storage writes with a queue to prevent stale snapshot overwrites, and add regression tests for restart restore and write-order safety.
Add shared log filter that suppresses [onUI...] console.log output in production builds while keeping warnings/errors. Initialize filter in content, background, and popup entrypoints. Keep logs opt-in via localStorage key onui_debug_logs=1 or true.
iota31
left a comment
There was a problem hiding this comment.
Code Review — PR #8
Nice fix for a real MV3 pain point. The serialized write queue and hydration-on-access patterns are well thought out. A few issues to address before merging:
Bug: chrome used directly instead of webext abstraction
The codebase has a cross-browser shim at @/shared/webext (webext resolves browser or chrome at runtime). The existing state.ts and all other extension code uses it. This PR bypasses it entirely:
state.tsconstructor —chrome.tabs.onRemoved.addListener(...)should bewebext.tabs.onRemoved.addListener(...)getStorageArea()— accesseschrome.storagedirectly
This will break Firefox compatibility (Firefox uses browser.storage). Recommend using webext.storage.session (with a type assertion for session if needed, since the webext proxy will forward correctly).
Bug: localStorage is unavailable in MV3 service workers
In logging.ts, isDebugLoggingEnabled() checks localStorage.getItem(...). MV3 service workers have no localStorage — the try/catch prevents a crash, but the opt-in debug flag can never work in the background context. This is effectively dead code for the primary use case.
Consider using chrome.storage.session or chrome.storage.local for the debug flag, or just accept that the background context always suppresses logs (and document that the localStorage flag only works in content/popup).
Nit: persistStates always reads latest in-memory state, not call-time state
This is actually correct behavior (latest state wins), but the doc comment / PR description says "prevent stale snapshot overwrites." Worth a brief inline comment clarifying why reading inside the .then() is intentional — the queue serializes I/O order, not snapshot capture. Future readers might try to "fix" this by capturing state at call time, which would actually introduce the bug it claims to prevent.
Nit: Second commit feels like a separate PR
The log suppression (suppressOnUiDebugLogs) is unrelated to the state persistence fix. Bundling it here makes the PR harder to review and revert independently. Consider splitting it out.
Minor: console.log monkey-patching scope
suppressOnUiDebugLogs only patches console.log, not console.info or console.debug. If any [onUI prefixed messages use those, they'll leak through. Also, the filter matches any string starting with [onUI — this is fine today but could inadvertently suppress future diagnostic messages. Consider matching the full prefix pattern (e.g., [onUI]) more precisely.
Tests look solid
The deferred-write test that controls when storage.set resolves is a particularly nice way to verify serialization. Good coverage of the restart-restore flow.
Summary: The core persistence logic is sound. The main blocker is the webext abstraction bypass — that should be fixed before merge. The logging commit would ideally be a separate PR.
Problem
This Tabcan flip back to Off after hard reload (Ctrl+Shift+R) because MV3 background worker state was in-memory only and gets reset on worker restart.Fix
enabled,annotateMode) tochrome.storage.sessionTests
Added
packages/extension/src/background/state.test.ts:Validation
pnpm --filter @onui/extension test(pass: 7 files, 20 tests)pnpm --filter @onui/extension typecheck(pass)