Web app reconnecting to server #36
Labels
No labels
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Grandiras/Ponente#36
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
When installing the app as a web app, whenever bringing that to the foreground, it triggers the reconnecting modal. Should not happen.
🤖 On it — follow along in the action run; the reply will link the full session transcript.
📜 Live transcript — updating every ~5 minutes while the task runs.
Pushed to
claude/issue-36.What was happening
This isn't a bug in the reconnect logic — it's the framework doing exactly what it's designed to do. Backgrounding a page ends its circuit two ways over:
freezeevent, andblazor.web.jsbeacons/_blazor/disconnectonpagehide.In an installed web app, switching away is how you leave the app, so every return to the foreground is a reconnect. And the framework's reconnect UI is unconditional: it opens the dialog the moment the connection goes down, before the first attempt has even been made. Hence "rejoining the server" as a greeting.
The fix
ReconnectModal.razor.jsnow schedules the dialog instead of showing it. A reconnect that lands inside the grace window puts nothing on screen at all; one that doesn't still gets the dialog, because past that point there's a Retry button the user actually needs.failed/resume-failedbypass the delay (but still wait for the foreground), and theretry()catch surfaces a held-back dialog, so a real outage can't end up silently invisible.The grace window is longer just after the page comes back, and that's the part worth not simplifying later:
visibilityStatealone can't recognise a backgrounded page. A frozen tab runs no script, so the socket's close is usually first observed after the page unfreezes — the "show" arrives with the page already visible. What marks a resume is having been hidden recently, not being hidden now.CircuitOptionsinProgram.csnow holds disconnected circuits for 30 minutes instead of the default three. Past the retention period the server no longer has the circuit,Blazor.reconnect()is rejected, andblazor.web.jsreloads the page — so the default turns "back after lunch" into a full reload with your open card modal and scroll position gone. Cheap here for a single shared board, capped byDisconnectedCircuitMaxRetained.Also documented in
CLAUDE.mdunder a new Reconnecting section.Verified
I brought up a real stack in the CI container (installed the .NET 10 SDK and Postgres; no container engine available here, so the app ran directly on
:8090in Development).tests/e2e/reconnect.spec.js, 4 tests, all passing. Playwright can't background a page, so it drivesdocument.visibilityStatedirectly — which is what both this file and Blazor's own retry backoff read — and countsHTMLDialogElement.prototype.showModalrather than pollingopen, since the complaint is a flash and a poll can miss one.showModalcalled once, immediately). The 4th — "a connection that is really gone still shows the dialog" — passes both ways by design; it's the guard against over-suppressing.dotnet buildclean,dotnet test46/46 passing.auth-entrance,board-entrance,i18n,reconnect→ 30/30;board+mobile→ 40/41.The one failure is environmental, not a regression:
mobile.spec.js:103waits for.card .tag-chip-static, which needs a pre-existing tagged card from the long-lived dev board — my database was freshly migrated and empty.requests.spec.js/pds.spec.jsweren't run at all, since there's no PDS container in this environment.One thing I couldn't exercise end-to-end: the >30-minute background, where the circuit is genuinely gone and the page reloads. That path is unchanged from before apart from being reached far less often.
⏱ 12m 55s · 58 turns · tokens: 92.7k in / 36.1k out (+3731.2k cache-read)
📜 Full session transcript
→ Opened PR: #39