Skip to content

Start Practice when session storage is blocked - #603

Merged
jserv merged 2 commits into
sysprog21:mainfrom
sidney-Hung:lobby-blocked-storage
Nov 8, 2026
Merged

jserv merged 2 commits into
sysprog21:mainfrom
sidney-Hung:lobby-blocked-storage

Conversation

@sidney-Hung

@sidney-Hung sidney-Hung commented Nov 6, 2026 •

Copy link
Copy Markdown
Contributor

Problem and behavior

Where site data is blocked, reading the sessionStorage global throws. The lobby's Start handler named the global in the arguments of its storage helpers, so the exception came before any helper could handle it. Its message landed in #grounding-error, inside a collapsed <details>, and Start looked like it did nothing.

After this change, Practice without grounding opens the interview with storage unreachable. With grounding selected, Practice shows the existing "Clear grounding to start normally" message, since the packet can only reach the interview page through storage. When Practice starts without grounding, a ticked "share this focus" choice is not carried: storeSharedFocus() already treats a failed write as an unshared focus, and a refusal would land in the collapsed section like the original bug. Strict still stays on the lobby because it cannot run without storage, and #grounding-error now holds storageError instead of the raw exception text.

A real browser block also refuses the session cookie. On a server that admits guests, Practice reaches the interview. On a server that requires sign-in, with nobody signed in and session storage unreachable, the lobby says in #account-status as soon as the session answers: "Signing in needs site data, which this browser is blocking for this site. Allow site data for this site, then reload this page." Start stays on the lobby with its choices kept, and pressing it moves focus to the sign-in field, which has aria-describedby="account-status", so the reason is read with the field and is on screen at narrow widths. Without that guard, the storage fix alone would send the candidate to the interview page, which finds nobody signed in and bounces back to a reset lobby. Enter in the sign-in field and the Use GitHub button both still sign in, so a block on storage alone, with cookies working (Firefox's dom.storage.enabled=false may produce this; I have not checked), keeps a way in by keyboard or mouse; there the site-data message is not accurate. Under a full block the login request succeeds but no session follows, and #account-status shows "GitHub username recorded, but no signed-in session was found. Try signing in again." in place of the reason until a reload.

That guard needs the session state, which is unknown until /api/session answers. With session storage unreachable, Start is held while the answer is pending, without the site-data message since the server may still admit guests, and is released when it arrives. If the check fails with no login attempted, loginResult becomes "unchecked": Start stays held, the account row offers "Retry account check" with a message that does not mention signing in, the retry stays a GET, and Log out stays hidden. The cost: with storage blocked and the check failing, a guest also waits for a successful retry before Start.

A storage global named directly, or read as .localStorage or .sessionStorage on any object, under web/ now fails ESLint, apart from five existing references marked one by one, described below.

Limits, not changed here:

From reading the code, once Start reaches the interview with storage unreachable, some existing choices do not survive, as before for any page without storage: the "Show the LeetCode problem" tick, lobby settings after returning, candidate cases on reload, and in-interview preferences. The grounding refusal is also hidden if the candidate closed the section again before pressing Start.

Strict visibility: the storageError text stays hidden while the <details> is collapsed. Where to show a storage failure is an open question in #600, so this change leaves the <details> as it is.

web/peer-lobby.js has the same failure, at module scope when opened from an invite link, and its fix needs a decision about where the invite token lives once the page has removed it from the URL. Its five references each carry their own eslint-disable-next-line no-restricted-globals, and reportUnusedDisableDirectives is an error, so a sixth reference fails and each fix removes its own directive. I will open a follow-up issue for it, as agreed in review, and another to move storageArea, readStored and writeStored to web/storage.js so the lint message no longer points at the audio module.

Approach

The first commit resolves storageArea("sessionStorage"), added in d5ad668, once at module scope into tabStorage, with tabStorageBlocked derived from it, since the block lifts only on reload. The Start handler and clearGrounding pass tabStorage to their storage helpers, which already handle the null it can be. It also adds the site-data reason and the Start guard above, the hold while the session is pending, and the "unchecked" value of loginResult for a failed check, all only when session storage is unreachable. tests/browser/account.test.js asserted the call text, so it now matches each call through tabStorage and that tabStorage is bound to storageArea("sessionStorage"). tests/browser/lobby-render.test.js pinned the old failed-check state and relied on the runtime having no storage globals; it now makes both localStorage and sessionStorage throw before importing /app.js, and expects the new state. New lobby tests cover Start with storage unreachable in Practice, in Practice with grounding selected, in Practice with a shared focus ticked, in Strict, on a server that requires sign-in (the reason before Start, Enter reaching /api/login like the button, and focus at a 375-pixel width), while the session check is pending, and after it fails, including that Log out stays hidden and that a retry on a guest server does not ask for a sign-in that never happened. They make sessionStorage throw while cookies and localStorage still work, a partial block rather than a real browser setting. Most of the commit's size is these tests.

The second commit adds no-restricted-globals and no-restricted-properties for both storage names, with a message naming web/audio-output.js, and converts the seven references in web/history.js. They go through a small deviceStorage() helper that throws when no store can be reached, so each caller's catch still reports the failure and their results do not change. A new test checks that deleteReport still answers "failed" and "account-deleted-local-failed" with storage unreachable; it also passes on main, where the bare global throws inside the same try, and guards against a conversion that passes the null through. The storageArea() doc comment in web/audio-output.js now says that a caller that must tell a failure from an empty store has to check for null itself, which is what deviceStorage() does. The rules apply to web/**/*.js only; the review workbench is a local developer tool that does not import audio-output.js.

Validation

Each new Start test fails without its part of the change and passes with it. The deleteReport test fails when the null from storageArea() is passed through instead of deviceStorage(), and passes with it. npx eslint . passes across the repository, and reintroducing the module-scope read d5ad668 removed fails it at web/interview.js:480. A script also ran Start in Chromium with its real block-site-data setting: on a server that requires sign-in it stays on the lobby with the message above, and after allowing site data the open page still threw until it was reloaded, which is why the message asks for a reload.

Locally, interview-scroll.test.js fails the same way at the base commit on this machine. review-memory-tests needs a newer gh than the one installed, and shell-syntax flags scripts/git-post-commit.sh; this change touches neither. local-recorder.test.js failed once in a full browser lane and passed six of six runs alone, on this branch and on main; this change does not touch the files it covers.

Not checked: Firefox, the hold and retry states under a real browser block, and the real interview page under a real block on a server that admits guests. Pressing Start again rewrites the same reason, so a screen reader may not announce the repeat, though the field's description now carries it.

Close #600

Written for commit 8a49e07. Summary will update on new commits.

View guided diff

cubic-dev-ai[bot]

This comment was marked as resolved.

@sidney-Hung
sidney-Hung force-pushed the lobby-blocked-storage branch from e23d9fd to 8a49e07 Compare November 6, 2026 13:32
jserv

This comment was marked as outdated.

Comment thread web/app.js Resolved
jserv

This comment was marked as outdated.

@jserv jserv added the explained label Nov 6, 2026
@sysprog21 sysprog21 deleted a comment from sidney-Hung Nov 6, 2026
@sidney-Hung
sidney-Hung force-pushed the lobby-blocked-storage branch from 7bdf6a2 to 970da60 Compare November 6, 2026 18:12
jserv

This comment was marked as outdated.

Comment thread web/app.js Resolved
@sidney-Hung
sidney-Hung force-pushed the lobby-blocked-storage branch from 970da60 to fad6f07 Compare November 7, 2026 04:52
cubic-dev-ai[bot]

This comment was marked as resolved.

Comment thread web/app.js Resolved
Comment thread web/app.js Resolved
jserv

This comment was marked as outdated.

@sidney-Hung
sidney-Hung force-pushed the lobby-blocked-storage branch from fad6f07 to 3d27e68 Compare November 7, 2026 05:51
cubic-dev-ai[bot]

This comment was marked as resolved.

@sidney-Hung
sidney-Hung force-pushed the lobby-blocked-storage branch from 3d27e68 to 58ec809 Compare November 7, 2026 06:08
jserv

This comment was marked as outdated.

@jserv

jserv commented Nov 7, 2026

Copy link
Copy Markdown
Contributor

The interactive-review check changed in #610: the explained label no longer passes it, and this pull request is over the large-change thresholds, so its next run needs a signed record of the interactive review.

  1. Once: set up a signing key, as "Before your first large change" in CONTRIBUTING.md lists.
  2. Rebase this branch onto main; the pre-push hook that signs and sends the record is there.
  3. Run /codetrial-review on the branch and push. If the branch did not change, send the record with sh scripts/git-pre-push.sh --publish-review <fork-remote> [<branch>], then close and reopen this pull request.

Each refusal and its fix: https://github.com/sysprog21/codetrial/blob/main/docs/large-change-explanation.md#troubleshooting

@jserv jserv removed the explained label Nov 7, 2026
@sidney-Hung
sidney-Hung force-pushed the lobby-blocked-storage branch from 58ec809 to 3c991fc Compare November 7, 2026 09:10
Comment thread tests/browser/lobby-render.test.js Resolved
@sidney-Hung
sidney-Hung force-pushed the lobby-blocked-storage branch from 3c991fc to e6dafc1 Compare November 7, 2026 09:29
cubic-dev-ai[bot]

This comment was marked as resolved.

Comment thread web/app.js Resolved
Comment thread web/app.js Resolved
Comment thread web/app.js Resolved
Comment thread web/history.js Resolved
Comment thread tests/browser/account.test.js Resolved
@sidney-Hung
sidney-Hung force-pushed the lobby-blocked-storage branch from e6dafc1 to 0f7281b Compare November 7, 2026 10:52
Comment thread eslint.config.mjs Resolved
Comment thread web/app.js Resolved
@sidney-Hung
sidney-Hung force-pushed the lobby-blocked-storage branch from 0f7281b to c72a284 Compare November 7, 2026 15:23
Comment thread eslint.config.mjs Resolved
@sidney-Hung
sidney-Hung force-pushed the lobby-blocked-storage branch from c72a284 to d6acc77 Compare November 7, 2026 23:48
Comment thread eslint.config.mjs Resolved
Comment thread web/app.js Resolved
Comment thread web/history.js Resolved
@sidney-Hung
sidney-Hung force-pushed the lobby-blocked-storage branch from d6acc77 to 46f192b Compare November 8, 2026 02:33
cubic-dev-ai[bot]

This comment was marked as resolved.

@sidney-Hung
sidney-Hung force-pushed the lobby-blocked-storage branch from 46f192b to e7d2195 Compare November 8, 2026 02:52
Comment thread web/app.js Resolved
Where site data is blocked, reading the sessionStorage global throws.
The lobby's Start handler named the global in the arguments of its
storage helpers, so the throw came before any helper could answer it,
and the only trace was the browser's exception in an error line inside a
collapsed section: Start looked like it did nothing. d5ad668 gave the
interview page storageArea() for this, and the helpers already accept
the null it answers, so Practice without selected grounding now reaches
the interview, as the interview page expects. A server that requires
sign-in cannot admit a candidate whose session cookie is refused, so
when nobody is signed in the account line says why as soon as the
session answers, and Start stays on the lobby without calling
/api/login; Use GitHub and Enter in the username field still sign in
where only storage is blocked. This assumes a blocked session storage
means blocked cookies. Until the session answers Start waits, and a
failed check offers the account retry instead of navigating, because
either could hide such a server. A practice focus the candidate ticked
to share is not carried: storeSharedFocus holds an unshared focus to be
the safe outcome, and a refusal would land in the collapsed section.
Selected grounding can only travel through storage, and Strict cannot
run without it, so both stay on the lobby and that line now holds their
own reason instead of the browser's exception. The section stays
collapsed, so a Strict candidate still has to open it to read the
reason.

Change-Id: If597e1f145134c8d887f4a97db9dd356f3f18a28
Naming localStorage or sessionStorage throws where site data is blocked,
and works everywhere else, so the bug passes every local run: it stopped
the interview page from loading until d5ad668 and left the lobby's Start
doing nothing visible. The lint gate now refuses the names under web/
and points at storageArea(), which answers null instead. web/history.js
moves to it through a helper that throws when no store can be reached,
because its callers tell a failed delete from an empty store by that
throw: each catch still reports the failure, so its results do not
change. web/peer-lobby.js keeps its five references, each behind its own
disable directive with the reason beside the first: it has the same
failure, at module scope when opened from an invite, and fixing it means
choosing where an invite token lives without storage, not a rename.
Unused directives are errors, so each fix removes its own.

Close sysprog21#600

Change-Id: I799b1c07616a3689121d633cb17d9c26b07ae41f
@sidney-Hung
sidney-Hung force-pushed the lobby-blocked-storage branch from e7d2195 to 9842083 Compare November 8, 2026 03:31
@jserv
jserv merged commit fcae4d8 into sysprog21:main Nov 8, 2026
15 checks passed
@jserv

jserv commented Nov 8, 2026

Copy link
Copy Markdown
Contributor

Thank @sidney-Hung for contributing!

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.

Start does nothing visible in the lobby when site data is blocked

2 participants