Repository navigation
fix(editor): say "Session expired" and link back in when the server refuses the socket (QA sweep NET-06) - #31
Merged
Conversation
…efuses the socket (QA sweep NET-06) A browser is never told the status of a refused WebSocket handshake, so once the 24-hour token ran out the editor retried under "Reconnecting..." forever while the next navigation went to a bare /login. - usePresence gains status "expired": a presence socket that closes without having opened asks GET /api/auth/me, and a 401 reads as expired. A 200, an unreachable API or isOffline() leaves Connecting / Reconnecting. Retries carry on, so logging in from another tab reconnects this one. - MobileSyncPill and FooterBar show that status as SignInAgainLink, an anchor to /login?returnTo=<this page with ?section=/?item=>. The padding makes the tap target at least 44px tall. - The /login loader no longer bounces a token past its exp to returnTo (isTokenExpired is now exported); that bounce sent the destination's loader straight back to a /login with returnTo dropped. - Copy says "log in" to match the Log In button. Known ceilings are in the docs/develop/fork-log.md row: a token invalidated by a password change but not yet past exp is still bounced; under saas /login redirects to the portal and drops returnTo; on an origin where the browser refuses the __Host- cookie the readout says expired. Chrome: /inspections/<id>/edit — at 1024px with the session ended in a second tab and the server restarted, the footer went red "Session expired: log in to continue" as an underlined link to /login?returnTo=%2Finspections%2F<id>%2Fedit; /api/auth/me answered 401 on each retry Chrome: /login?returnTo=%2Finspections%2F<id>%2Fedit — following the footer link and logging in landed back on the editor with the footer "Connected" and the note typed while expired present in the Notes box Chrome: /inspections/<id>/edit?section=structure&item=foundation — at 375px the app-bar pill went red "Session expired" as a link whose returnTo carried section and item, logging in returned to the same Foundation item, and the pill read Synced again Chrome: /inspections/<id>/edit?section=structure&item=foundation — at 375px the title width fell from 121px (Synced) to 73px (Session expired) so "Foundation" shows as "Founda...", with no horizontal scroll; the pill link measured 38px tall before and 46px after the padding change, the desktop footer link 48px Not seen in a browser: light theme, a real phone, and the case where __session still holds a JWT past its exp (covered by the login-saas-bounce spec only). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
QA sweep finding NET-06 (Minor). With the session cookie cleared, the editor kept looking normal and "Synced", and the next navigation was a silent 302 to
/loginwith no explanation and no way back. The presence socket is now the signal: when it closes without ever opening and the browser is online, the editor asksGET /api/auth/me; a 401 means the session expired, and the mobile pill and desktop footer say "Session expired" and become a link to/login?returnTo=<this page>.A browser is never told the status of a refused WebSocket handshake (a 401 and a dead network both arrive as
closewith noopen), and the socket's own URL answers 426 before it checks auth, so the probe has to be a separate request.What changes
app/hooks/usePresence.ts:PresenceStatusgains"expired". A 401 from/api/auth/mesets it, anything else clears it, a rejected fetch is ignored,openclears it. Retries continue, so a login in another tab reconnects.app/components/editor/SignInAgainLink.tsx(new), used byMobileSyncPill.tsx(short label) andFooterBar.tsx(full sentence).app/routes/login.tsx+app/lib/session.server.ts:isTokenExpiredis exported and the/loginloader no longer bounces a token that is past itsexp(it used to send it toreturnTo, whose loader ended the session and dropped it).editor_session_expiredandeditor_session_expired_shortinenandes-419.docs/develop/fork-log.md: one row (fork deviation, no upstream fix found), with three known ceilings.Type of change
Test plan
app/hooks/usePresence.test.ts(new), an expired case inMobileSyncPill.test.tsx, a NET-06 case inlogin-saas-bounce.test.ts.lint:gates27 of 27;lint:middleware-budgetPASS; build exit 0; pre-commit hook passed.type-check:appwas exit 0 before the last edit (one class string and a comment inSignInAgainLink.tsx) and was not re-run after it; this PR's CI covers it.returnTo, logging in lands back on the same item with the typed note intact.__sessionholding a JWT past itsexpthrough the/loginloader (unit spec only).For the reviewer
🤖 Generated with Claude Code