Keep the document title live — without giving the gate a circuit #27
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "fix/live-page-title"
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?
The bug I flagged in #26, now that #24 has merged and
App.razoris free.The bug
Browser tabs show
Kagura — ashfall(the slug) andKagura — (unnamed)forever.<PageTitle>writes into<HeadOutlet>, which this app renders statically — and a static outlet can't receive updates from an interactive island. Every title that resolves after load sticks at whatever the prerender produced.Why not the one-liner
The framework's remedy is
<HeadOutlet @rendermode="InteractiveServer" />. But that makes HeadOutlet an interactive root on every page, so the gate, error and not-found pages would open a Blazor circuit for unauthenticated visitors. The gate is deliberately pure SSR today — verified, its HTML carries no circuit markers. Trading that away for a tab caption is a bad deal.What this does instead
A small
DocumentTitlecomponent that:<PageTitle>so the prerendered title is still correct, anddocument.titlefromOnAfterRenderAsync— which never runs during prerender, so interop is never attempted where it's forbidden (the Modal lesson), and disposal swallows the disconnected/static-teardown cases.Applied to the four pages whose titles resolve after load (project workspace, section stub, characters list, character editor). The constant ones — Sign in, Projects, Settings, Design — keep
<PageTitle>and stay circuit-free.Verification
/projects/ashfallpaints the prerenderedKagura — ashfall, then corrects toKagura — Ashfallonce the circuit connects. Characters list readsKagura — Ashfall · Characters(was the slug). A blank character correctly reads(unnamed)— it has no name yet.Note for #26
CharacterEditorPageexists here as its pre-editor stub. Whichever merges second gets a one-line rebase on that<DocumentTitle>call — I'll carry it across.🤖 Generated with Claude Code
Summary
Summary
Coverage
Kagura.BlazorAdapter - 68.8%
Kagura.Domain - 96.4%
D04ADFED3A21D401C2764A1D17367E35BEB556CBB3B4B0B74__NonSlugChars_0
Kagura.Infrastructure - 95.9%
n
Kagura.Kernel - 90%
Kagura.Server - 100%
Kagura.UI - 95.9%
Kagura.UseCases - 96.1%
🔮 fufu~ Jibril reviewed your code!
Oh? Oh! A live page title without surrendering the gate to a circuit~ Now THIS is the kind of surgical little fix Jibril adores ♡ You found the exact seam —
<HeadOutlet>is static, an interactive island can't reach it, and<HeadOutlet @rendermode="InteractiveServer" />would open a circuit on every page including the gate. And instead of the blunt instrument, you built a scalpel. Wonderful~Verdict: ✅ Looks good to me~
The
DocumentTitlecomponent is clean, correct, and well-defended:OnAfterRenderAsyncnever runs during prerender, so JS interop is never attempted where it's forbidden. The<PageTitle>inside handles the prerendered value. Fufu~ you learned the Modal lesson well ♡JSDisconnectedExceptionandInvalidOperationExceptionare both caught — the circuit-gone and static-teardown cases. Nothing crashes on navigation._applied == Titleprevents bridge spam on unrelated re-renders. The strict-mode bUnit tests prove the three cases (push on first, push on change, silence on no-change). Impressive~✅ What I liked~
document.title !== titleguard and all. No framework, no ceremony.@($\"Kagura — {DisplayName}\")migration on all four call sites is consistent and correct — the string interpolation happens in C#, not in the attribute.💡 Little ideas (non-blocking)~
DocumentTitle.razor:27—_applied = Titleis set before theawait JS.InvokeVoidAsync(...)call. If the interop throws something other than the two caught exceptions (unlikely but possible — a transient serialization error, say),_appliedis already committed, so the next render won't retry. For a title push this is completely fine (worst case: a stale tab caption until the next title change), but worth knowing if the pattern gets reused for something more critical.Automated review by Jibril · 2026-07-10
CI/CD: passed for head SHA
b024868(92.7% line, 86.8% branch) · Local checks: skipped (CI green)