Lane 1: the shell around every page, and the vocabulary rename (#727) #742

Merged
claude-bot merged 1 commit from build/lane1-shell into main 2026-09-22 04:36:02 +00:00
Collaborator

Lane 1's shell PR: the build plan's rollout rule made real (§2). AppLayout is now the lane 0 shell, and every signed-in page renders inside it: the app bar with where you are, the search box inside a campaign, the bell and the account menu; the live bar when a session is in progress; the Book with the Contents rail and the page; the footer; and on a phone the Contents sheet with the account menu in it. NavBar.jsx is gone. Route paths do not change. Main stays deployable: the old pages are unchanged inside the frame.

What the frame knows and where it gets it. The campaign list once per app load; the Needs you count from /api/me/attention; the version from GET /api/meta; the bell from /api/me/notifications (polled for the unread count every 60 s while the tab is visible, the list fetched when the panel opens, mark-read optimistic); search from GET /api/campaigns/{id}/search (debounced, aborted, nothing under two letters, the transcripts switch for a GM); the live bar from GET /api/campaigns/{id}/live every 30 s while visible. Every one of these is optional: a 404 or a failure means that piece is not drawn, because three of the endpoints arrive with the server PR (#727's first) and a frame is on every screen. The one exception is the notifications panel, which says it did not load once somebody has opened it.

The Contents lists only the pages that exist today (no door that leads nowhere): Home, Needs you and the campaigns on Home; Sessions, Highlights, Analytics, the wiki's front page, and for a GM Prep and Suggested wiki updates inside a campaign. The entries that arrive with later lanes (This week, Discussions, Storyline, Members & characters, Table rules & safety, Settings, the wiki's groups) are named in a comment against their lane.

Decisions a reviewer should know.

  • .qb (the base type rules) goes on each chrome piece and never around the <Outlet/>, so the pages not yet rebuilt keep their look. AppShell no longer hardcodes it; /design puts it on its own root and was render-checked in both themes.
  • The library's components draw real <a href>s and stay free of react-router, which is what lets /design render them. So the frame routes their clicks itself: one listener on the shell (useChromeLinks) hands an in-app chrome link to navigate(), and leaves alone anything inside the page's <main>, modified and middle clicks, target, download, rel="external", other origins and /auth/logout.
  • main.jsx imports index.css before App.jsx, deliberately: the bundler emits CSS in module-evaluation order, and with App.jsx first base.css landed after the component stylesheets and .qb repainted the app bar. Verified in the built CSS.
  • The frame is exactly the viewport (height: 100dvh on its own root) because the phone sheet is positioned inside it. An old page that says min-h-screen can therefore scroll by about a bar's height inside the frame; its colour, type and spacing are unchanged, and each such page is replaced by its own lane.
  • Theme is three modes now (useTheme: light, dark, auto that follows the OS live); toggle is kept for anything still calling it.
  • The session pages' crumb reads "Session" (with "The shelf" / "Table mode" after it): no session number is in hand without a request the frame must not make; lane 4 has it.
  • Bell now renders a row without an href (the contract's "No longer available" rows) as text, not a dead link.

The vocabulary rename (§5) lands in vocabulary.js with the redesign's names as constants, and on the old pages' visible text only: Session Prep → The plan, Beat Planner → Beats, Session Shelf / the board → The shelf, Campaign Journal → Session notes, journal entries → recaps, campaign story → the story / Storyline, lore → the wiki, Back to Dashboard and its variants → Home, and the two help guides. No route path, field name, tool_id or API string changed. The full table is in the commit.

Coverage rows (lane1.py): SH-1 CA-002, SH-017, IS-158, IS-198, IS-298, IS-304, IS-307 (CA-001, SH-023, SH-029, IS-303 replaced); SH-2 SH-019 (SH-018 replaced); SH-3 SH-020, SH-021, IS-157 (the entries that exist), IS-310 pending its pages; SH-4 SH-024, SH-025, SH-027, SH-037, IS-190, IS-197 (SH-026 replaced by the three-mode strip); SH-5 IS-159, IS-160, IS-162, IS-158, IS-198; SH-6 IS-304 to IS-309; SH-9 IS-298 to IS-302. SH-8 stays on the campaign pages that own it until lane 3.

Tests: 1,156 (30 new for the frame, mounting the real route table with the API mocked; 7 for the theme modes). Lint clean, build green.

🤖 Generated with Claude Code

Lane 1's shell PR: the build plan's rollout rule made real (§2). `AppLayout` is now the lane 0 shell, and every signed-in page renders inside it: the app bar with where you are, the search box inside a campaign, the bell and the account menu; the live bar when a session is in progress; the Book with the Contents rail and the page; the footer; and on a phone the Contents sheet with the account menu in it. `NavBar.jsx` is gone. Route paths do not change. Main stays deployable: the old pages are unchanged inside the frame. **What the frame knows and where it gets it.** The campaign list once per app load; the Needs you count from `/api/me/attention`; the version from `GET /api/meta`; the bell from `/api/me/notifications` (polled for the unread count every 60 s while the tab is visible, the list fetched when the panel opens, mark-read optimistic); search from `GET /api/campaigns/{id}/search` (debounced, aborted, nothing under two letters, the transcripts switch for a GM); the live bar from `GET /api/campaigns/{id}/live` every 30 s while visible. **Every one of these is optional**: a 404 or a failure means that piece is not drawn, because three of the endpoints arrive with the server PR (#727's first) and a frame is on every screen. The one exception is the notifications panel, which says it did not load once somebody has opened it. **The Contents lists only the pages that exist today** (no door that leads nowhere): Home, Needs you and the campaigns on Home; Sessions, Highlights, Analytics, the wiki's front page, and for a GM Prep and Suggested wiki updates inside a campaign. The entries that arrive with later lanes (This week, Discussions, Storyline, Members & characters, Table rules & safety, Settings, the wiki's groups) are named in a comment against their lane. **Decisions a reviewer should know.** - `.qb` (the base type rules) goes on each chrome piece and never around the `<Outlet/>`, so the pages not yet rebuilt keep their look. `AppShell` no longer hardcodes it; `/design` puts it on its own root and was render-checked in both themes. - The library's components draw real `<a href>`s and stay free of react-router, which is what lets `/design` render them. So the frame routes their clicks itself: one listener on the shell (`useChromeLinks`) hands an in-app chrome link to `navigate()`, and leaves alone anything inside the page's `<main>`, modified and middle clicks, `target`, `download`, `rel="external"`, other origins and `/auth/logout`. - `main.jsx` imports `index.css` before `App.jsx`, deliberately: the bundler emits CSS in module-evaluation order, and with `App.jsx` first `base.css` landed after the component stylesheets and `.qb` repainted the app bar. Verified in the built CSS. - The frame is exactly the viewport (`height: 100dvh` on its own root) because the phone sheet is positioned inside it. An old page that says `min-h-screen` can therefore scroll by about a bar's height inside the frame; its colour, type and spacing are unchanged, and each such page is replaced by its own lane. - Theme is three modes now (`useTheme`: light, dark, auto that follows the OS live); `toggle` is kept for anything still calling it. - The session pages' crumb reads "Session" (with "The shelf" / "Table mode" after it): no session number is in hand without a request the frame must not make; lane 4 has it. - `Bell` now renders a row without an `href` (the contract's "No longer available" rows) as text, not a dead link. **The vocabulary rename (§5)** lands in `vocabulary.js` with the redesign's names as constants, and on the old pages' visible text only: Session Prep → The plan, Beat Planner → Beats, Session Shelf / the board → The shelf, Campaign Journal → Session notes, journal entries → recaps, campaign story → the story / Storyline, lore → the wiki, Back to Dashboard and its variants → Home, and the two help guides. No route path, field name, `tool_id` or API string changed. The full table is in the commit. Coverage rows (`lane1.py`): SH-1 CA-002, SH-017, IS-158, IS-198, IS-298, IS-304, IS-307 (CA-001, SH-023, SH-029, IS-303 replaced); SH-2 SH-019 (SH-018 replaced); SH-3 SH-020, SH-021, IS-157 (the entries that exist), IS-310 pending its pages; SH-4 SH-024, SH-025, SH-027, SH-037, IS-190, IS-197 (SH-026 replaced by the three-mode strip); SH-5 IS-159, IS-160, IS-162, IS-158, IS-198; SH-6 IS-304 to IS-309; SH-9 IS-298 to IS-302. SH-8 stays on the campaign pages that own it until lane 3. Tests: 1,156 (30 new for the frame, mounting the real route table with the API mocked; 7 for the theme modes). Lint clean, build green. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat(frontend): lane 1, the shell around every page (#727)
All checks were successful
CI / Summarisation accuracy eval harness (stub provider) (pull_request) Successful in 1m1s
CI / Bot/backend version sync (pull_request) Successful in 42s
CI / Docker image build (pull_request) Successful in 43s
CI / Backend lint (ruff) (pull_request) Successful in 48s
CI / Bot tests and audit (pull_request) Successful in 1m58s
CI / Feedback site tests (pull_request) Successful in 1m3s
CI / Frontend tests, audit, and build (pull_request) Successful in 2m54s
CI / Backend migration, tests, and audit (pull_request) Successful in 8m49s
CI / Synthetic session harness (no GPU, no LLM) (pull_request) Successful in 17m27s
993b4013bd
Every signed-in route renders inside the redesign's frame now. `App.jsx`'s
`AppLayout` (a nav bar above an `<Outlet/>`) is replaced by
`src/shell/AppFrame.jsx`, built out of lane 0's library: the app bar with
where you are, the campaign search box, the bell and the identity disc; the
Contents as a rail and, on a phone, as the sheet that also carries the account
menu; the live-session bar; and the footer. `NavBar` is deleted. Route paths
are unchanged — this is the build plan's rollout rule (§2), so from here on
nobody sees an old page without the new navigation, and no page is missing
while its lane is being built.

The Contents lists **only the pages that exist**: Sessions, Highlights,
Analytics, the wiki's front page, and Prep and Suggested wiki updates for a
GM. The rest are named in comments against the lane that builds them (lane 3's
This week, Storyline, Members, Settings; lane 7's Discussions; lane 8's wiki
groups) rather than drawn as doors that lead nowhere.

The old pages are unchanged inside it. The base type rules are scoped to `.qb`
so a page keeps its own look until its lane replaces it, and every page now
renders inside `AppShell` — so `AppShell` drops the class and the frame puts it
on each chrome piece instead, never on the element the Outlet is in. The
Outlet's wrapper is a plain div carrying the Tailwind colours the layout div it
replaced used to carry. `main.jsx` imports `index.css` first so the design
system's base lands before the components that are written to override it;
imported after `App.jsx`, as it was, base.css won every tie and `.qb` on the
app bar would have repainted it in paper.

Everything the frame asks the server for is optional — the campaign list, the
attention count, and four endpoints that land in their own PR
(`/api/me/notifications`, `/api/campaigns/{id}/live`,
`/api/campaigns/{id}/search`, `/api/meta`). A failure draws nothing rather than
a banner on every screen; only the notifications panel says it did not load,
because by then somebody has asked.

Also here:

- **Three themes.** `useTheme` is light/dark/**auto**, where auto removes the
  stored choice and follows `prefers-color-scheme` live. `toggle` still works.
- **The vocabulary** (brief §5) in `vocabulary.js`, and applied: Dashboard →
  Home, Session Shelf → The shelf, Beat Planner → Beats, the `session_prep`
  tool → The plan, Session Prep (the place) → Prep, Campaign Journal → Session
  notes, journal entry → Recap, Campaign storyline → Storyline, "campaign
  lore" → the campaign wiki, "the board" → the shelf.
- **Bell** draws a row with no `href` as text rather than a dead anchor, for the
  `unavailable` rows the contract allows.
- `jsx-a11y` runs at error on `src/shell/` as it does on `src/ui/`.

Verified in Docker: eslint clean (no warnings in the new files either),
1,156 tests green, `vite build` green, and `/design` render-checked in headless
Edge in both themes after the `AppShell` change.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Sign in to join this conversation.
No description provided.