What changed, and why
On the final win/lose screen, the Chronicle epilogue looked different while it was being written than once it finished. Streaming, it was one raw paragraph capped at half the column with the title inline as plain text; settled, it was a full-width titled article with separated serif paragraphs. So the moment the telling landed it jumped in both width (~50% to 100%) and formatting.
The fix is a reuse, not a redesign: render the streaming state through the exact markup the settled epilogue already uses, sourced from the live buffer and parsed by the same helper the in-game Chronicle's live view relies on. The account now wears its final shape as it streams, so the settle is a quiet cross-fade with no reflow. Blast radius is one component (plus one new test); the settled rendering, the store, and the parser are untouched.
The streaming branch, reshaped to match the settled one
Both tellings live in the same <Transition mode="out-in">. The streaming branch (shown first) now emits the same structure as the settled branch below it: a titled <h4> with the identical classes, then a v-for of <p> paragraphs with the identical classes — no width cap. The only streaming-specific touch is the ink cursor at the writing tip, and aria-busy while it composes.
Streaming: a titled heading + separated paragraphs off liveEpilogue, ink cursor at the tip.
components/EndGameScreen.vue · 622 lines
⋯ 117 lines hidden (lines 1–117)
⋯ 469 lines hidden (lines 154–622)
Settled: the same <h4> and <p> classes. The two now render identically.
components/EndGameScreen.vue · 622 lines
⋯ 161 lines hidden (lines 1–161)
⋯ 442 lines hidden (lines 181–622)
Parsing the live buffer the same way it settles
liveEpilogue parses the raw stream with parseLiveChronicle into the settled {title, paragraphs} shape the template iterates. It sits right beside streamingNow, the guard that already decided this buffer is the live one (its length matches the current ledger).
streamingNow gates the branch; liveEpilogue gives it the parsed shape.
components/EndGameScreen.vue · 622 lines
⋯ 463 lines hidden (lines 1–463)
⋯ 146 lines hidden (lines 477–622)
The one new import — the shared client-safe parser.
components/EndGameScreen.vue · 622 lines
⋯ 329 lines hidden (lines 1–329)
⋯ 292 lines hidden (lines 331–622)
The test, and how it was verified
A new case drives the streaming state and asserts the fixed shape: a titled heading, its text present, the body split into its own paragraph elements, and the old narrow .end-chronicle-live view absent. It fails on the pre-fix markup (which had no title element and one blob paragraph), so it discriminates the fix.
Streaming now renders the settled shape — titled heading, separated paragraphs, no raw blob.