P5-01 — AppShell and grouped navigation
Source checkpoint: 38cf37e, branch codex/ui-redesign-phase4. Validated locally on Windows with the disposable API/SQLite and Chromium environment. The containing checkpoint is discoverable with git log -- docs/redesign/phase-5/p5-01.
This slice is the first major visible Phase 5 redesign. It implements the approved grouped shell in NAVIGATION_MIGRATION without changing the router, providers, authentication boundary, route patterns or business workflows. P5-02 utilities, P5-03 Jobs presentation, P5-05 quick preview, B07 and later redesign work are not included. F04 remains open.
Scope and result
- Dashboard remains an immediate primary destination.
- Job search owns Jobs plus Discover, Follow-ups, Board, Companies, Offers and Trash. Job email remains a distinct destination in the same group.
- Career owns Career Profile and CVs. Longest-path ownership keeps
/career/builder/:idwith CVs, never Career Profile. - Admin remains permission-filtered and retains Audit log, Users and System routes.
- Account owns Settings with Profile and Connected accounts as explicit secondary destinations; the existing account menu remains available.
- The expanded sidebar displays primary and secondary hierarchy. The persisted desktop rail uses labelled MUI tooltips and a keyboard-operable secondary menu. The mobile drawer keeps full labels and a scrollable hierarchy.
- The permanent drawer allocates navigation only the height remaining below the app bar; the upper destination group scrolls when necessary and Account links remain reachable at 900px desktop height.
- A selected secondary destination identifies itself with
aria-current="page"; its owner is markedaria-current="location". The visual selected indicator and keyboard focus remain separate. - The shell now has a visible-on-focus skip link, one named navigation landmark, one named main target and one shared
PageHeaderh1. A successful shell route selection moves focus to the new h1. Escape from the drawer still returns focus through the existing MUI modal behavior; a cancelled dirty-editor transition does not produce a false route-focus change. - Existing desktop collapse persistence remains separate from temporary mobile-open state and retains the accepted 1024px boundary from P5-00.
No new route, navigation framework, store, API, dependency, branding asset, notification destination, Documents destination or Admin dashboard was introduced. The existing quick search, notifications, account menu, language controls and contextual Add Job action remain in place for P5-02.
Changed owners
| File | Bounded responsibility |
|---|---|
job-tracker-ui/src/App.tsx |
Re-express the existing route destinations as the approved grouped navigation tree; retain callbacks, permissions and route elements. /applications/:id remains an explicit Jobs ownership alias. |
job-tracker-ui/src/layout/AppShell.tsx |
Render hierarchy in expanded/mobile modes and an accessible menu in rail mode; compute parent/child ownership; add skip/main/route-heading focus semantics; apply the visible selected indicator and bound the permanent drawer below the app bar. |
job-tracker-ui/src/layout/PageLayout.tsx |
Allow the existing shared PageHeader title to receive the shell focus ref. It does not alter section layout or consumer workflow behavior. |
job-tracker-ui/src/components/QuickCommandDialog.tsx |
Preserve the inherited command-dialog initial-focus contract after the shell transition by focusing the existing search field when the MUI transition has entered. The command catalogue and execution behavior are unchanged. |
job-tracker-ui/src/prompt.tsx, confirm.tsx, prompt.test.tsx |
Keep existing dialog keyboard entry deterministic; the prompt input is focused once after opening and the confirmation cancel action regains initial focus. No dialog result or action changed. |
job-tracker-ui/src/i18n/translations.ts |
English/Bokmål group names and skip-link label only. |
job-tracker-ui/src/sidebar-active-nav.test.ts |
Non-nested Jobs ownership, legacy application ownership and Career/CV specificity. |
job-tracker-ui/src/app-shell-navigation.test.tsx |
Group/current semantics, rail menu operation and successful route-heading focus. |
job-tracker-ui/e2e/shell-readiness.spec.ts |
Four real-App light/dark × English/Bokmål journeys across expanded, rail and phone navigation. |
job-tracker-ui/e2e/accessibility.spec.ts, redesign-preservation.spec.ts, smoke.spec.ts, csv-export.spec.ts |
Give long real-browser journeys sufficient time, target the exact created job ID, and set up API-owned data before opening the Dashboard so unrelated concurrent startup requests do not race setup writes. The separate UI login smoke case still protects sign-in. No domain assertions or routes were removed. |
PageLayout.tsx is necessary because focus must land on the actual shared page heading rather than a duplicate or wrapper; its optional ref is unused elsewhere. The dialog changes preserve the inherited initial-focus contract exposed by the loaded shell browser run. A transition callback on the prompt was removed after it disturbed the mobile Tab wrap in the production build; a one-time open effect preserves initial focus in both dev and static-export modes. No dialog outcome or domain workflow changed.
Preservation trace
| PRES → existing behavior | P5-01 change | Automated evidence | Visual/UX evidence |
|---|---|---|---|
| PRES-E07 → authenticated shell, every existing destination, collapse/drawer and active route | Same destinations grouped under readable owners; rail children remain keyboard reachable | sidebar-active-nav, app-shell-navigation, all existing route-contract and shell-readiness coverage |
Expanded/rail/mobile captures in all four theme/locale pairs |
| PRES-E08 → focus, theme, locale and responsive shell behavior | Skip/main target, one h1, current parent/child semantics and route-heading focus | Unit focus contract; real route selection to Trash/Board; P5-00 drawer entry/Escape and dirty-CV fixtures retained | Focused rail menu and mobile drawer; light/dark and EN/NB |
| PRES-E05/E06/E09 → auth/session disposal, permissions and protected shell | No provider/auth/session change; hidden admin rows remain filtered | Complete frontend/backend/account-isolation/browser baseline | Standard/admin shell states; no private data added to navigation |
| PRES-S02/S03 → notification and Settings/account entry | Existing header utilities retained; Settings owns existing Profile/Connections links | Existing notification, Settings and account-switch tests plus grouped-shell tests | Account group in expanded/mobile shell |
| PRES-A01–A03 → protected Admin audit/users/system and metadata | Same three permission-gated routes, now under a stable Admin group | Existing admin/API permission tests and complete browser baseline | Admin group in both locales/themes and narrow drawer |
| PRES-J/W/C/M route entry contracts | Presentation/ownership only; all approved entries and compatibility paths remain routed by unchanged Routes |
Existing 31-pattern route contract plus complete domain suites; direct Jobs/Career ownership cases added | Actual populated Board and Trash states; no domain forms or mutations changed |
Mapping a capability does not claim exhaustive coverage of that capability. This slice protects navigation entry and shell behavior; domain behavior remains owned by its existing tests and later gated slices.
Accessibility, responsive and visual validation
The real-App fixture uses synthetic owned data and blocks external browser traffic. It verifies:
- expanded desktop group labels, exact current parent/child semantics and fully visible Account destinations at 900px height;
- rail activation by keyboard and access to every Jobs secondary destination through a labelled MUI menu;
- mobile drawer labels, current state, route selection, close and h1 focus;
- skip-link target and named navigation/main regions;
- light/dark and English/Bokmål at 1440px and 390px, retaining P5-00 checks at 320/767/768/900/1023/1024px;
- axe WCAG A/AA scans for the loaded grouped navigation and mobile dialog.
The first browser run found a serious list-structure violation: a secondary <ul> was a direct child of its owning <ul>. The final implementation nests the secondary list inside the owning <li>; all four reruns then reported zero violations for the scoped scans. Visual review also found the permanent drawer's full-height content extending below the app bar and clipping Account links; a remaining-height flex layout and full-viewport assertion now cover that boundary. Incomplete axe nodes, manual screen-reader/touch/zoom/forced-colors limits and the inherited capture-quality caveat remain evidence limits rather than passes.
Representative evidence is stored under visual/: expanded shell, rail menu and mobile drawer for all four theme/locale pairs. Review confirmed useful density, readable hierarchy, selected/focus distinction, no new horizontal overflow and continued access at phone width. The historical post-axe missing-glyph fragments remain a capture limitation when present; no unrelated typography repair is inferred from them.
Validation baseline
Inherited P5-00 baseline: backend 916, frontend 359, browser 66 actual passes + P2-D01 expected failure, rate-limit 9, ingress 6, PDF 1, configuration 3. A separately committed prerequisite repair, 57a6bd0, made default Rules settings and workspace checklist initialization safe under concurrent first reads; its two backend regressions establish 918/918 before this shell checkpoint.
Final P5-01 candidate:
| Check | Result / change |
|---|---|
| Backend Release tests | 918/918 passed, no skips; +2 from the separate initialization repair, no P5-01 backend change |
| Frontend Jest | 365 passed / 77 suites, +6 behavioral cases (three route-ownership and three shell-navigation cases) |
| Main Chromium browser | 70 actual passes + unchanged P2-D01 expected failure, zero unexpected failures or flaky cases; +4 grouped-shell journeys over P5-00. Production static export, PLAYWRIGHT_STATIC_EXPORT=true, PLAYWRIGHT_PREBUILT_EXPORT=true, npm.cmd run test:e2e -- --reporter=list,json; result in ignored .artifacts/p5-01-browser-static-accepted.json |
| TypeScript / edited E2E TypeScript / lint | Passed (npm.cmd run typecheck; node node_modules/typescript/bin/tsc --project ../.artifacts/p5-00-e2e-tsconfig.json --noEmit; npm.cmd run lint) |
| Frontend production build | Passed |
| API Release build | Passed, zero warnings/errors |
| Rate-limit / ingress / PDF / configuration | 9 / 6 / 1 / 3 passed, respectively. Playwright security configurations use disposable local APIs; python scripts/test-ingress.py validates configuration. |
| Ancillary local checks | Supply-chain / model-evaluation harness / extension 3 / 5 / 3 passed; no live-provider claim |
| Secret / whitespace / documentation links | Tracked-secret scan passed; git diff --check and staged checks passed; 133 current/root Markdown documents, zero missing local targets (historical archive excluded) |
The backend command was dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --configuration Release --no-restore with xUnit collections/threads serialized; the API Release build used -warnaserror. The frontend production build set the local test API base URL and disabled telemetry. The complete browser run used a prebuilt production static export and the repository's disposable backend. No expected-failure marker, retry or exclusion was added. P2-D01 remains separately identified. Test-result artifacts contain disposable synthetic data only and are not committed.
Deferred issues and next gate
- P5-00-D02 remains open: browser Back from the loaded Job Workspace preserves the query but does not restore row focus. The explicit workspace Back control remains protected. This blocks P5-03/P5-08 affected navigation acceptance, not P5-01.
- Initialization gate resolved separately: React development-mode duplicate GETs exposed competing first-read inserts in Rules settings and workspace checklist seeding at the clean inherited checkpoint. Commit
57a6bd0makes both owners reload the winning row after a unique-key race and adds two backend regression cases. P5-01 itself contains no backend or database change. - SQLite write contention remains open (P4-04-O02): one complete browser run returned 500 while test setup created a company and a Free user and published a CV. The failures occurred on three different existing API writes, outside the grouped-shell code. The CSV case passed ten isolated repeats; API setup now authenticates through the existing API before rendering a Dashboard with concurrent background reads/writes. This narrows the browser fixtures to the workflows they actually assert; it does not claim server-side concurrent writes are safe or close P4-04-O02. A separate ten-repeat Free-user probe exposed two navigation aborts when clearing cookies on a live signed-in page; API setup also removes that test-only race. The failed full-run traces remain in ignored local artifacts and are not represented as passes.
- P3-O02 command failure/pending isolation remains the P5-02 acceptance gate. P5-01 retains the current catalogue and behavior.
- P4-05-O01 table localization blocks P5-03 acceptance; Table/Board parity blocks P5-04b; package proof blocks P5-08b changes; stale CV revision and rendering fidelity block P5-09–14.
- P2-D01 and F04 remain open with their original classifications. No B07, backend contract or production verification work occurred.
- Final product name/logo/brand color remain substitutable decisions. The current provisional identity is unchanged.
P5-02 may start only after this checkpoint is committed cleanly and its command failure/pending-isolation gate is characterized and protected. P5-03 must not start until P5-00-D02 and its own localization gate are resolved.