Context
PR #157 wired Breadcrumbs.tsx into the six screens that specs/behaviors/app-shell.md names. Each screen renders <Breadcrumbs> itself as a fragment sibling above its content container, because that was the minimal change that made the spec's trail table true.
Problem
app-shell.md describes the breadcrumb row as part of the shell ("an optional row below the header ... a screen opts in by declaring a breadcrumbs trail"), and the shell's skip link targets <main>. With the crumbs rendered inside each screen, "skip to main content" lands on the breadcrumb nav rather than past it, and every screen repeats the same import + placement.
Proposal
Render breadcrumbs once from AppShell, sourced from route handles via useMatches() (React Router v7). Each opted-in route declares its trail on its handle (with loader/query data supplying the dynamic segment), and the six per-screen <Breadcrumbs> calls go away. The row then sits between the header and <main>, so the skip link clears it.
Scope: apps/web/src/components/AppShell.tsx, the router config, and the six screens (ProjectDetail, ProjectEdit, PersonDetail, TagDetail, Account, plus the create variant). Tests in ProjectDetail.test.tsx / PersonDetail.test.tsx assert the trails and should keep passing.
Deferred from plans/a11y-mechanical.md (PR #157).
Context
PR #157 wired
Breadcrumbs.tsxinto the six screens thatspecs/behaviors/app-shell.mdnames. Each screen renders<Breadcrumbs>itself as a fragment sibling above its content container, because that was the minimal change that made the spec's trail table true.Problem
app-shell.mddescribes the breadcrumb row as part of the shell ("an optional row below the header ... a screen opts in by declaring abreadcrumbstrail"), and the shell's skip link targets<main>. With the crumbs rendered inside each screen, "skip to main content" lands on the breadcrumb nav rather than past it, and every screen repeats the same import + placement.Proposal
Render breadcrumbs once from
AppShell, sourced from routehandles viauseMatches()(React Router v7). Each opted-in route declares its trail on itshandle(with loader/query data supplying the dynamic segment), and the six per-screen<Breadcrumbs>calls go away. The row then sits between the header and<main>, so the skip link clears it.Scope:
apps/web/src/components/AppShell.tsx, the router config, and the six screens (ProjectDetail,ProjectEdit,PersonDetail,TagDetail,Account, plus the create variant). Tests inProjectDetail.test.tsx/PersonDetail.test.tsxassert the trails and should keep passing.Deferred from
plans/a11y-mechanical.md(PR #157).