feat(webapp): a persistent nav bar and one vocabulary (phase 2 of v4.3.0) #519

Merged
claude-bot merged 2 commits from feat/phase2-nav-and-vocabulary into main 2026-09-06 03:09:05 +00:00
Contributor

Closes #378, closes #379. Phase 2 of the v4.3.0 build order (#514): the navigation spine, then one vocabulary.

#378 nav bar. NavBar is now the whole bar, rendered once from a layout route (AppLayout in App.jsx) that also carries the single AuthGuard, replacing 16 per-route copies. Left to right: the Quest Board wordmark (home), a campaign switcher (shown when the user is in more than one campaign; otherwise the campaign's name), and inside a campaign the links Sessions / Prep / Wiki / Highlights (Prep only for GMs, since /planning bounces everyone else). One wrapping flex row, so on a phone the campaign links drop to a second line. "Inside a campaign" is read from the /campaigns/:id/* route, and on session surfaces from a declaration the page makes once it knows its campaign (CampaignContext), tagged with the pathname so it cannot linger after navigation. The switcher's data is the campaign list the dashboard already fetches, requested once per app load and de-duplicated by the shared client. The wiki tab and "Open wiki" button no longer open a new tab; every remaining target="_blank" points at an external URL. Page roots dropped min-h-screen because the in-flow bar was adding a permanent scrollbar; the two wiki layouts that hard-coded a viewport calc use flex-1.

#379 vocabulary. One term per concept, from a new vocabulary.js: Prep (was GM Workbench / Session Prep Sheet), Wiki (was Campaign Story, lore), Quests (was Thread Tracker / Plot Threads / loose ends / open loops), Highlights (was Quote Board), Suggested wiki updates (was AI / Entry / Bot / lore proposals; the Celery worker writes them, not the bot), and the recording link reads "Recording →" instead of "Dashboard →". GenerationHistory rows and filter show human tool labels from TOOL_LABELS, which the Prep palette also builds from; WikiArticle renders the character timeline once. Route paths, API field names, tool_id values and code identifiers that mirror backend endpoints are unchanged, so bookmarks and Discord links keep working. The full old→new table and the deliberate survivors are in the completion comments.

vitest 523 passed (48 files, +11), eslint clean apart from the pre-existing CampaignDetail warning, vite build green. Frontend only; no backend change, no migration.

🤖 Generated with Claude Code

Closes #378, closes #379. Phase 2 of the v4.3.0 build order (#514): the navigation spine, then one vocabulary. **#378 nav bar.** `NavBar` is now the whole bar, rendered once from a layout route (`AppLayout` in `App.jsx`) that also carries the single `AuthGuard`, replacing 16 per-route copies. Left to right: the Quest Board wordmark (home), a campaign switcher (shown when the user is in more than one campaign; otherwise the campaign's name), and inside a campaign the links **Sessions / Prep / Wiki / Highlights** (Prep only for GMs, since `/planning` bounces everyone else). One wrapping flex row, so on a phone the campaign links drop to a second line. "Inside a campaign" is read from the `/campaigns/:id/*` route, and on session surfaces from a declaration the page makes once it knows its campaign (`CampaignContext`), tagged with the pathname so it cannot linger after navigation. The switcher's data is the campaign list the dashboard already fetches, requested once per app load and de-duplicated by the shared client. The wiki tab and "Open wiki" button no longer open a new tab; every remaining `target="_blank"` points at an external URL. Page roots dropped `min-h-screen` because the in-flow bar was adding a permanent scrollbar; the two wiki layouts that hard-coded a viewport calc use `flex-1`. **#379 vocabulary.** One term per concept, from a new `vocabulary.js`: **Prep** (was GM Workbench / Session Prep Sheet), **Wiki** (was Campaign Story, lore), **Quests** (was Thread Tracker / Plot Threads / loose ends / open loops), **Highlights** (was Quote Board), **Suggested wiki updates** (was AI / Entry / Bot / lore proposals; the Celery worker writes them, not the bot), and the recording link reads "Recording →" instead of "Dashboard →". `GenerationHistory` rows and filter show human tool labels from `TOOL_LABELS`, which the Prep palette also builds from; `WikiArticle` renders the character timeline once. Route paths, API field names, `tool_id` values and code identifiers that mirror backend endpoints are unchanged, so bookmarks and Discord links keep working. The full old→new table and the deliberate survivors are in the completion comments. vitest 523 passed (48 files, +11), eslint clean apart from the pre-existing `CampaignDetail` warning, `vite build` green. Frontend only; no backend change, no migration. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Navigation was a strict tree walk. The only thing every page shared was a
corner strip — name, Profile, Admin, theme, Sign out — so every cross-page
move went through a page-specific "← Campaigns" / "← Campaign" / "← Session"
breadcrumb. There was no global way to reach campaigns, and none at all to
reach a campaign's wiki or prep from a session.

NavBar becomes the whole bar rather than the right-hand cluster of one, and
App renders it once in a layout route instead of each page pasting a copy into
its own header. Left to right: the Quest Board wordmark (always home), a
campaign switcher, and — when you are inside a campaign — Sessions, Prep, Wiki
and Highlights for it. The right-hand cluster is unchanged.

Three decisions worth recording:

  * "Inside a campaign" is read from the route on the /campaigns/:id/* family
    and, on the session surfaces whose URL carries no campaign id, from a
    declaration the page makes once it has loaded its session
    (CampaignContext). A declaration is tagged with the pathname it was made
    on, so it stops counting the moment you navigate away — no clearing on
    unmount, which would flicker the links off and on between two sessions of
    the same campaign.
  * The switcher's data is the campaign list the dashboard already fetches.
    The layout outlives every page, so that is one request per app load rather
    than one per navigation, and the shared client de-dupes it against the
    dashboard's own call.
  * Prep is shown to GMs only. /campaigns/:id/planning already bounces
    everyone else back to the campaign, and a link that exists to reject you
    is worse than no link.

Labels use the phase-2 vocabulary from #379 from the start: Sessions, Prep,
Wiki, Highlights — not "Quote Board".

Two knock-on changes:

  * The wiki tab and the "Open wiki" button on the campaign page drop
    target="_blank". Both are internal routes, and the wiki tab in particular
    is styled identically to the no-op journal tab beside it, so opening a new
    window from it broke the back button for the whole wiki subtree. The
    shelf's "Draft created — Review" link goes the same way and becomes an
    in-app <Link> rather than a full page load. Every remaining _blank in the
    frontend points at an external URL (a character sheet, Google Calendar, a
    markdown link).
  * The shell now owns the viewport height, so pages drop the min-h-screen
    they each carried; without that the bar's height would be added to a full
    screen and put a permanent scrollbar on every page. The two wiki layouts
    that hard-coded min-h-[calc(100vh-61px)] use flex-1 instead, which needs
    no magic number at all.

Per-page breadcrumbs stay for now; #374/#386 reduce them.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
refactor(webapp): one vocabulary across prep, wiki, quests and highlights (#379)
All checks were successful
CI / Bot/backend version sync (pull_request) Successful in 45s
CI / Backend lint (ruff) (pull_request) Successful in 52s
CI / Docker image build (pull_request) Successful in 27s
CI / Summarisation accuracy eval harness (stub provider) (pull_request) Successful in 1m24s
CI / Frontend tests, audit, and build (pull_request) Successful in 1m55s
CI / Bot tests and audit (pull_request) Successful in 2m16s
CI / Backend migration, tests, and audit (pull_request) Successful in 9m56s
CI / Synthetic session harness (no GPU, no LLM) (pull_request) Successful in 22m43s
613a2dbbbd
The same concept answered to a different name on almost every screen, which
makes two screens about one thing look like two features. The prep workspace
alone was "planning" (route), "GM Workbench" (heading and entry button),
"Session Prep" (palette) and "Session Prep Sheet" (panel), and its output was
split between "beat notes" and a "Session Shelf".

One term per concept now, and the canonical labels live in a new
`src/vocabulary.js` so the next rename is one file:

  Prep       — was GM Workbench / Session Prep Sheet / planning-as-a-noun.
               "Beat Planner" survives as a tool name inside Prep, but what it
               writes is "prep notes" in copy (`beat_notes` stays the field).
  Wiki       — was Campaign Story / storyline / lore. The landing page's <h1>
               now matches the breadcrumb that always said Wiki.
  Quests     — was Thread Tracker / loose ends / open loops. The shelf's cold
               open lists "Open quests" (`open_loops` stays the API field).
  Highlights — was Quote Board.
  Suggested wiki updates — was AI Proposals / Entry Proposals / Bot Proposals /
               lore proposals. "Bot" was also just wrong: the Celery worker
               writes these, not the Discord bot, so the name sent GMs to the
               wrong component when they broke. Shared with #375, which will
               not redo it.

Two things that were not renames:

  * `GenerationHistory` printed raw `tool_id` in its rows and its filter, so a
    GM read "session_prep" and "loot_parcel". Both go through `TOOL_LABELS`,
    which the Prep palette also builds its labels from — one map, so a tool
    cannot be called two things in two places. Unknown ids fall back to the id
    rather than vanishing.
  * `WikiArticle` rendered a character's timeline twice: a cramped infobox row
    and the full section below the article. The section stays; the infobox row
    and the now-unused `milestones` prop are gone.

Deliberately unchanged, because they are contracts rather than words: route
paths (`/campaigns/:id/planning`, `/wiki/proposals`), API fields (`beat_notes`,
`open_loops`, `lore_*`), `tool_id` values, and the `workbench`/`Lore*` code
identifiers that mirror backend endpoints. Component filenames stay too —
renaming `CampaignPlanning.jsx` is churn with no user-visible gain.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
claude-bot scheduled this pull request to auto merge when all checks succeed 2026-09-06 02:45:29 +00:00
claude-bot deleted branch feat/phase2-nav-and-vocabulary 2026-09-06 03:09:06 +00:00
Sign in to join this conversation.
No description provided.