Application Navigation Material 3 Migration Plan¶
This plan executes the "application and project navigation visual states" item of the technical plan Wave 3. It is the authorized navigation owner that the ownership ledger deferred to "PR #2533 or its successor": PR #2533 is stale and conflicting and is not merged or rebased; its intent (signal-based shell state) is reauthored here on current main.
The migration is presentation and interaction-state work. Routes, permissions, route guards, feature-flag gates, the project navigation section model and the 255px/60px rail layout contract are unchanged. Form-validation indicator semantics are out of scope (separate design-approval gate).
Inventory (audited 2026-09-15 at origin/main@f2bb1aa15)¶
| Surface | Files | State | Owner / conflicts |
|---|---|---|---|
| Top app bar (wide row, compact row, compact context row, breadcrumb, account/help/admin/theme menus) | core/nav/nav.component.{html,scss,ts,spec.ts}; chrome block in global-styles/styles.scss (app-nav mat-toolbar icon/button pins); dark toolbar override in syrf-theme.scss |
M2 mat-toolbar color="primary" on brand navy; hard-coded white/#fff/black; @angular/animations (collapseInOut, appearDisappear); FlexLayout (fxLayout, fxHide/fxShow.gt-md); Eager change detection; @Input; observables via | async; theme menu duplicated three times, admin menu twice |
PR #3468 (open, active) gates the theme toggle in nav.component.{html,ts,spec.ts}; this programme's app-bar slice lands after it |
| Project navigation drawer and rail | project/project.component.{html,scss,ts}, project/project-nav/* incl. _project-nav.component-theme.scss, project/project.animations.ts, core/services/layout/*, --project-nav-width in styles.scss, .navigationList/--project-drawer-surface dark overrides in syrf-theme.scss |
Stock mat-sidenav (ADR-013) with mat-nav-list; active rows via --syrf-brand-* washes; #f2f2f2 light drawer fallback; disabled rows painted --mat-sys-error red; section headers 12px; @angular/animations (navCollapsible, slideInOut, rotate90, rotate180); FlexLayout; ComponentStore + observables; rail expands on hover only (no keyboard expansion); E2E project-navigation-rail.spec.ts pins the 60px footprint and content-margin contract |
No active writer. Study Management PRs touched the section model only (merged). |
| Section shell (About, My Account, Admin Console) | shared/side-nav/* |
Already on --mat-sys-* roles with the adaptive wide-rail / sticky compact-selector hierarchy (merged 2026-08-31, bd3f11f46); E2E section-shell-responsive.spec.ts |
Supersedes stale PR #2992, which should be closed rather than merged. Remaining: active-item role differs from the global .mat-mdc-menu-panel a.active-menu-item rule (secondary vs primary container); literal 12px/72px sizes |
| Page shell | shared/page-shell/* |
M3 typescale and spacing tokens (Study Management) | Done; no change planned |
| Header banners and chip | core/components/environment-banner, environment-chip, impersonation-banner |
Inline hard-coded gradients and white; impersonation banner is the Wave 2 "impersonation colours" slice |
Environment identity colours become --syrf-env-* domain roles; impersonation colours follow the Wave 2 boundary |
| Footer | app.component.{html,scss} |
Literal #1976d2, #61a0eb, #9fcaef, black link colours |
No conflicts |
Skip link, loading bar, .nav-container sticky shell |
app.component.* |
Already token-based except footer links | No change |
| Review navigation, Dockview tabs, AF2 toolbars, Study/Stage end drawers | stage/stage-review/**, studies/study-table, stage/stage-studies |
Feature surfaces, not navigation chrome | Out of scope: owned by the stage-review / AF2 programme and the study-management owner |
Design decisions¶
- Brand navy chrome stays in both modes. The README records that mapping the chrome onto
--mat-sys-primaryis a redesign needing rendered review. The app bar keeps--syrf-brand-ink/--syrf-on-brand-ink, expressed throughmat.toolbar-overrides,mat.button-overridesandmat.icon-button-overrideswith M3 state layers instead of literal white. A surface-coloured app bar (as in the annotation-form redesign prototype) is a separate, user-approved design decision and is not made here. - Project navigation adopts the M3 navigation-drawer idiom on the existing list: a rounded
active indicator (
--mat-sys-primary-container/--mat-sys-on-primary-container; the generated secondary palette is a neutral grey, so a secondary-container pill read as disabled in both modes), hover and focus state layers from the list tokens, uppercase--mat-sys-label-mediumsection headers, drawer surface--mat-sys-surface-container-lowin both modes, and the M3 disabled treatment (on-surfaceat 38%, tooltip retained) instead of error red. Section-level "has active child" keeps a distinct, quieter cue from the leaf indicator. - Layout contracts are frozen. The 255px/60px footprint, hover overlay and content-margin
behaviour proven by the rail E2E spec do not move. Keyboard users gain rail expansion on
focus-within, which is additive. - Motion moves off
@angular/animationsin its own slice, using CSS transitions withprefers-reduced-motionsupport, keeping thenavCollapsibleDonere-measure hook viatransitionend. This matches the zoneless programme's direction (ADR-013 step 10). - Modernise as touched. Components edited by a slice move to signal inputs/outputs,
selectSignal,OnPush, and drop FlexLayout in favour of CSS, scoped to the files the slice already owns.
Slices¶
| # | Slice | Files | Acceptance |
|---|---|---|---|
| 1 | Project navigation drawer M3 states (this PR, #3489) | project/project-nav/*, project/project.component.scss, syrf-theme.scss (drawer/list roles only), syrf-theme.spec.ts, this doc |
Unit specs green; check:theme-migration + check:contrast green; rail E2E green; light/dark and wide/compact evidence attached |
| 2 | Top app bar chrome, breadcrumb and menus (after #3468 merges) | core/nav/*, styles.scss chrome block, syrf-theme.scss toolbar override, app.component.* footer |
Nav spec green; no literal colours in nav chrome; menus deduplicated; light/dark evidence at 320/600/905/1240/1440 |
| 3 | Header banners and environment chip roles | core/components/environment-banner, environment-chip; impersonation banner per Wave 2 boundary |
--syrf-env-* roles emitted in both modes and contrast-tested |
| 4 | Rail motion without @angular/animations |
project/project.animations.ts, project-nav.component.*, project.component.* |
Rail E2E green; reduced-motion honoured; re-measure still fires |
| 5 | Section shell consistency and ledger cleanup | shared/side-nav/*, global active-menu rule, technical-plan ownership rows for #2533/#2992 |
Section-shell E2E green; one active-item role across rail, selector and menus |
Each slice is a ready-for-review PR with focused scope, its own tests and rendered evidence. Cloud review is requested once the applicable checks pass.
Validation¶
pnpm exec ng test --no-watchfor the touched specs plus the repo-wide guard specs (syrf-theme.spec.ts, hot-hook zoneless discipline, help-URL guard).pnpm run check:theme-migrationandpnpm run check:contrastfromsrc/services/web.bash e2e/run-local.sh --spec project-navigation-rail --iterativefor slices 1 and 4;section-shell-responsivefor slice 5.- Browser evidence: light and dark, wide rail (1440px), rail collapsed and hover/focus expanded, compact overlay (1024px) and narrow (390px); hover, focus-visible, selected, disabled states.