Probe: browser-restart resume on mobile + grace-window UX during opponent wait #123

Closed
opened 2026-06-23 14:41:23 +02:00 by bosun · 1 comment
Owner

Behavior (operator playtest 2026-06-23 post round-22)

"Restarting the browser on mobile failed to resume the previous play session. The desktop part was sitting frozen for half a minute."

Operator tested mobile-versus scenario:

  1. Active versus match between mobile + desktop
  2. Closed mobile browser entirely (different from page-reload OR ws.close())
  3. Restarted mobile browser → expected to resume the previous play session
  4. Resume failed
  5. Desktop (the survivor) sat frozen for ~30 seconds before resolving

Likely explanations (NEEDS PROBE)

For mobile-side resume failure:

  • LS_TOKEN may not persist across full browser restart (vs page-reload which keeps localStorage)
  • 25s server grace window may have already expired by the time mobile re-opened browser + navigated back to URL
  • Resume code path may not be triggered automatically on browser-fresh-open vs page-reload

For desktop-side ~30s freeze:

  • Likely matches expected behavior: server grace window (~25s per #93 substrate-grounded reference: server grace = 25s) + UI render-loop slack = "half a minute"
  • During grace, server holds match; on grace-expiry, server declares survivor wins
  • If desktop showed nothing during the wait → that's the #93-adjacent survivor-overlay finding (filed separately)

Severity: LOW-MED — probe-first

The desktop-freeze portion is likely expected-grace-window-behavior (not a bug); the mobile-resume failure is the substantive question.

Acceptance criteria (refine per probe)

  1. Probe identifies whether LS_TOKEN persists across browser restart
  2. Probe verifies grace-window timing in operator's scenario (25s elapsed before mobile re-opened?)
  3. If LS_TOKEN persists + grace-window not expired but resume still fails: fix the resume path to handle browser-fresh-open
  4. If LS_TOKEN doesn't persist: document as known limitation OR fix with cookie/server-side session
  5. If grace window expired: document the limit clearly (or extend grace if operator wants longer)

Cross-refs

  • cellblock#93 (ws.onclose/onerror; established 25s server grace + client resume-on-onopen substrate)
  • cellblock#115 (#93 AC3 follow-up for reconnect-via-resume; may overlap)
  • Anchor: substrate-grounded reference per Shipwright's #93 pre-flight ab34 ("Server grace = 25s reconnect.go; client already sends resume-on-onopen")

Anchor

2026-06-23 operator playtest cross-device resume cycle.

## Behavior (operator playtest 2026-06-23 post round-22) > "Restarting the browser on mobile failed to resume the previous play session. The desktop part was sitting frozen for half a minute." Operator tested mobile-versus scenario: 1. Active versus match between mobile + desktop 2. Closed mobile browser entirely (different from page-reload OR ws.close()) 3. Restarted mobile browser → expected to resume the previous play session 4. Resume failed 5. Desktop (the survivor) sat frozen for ~30 seconds before resolving ## Likely explanations (NEEDS PROBE) **For mobile-side resume failure**: - LS_TOKEN may not persist across full browser restart (vs page-reload which keeps localStorage) - 25s server grace window may have already expired by the time mobile re-opened browser + navigated back to URL - Resume code path may not be triggered automatically on browser-fresh-open vs page-reload **For desktop-side ~30s freeze**: - Likely matches expected behavior: server grace window (~25s per #93 substrate-grounded reference: server grace = 25s) + UI render-loop slack = "half a minute" - During grace, server holds match; on grace-expiry, server declares survivor wins - If desktop showed nothing during the wait → that's the #93-adjacent survivor-overlay finding (filed separately) ## Severity: LOW-MED — probe-first The desktop-freeze portion is likely expected-grace-window-behavior (not a bug); the mobile-resume failure is the substantive question. ## Acceptance criteria (refine per probe) 1. Probe identifies whether LS_TOKEN persists across browser restart 2. Probe verifies grace-window timing in operator's scenario (25s elapsed before mobile re-opened?) 3. If LS_TOKEN persists + grace-window not expired but resume still fails: fix the resume path to handle browser-fresh-open 4. If LS_TOKEN doesn't persist: document as known limitation OR fix with cookie/server-side session 5. If grace window expired: document the limit clearly (or extend grace if operator wants longer) ## Cross-refs - cellblock#93 (ws.onclose/onerror; established 25s server grace + client resume-on-onopen substrate) - cellblock#115 (#93 AC3 follow-up for reconnect-via-resume; may overlap) - Anchor: substrate-grounded reference per Shipwright's #93 pre-flight ab34 ("Server grace = 25s reconnect.go; client already sends resume-on-onopen") ## Anchor 2026-06-23 operator playtest cross-device resume cycle.
Owner

Probe findings — root cause: resume token in sessionStorage (cleared on browser-restart)

Probe complete (static-substrate-grounded; recommend verify-on-live on the fix PR).

Root cause (CONFIRMED, client-lane): the resume token is stored in sessionStorage, not localStorageclient/src/net.ts:108 (read), :157 (write), :183/:217/:258 (clear). The const is misleadingly named LS_TOKEN. sessionStorage survives a page-reload within the same tab but is cleared when the browser/tab is fully closed. So the operator's exact scenario — close mobile browser entirely → reopen → navigate back — finds no token at ws.onopen (net.ts:108) and falls through to a fresh sendJoin() (net.ts:113) instead of {type:'resume'}. That's the "resume failed → fresh session" observed.

  • AC1 (LS_TOKEN persistence): does NOT persist across browser restart — sessionStorage by design.
  • AC2 (grace timing): secondary — even within the 25s grace, browser-restart loses the token regardless of timing.

Fix options:

  • (A) sessionStoragelocalStorage (client-lane, my substrate): enables browser-restart resume within the 25s grace. Trade-offs: (i) cross-tab collision — two CELLBLOCK tabs would share one token slot, but this self-heals: the server rejects a stale/foreign token (Resume → "unknown/expired" / "seat still active", reconnect.go:188/202) and the client clears it + falls back to join (net.ts:214-218); (ii) a stale token from a long-past match is attempted once on next open, then cleared on server-reject. Net: low-risk, directly fixes the operator scenario.
  • (B) document as known limitation: only same-tab reload resumes; browser-restart is out of scope.

Residual regardless of (A): the 25s server grace (reconnect.go:26) means even with localStorage, resume only works if the browser reopens within ~25s of the drop — a narrow window on mobile. Beyond that, no resume is possible without extending grace (server-side, Engineer-adjacent, with its own cost of holding matches longer). NB: this window is further squeezed by the server-side drop-detection latency documented in #122 — the grace timer only starts once the server detects the drop, which can lag well past 25s.

Lean: (A) localStorage — low-cost, self-healing, directly addresses the scenario; pair with honest UX copy when resume fails ("session expired — starting fresh"). Surfacing as a fork rather than unilaterally building because it touches the session-token substrate; the grace-extension question is deferred to Engineer.

#82 note (gated decision): the server already guards double-attach (Resume rejects a seat that isn't disconnected, reconnect.go:200-203), which is relevant to whether the #82 idempotency-token-guard is moot.

Cross-ref: #122 findings — the survivor-side "30s freeze" traces to a separate server-side detection-latency finding.

## Probe findings — root cause: resume token in `sessionStorage` (cleared on browser-restart) Probe complete (static-substrate-grounded; recommend verify-on-live on the fix PR). **Root cause (CONFIRMED, client-lane):** the resume token is stored in `sessionStorage`, **not** `localStorage` — `client/src/net.ts:108` (read), `:157` (write), `:183`/`:217`/`:258` (clear). The const is misleadingly named `LS_TOKEN`. `sessionStorage` survives a page-reload *within the same tab* but is **cleared when the browser/tab is fully closed**. So the operator's exact scenario — close mobile browser entirely → reopen → navigate back — finds no token at `ws.onopen` (net.ts:108) and falls through to a fresh `sendJoin()` (net.ts:113) instead of `{type:'resume'}`. That's the "resume failed → fresh session" observed. - **AC1 (LS_TOKEN persistence):** does NOT persist across browser restart — `sessionStorage` by design. - **AC2 (grace timing):** secondary — even *within* the 25s grace, browser-restart loses the token regardless of timing. **Fix options:** - **(A) `sessionStorage` → `localStorage`** (client-lane, my substrate): enables browser-restart resume *within the 25s grace*. Trade-offs: (i) cross-tab collision — two CELLBLOCK tabs would share one token slot, but this **self-heals**: the server rejects a stale/foreign token (`Resume` → "unknown/expired" / "seat still active", reconnect.go:188/202) and the client clears it + falls back to join (net.ts:214-218); (ii) a stale token from a long-past match is attempted once on next open, then cleared on server-reject. Net: low-risk, directly fixes the operator scenario. - **(B) document as known limitation:** only same-tab reload resumes; browser-restart is out of scope. **Residual regardless of (A):** the 25s server grace (reconnect.go:26) means even with `localStorage`, resume only works if the browser reopens within ~25s of the drop — a narrow window on mobile. Beyond that, no resume is possible without extending grace (server-side, Engineer-adjacent, with its own cost of holding matches longer). **NB:** this window is further squeezed by the server-side **drop-detection latency** documented in #122 — the grace timer only *starts* once the server detects the drop, which can lag well past 25s. **Lean:** **(A) localStorage** — low-cost, self-healing, directly addresses the scenario; pair with honest UX copy when resume fails ("session expired — starting fresh"). Surfacing as a fork rather than unilaterally building because it touches the session-token substrate; the grace-extension question is deferred to Engineer. **#82 note (gated decision):** the server already guards double-attach (`Resume` rejects a seat that isn't `disconnected`, reconnect.go:200-203), which is relevant to whether the #82 idempotency-token-guard is moot. Cross-ref: **#122** findings — the survivor-side "30s freeze" traces to a separate **server-side detection-latency** finding.
bosun closed this issue 2026-06-23 18:54:08 +02:00
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
frankenbit/cellblock#123
No description provided.