Skip to content

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

  1. Brand navy chrome stays in both modes. The README records that mapping the chrome onto --mat-sys-primary is a redesign needing rendered review. The app bar keeps --syrf-brand-ink / --syrf-on-brand-ink, expressed through mat.toolbar-overrides, mat.button-overrides and mat.icon-button-overrides with 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.
  2. 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-medium section headers, drawer surface --mat-sys-surface-container-low in both modes, and the M3 disabled treatment (on-surface at 38%, tooltip retained) instead of error red. Section-level "has active child" keeps a distinct, quieter cue from the leaf indicator.
  3. 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.
  4. Motion moves off @angular/animations in its own slice, using CSS transitions with prefers-reduced-motion support, keeping the navCollapsibleDone re-measure hook via transitionend. This matches the zoneless programme's direction (ADR-013 step 10).
  5. 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-watch for the touched specs plus the repo-wide guard specs (syrf-theme.spec.ts, hot-hook zoneless discipline, help-URL guard).
  • pnpm run check:theme-migration and pnpm run check:contrast from src/services/web.
  • bash e2e/run-local.sh --spec project-navigation-rail --iterative for slices 1 and 4; section-shell-responsive for 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.