Skip to content

M4.S7.x: Counter circuit persists across enhanced-nav transitions (warmup-once-per-session) #116

Description

@miadey

Goal

When a user navigates between Blazor pages via enhanced navigation (no full reload), the existing SignalR circuit should stay attached so subsequent visits to interactive pages (e.g. `/counter`) don't re-pay the ~3.3 s handshake warmup.

Today

BlazorVanilla now ships enhanced navigation via client-side JS (commit `50515f6`). Page transitions between `/`, `/weather` are instant (5–19 ms in browser per agent verification).

But `/counter` specifically falls back to a full reload (commit `f760127`) because splicing the new HTML via `curMain.innerHTML = newMain.innerHTML` leaves the Blazor server-marker comment in the DOM but blazor.web.js doesn't re-run its marker-discovery scan over the new content. Result without the special-case: count never increments after click, button has no event-handler attribute attached (verified in browser).

What does NOT trigger marker rescan

Tested 2026-05-18:

  • `document.dispatchEvent(new CustomEvent('enhancedload', ...))` — no effect
  • `document.dispatchEvent(new CustomEvent('enhancedNavigationCompleted', ...))` — no effect
  • `window.Blazor._internal.attachRootComponents()` — function not exposed on this build
  • `MutationObserver` watching for marker comments — fires but no handler picks it up

What's known about blazor.web.js internals

`grep` on `aot/samples/BlazorVanilla/wwwroot/_framework/blazor.web.js` (the embedded build) shows the framework has:

  • `attachRootComponentToElement`
  • `attachRootComponentToLogicalElement`
  • `enhancedNavigationStarted` / `enhancedNavigationCompleted` (events the framework dispatches FROM, not necessarily listens to externally)
  • `onDocumentUpdate`
  • `localStorage` + `sessionStorage` + `reconnect` / `Reconnect` (so there's reconnect infrastructure too)

The minified source makes parameter shapes opaque. The 'right' API call almost certainly exists but pinning it down needs either:

  • Source-map-equipped build of blazor.web.js
  • IDE debugging on a working .NET 8/9/10 Blazor Server app
  • Reading the upstream framework's TypeScript source at `src/Components/Web.JS/` in dotnet/aspnetcore

Proposed approaches

A. Marker rescan after splice — find the correct `Blazor._internal.*` API to manually trigger discovery, OR replicate what blazor.web.js's own enhanced nav does internally (it must handle this case for the framework's own ``-driven enhanced nav). One reasonable target: simulate a `popstate` event after splice — that's the trigger blazor.web.js uses for its own back/forward enhanced nav. Try and measure.

B. Persistent circuit reconnect — even with full reload between pages, if blazor.web.js can be told to use a stored connectionId from a previous session (via sessionStorage or a query string), the server's existing circuit (still alive in heap) is reused. No handshake. Requires hooking `Blazor.start({ ... })` config or framework-side persistence.

C. Custom -equivalent — implement our own SPA navigator that keeps a single `` always mounted (display:none unless on /counter route). The circuit attaches once on first page load. Show/hide via CSS. Bypasses blazor.web.js's internal marker scan entirely.

Goal scorecard (per `/goal` user goal: pages <1s, warmup once, clicks <2s)

Goal Status
All pages open in < 1 s ✅ PASS (50–200 ms curl, 5–19 ms enhanced nav)
Warmup once at first load ⚠️ PARTIAL — applies to non-Counter transitions; Counter visits still pay 3.3 s warmup each
Click round-trip < 2 s ✅ PASS (~1.7 s slow click)

This issue tracks closing the Goal 2 gap. Realistic estimate: 1–2 days of framework-source-diving + testing.

File evidence

  • Enhanced nav implementation: `aot/samples/BlazorVanilla/Components/App.razor` (lines ~120-210, inline `<script>`)
  • Marker-bearing endpoint: `aot/samples/BlazorVanilla/Components/Pages/Counter.razor` + `aot/Wasp.AspNetCore.Blazor.Server/src/WaspMarkers.cs`
  • Pre-render registration: `aot/samples/BlazorVanilla/Program.cs:73`
  • Browser verification (2026-05-18, agents a12024a/a86a094): counter clicks unwired after splice; full reload restores them

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions