Summary
In a private window or when sessionStorage is unavailable/disabled, the per-tab clientId regenerates on every page reload, which breaks WebSocket echo-suppression across a reload: in-flight events for a mutation issued before the reload carry the old origin_client_id, no longer match, and are treated as external — so the user can see a pill for their own pre-reload change.
Root cause
resolveClientId() (frontend/src/stores/useWsStore.js:11-36) reads/writes sessionStorage["pixlstash:clientId"] inside try/catch (correctly logged, no silent swallow). When the write throws, the id is in-memory only and is regenerated on the next reload (:27-35). The mismatch is then detected at useGridRealtimeSync.js:274.
This is narrow and environment-gated — in normal browsers the id is stable for the tab's lifetime, and no mutating request path bypasses the X-Client-Id header (verified: single axios client, no mutating raw fetch). Lower priority than the dropped-origin and coalescing defects.
Reproduction
Manual (Phase 3 checklist): open a private window, perform a tag/score edit, reload immediately, observe whether a pill appears for the just-made change. No automated spec yet (Playwright contexts have working storage).
Fix direction
Fall back to a longer-lived identifier (e.g. derive from an existing stable signal) or accept the limitation and document it. Low priority.
Full analysis: docs/reviews/2026-06-grid-refresh-cleanup-plan.md §7.
Summary
In a private window or when
sessionStorageis unavailable/disabled, the per-tabclientIdregenerates on every page reload, which breaks WebSocket echo-suppression across a reload: in-flight events for a mutation issued before the reload carry the oldorigin_client_id, no longer match, and are treated as external — so the user can see a pill for their own pre-reload change.Root cause
resolveClientId()(frontend/src/stores/useWsStore.js:11-36) reads/writessessionStorage["pixlstash:clientId"]inside try/catch (correctly logged, no silent swallow). When the write throws, the id is in-memory only and is regenerated on the next reload (:27-35). The mismatch is then detected atuseGridRealtimeSync.js:274.This is narrow and environment-gated — in normal browsers the id is stable for the tab's lifetime, and no mutating request path bypasses the
X-Client-Idheader (verified: single axios client, no mutating rawfetch). Lower priority than the dropped-origin and coalescing defects.Reproduction
Manual (Phase 3 checklist): open a private window, perform a tag/score edit, reload immediately, observe whether a pill appears for the just-made change. No automated spec yet (Playwright contexts have working storage).
Fix direction
Fall back to a longer-lived identifier (e.g. derive from an existing stable signal) or accept the limitation and document it. Low priority.
Full analysis:
docs/reviews/2026-06-grid-refresh-cleanup-plan.md§7.