What changed, and why
The "Your runs" pages let a player revisit finished runs, but they made it hard to get back into the game. On the list (/runs), the "Play your first run" button showed only when you had no runs; once you had saved runs, the single route to /play was a faint footer link, easy to miss. On a run's detail page (/runs/:id), the footer was plain text with no link, so that page had no path to the game at all.
This PR adds one obvious, persistent action — "Start a new run" → /play — to both pages, and turns the detail page's dead footer text into a real link. Three files change: the two pages, and an end-to-end test. It's the mirror of #108 (which added a "Your runs" link to the win/lose screen), so navigation between play and runs is now obvious in both directions.
The list: an action that's always there
The heading area becomes a two-column row: the title block on the left, the "Start a new run" button on the right. The button is a NuxtLink to /play styled with the same rv-btn rv-btn--primary class the empty-state CTA uses, so it reads as the page's primary action.
The key detail is where it sits. The button lives in the heading block, above the v-if="pending" / v-else-if / v-else chain that swaps between the loading, error, empty, and list states (the first of those starts at line 35). Because it's a sibling of that chain rather than inside any branch, it renders in every state — including the populated list, which is exactly the case that used to have only the footer link.
The CTA sits in the heading row, before the state branches — so it shows regardless of run count.
pages/runs/index.vue · 108 lines
⋯ 15 lines hidden (lines 1–15)
⋯ 73 lines hidden (lines 36–108)
The detail page: no longer a dead-end
The replay page gets the same treatment. The ← Your runs back-link and a new "Start a new run" button share a row (lines 19–25), placed above the loading / not-found / replay branches — so the button is present in all three. That alone removes the dead-end.
The footer change is the second half. The old footer was a static Revisionist wordmark next to a tagline; it's now a real ← Back to play link (line 47), matching the list page's footer. The "this timeline is written" tagline stays.
Top: the CTA beside the back-link, above the state branches. Bottom: the footer is a link now, not dead text.
pages/runs/[id].vue · 86 lines
⋯ 14 lines hidden (lines 1–14)
⋯ 19 lines hidden (lines 26–44)
⋯ 36 lines hidden (lines 51–86)
Verification: a real saved run
The list is server-rendered (useFetch('/api/runs') runs during SSR), so a browser-side route mock can't seed it — the populated-list test plays a run to completion instead. It wins a run, waits for the POST /api/run-save that persists the snapshot, then loads /runs and asserts both the run list and the persistent CTA are present. That's the discriminating check: it fails if anyone ever gates the button behind the empty state again.
The spec also covers the empty list and the detail not-found state. All three pass against a production build; the repo's CI gates (nuxt typecheck, vitest run) stay green.
The populated-list test: play → save → reload /runs → the CTA is still there.