Skip to content

Material 3 Theming Consolidation

Source: distilled from .gsd/milestones/M001/* + .gsd/milestones/M002/* + .gsd/DECISIONS.md. Preserved here in non-GSD form so the strategic narrative, completion evidence, and decision register remain accessible independent of the separate .gsd/ cleanup effort.

Note on lifecycle: this planning doc (and the material-3-theming/ archive it points to) is a temporary artefact capturing the execution of completed milestones. Once the outstanding follow-ups below are tracked elsewhere (feature docs, ADRs, or issues), the archive can be retired.

Companion docs:

  • material-3-migration-plan.md — forward-looking feature plan (approved 2026-02-06)
  • material-3-theming/README.md — full planning archive (milestone narratives, slice-level execution receipts, requirements register R001–R018, project description)
  • This doc — the synthesized roadmap, research, completion record, and decision register for the consolidation work shipped via PR #2322

Scope covered here

Two milestones executed back-to-back on the same branch (claude/standardize-syrf-theme-ZUBP7, PR #2322):

Milestone Focus Slices
M001 — Design System Consolidation M2→M3 API migration, Bootstrap removal, design tokens, Stylelint enforcement, Storybook + dark mode + a11y 6
M002 — M3 System Variable Integration Replace custom tokens with --mat-sys-*; eliminate the dual-color system M001 created; toolbar/body/button fixes 3

30 commits total across 351 files; net −15,236 lines (19,077 insertions / 34,313 deletions).


M001 — Design System Consolidation

Status

Completed 2026-03-14. 6 of 6 slices done.

Key outcomes:

  • M3 theme live with HCT palettes from SyRF primary (#203457)
  • Zero M2 API references in any SCSS file
  • Zero Bootstrap class usage (shims and glyphicons removed)
  • Zero hardcoded hex colors in component SCSS (64 files migrated)
  • Near-zero rgba() in component SCSS (1 legitimate Sass fn remains)
  • Stylelint enforcedcolor-no-hex + color-named rules, zero violations
  • 45 Playwright tests passing: 30 layout integrity (light + dark), 5 visual regression, 10 a11y
  • Dark mode toggle in navigation with localStorage persistence
  • Design guidelines published at docs/design/design-system.md
  • ~60 design tokens covering colors, borders, surfaces, text, spacing, typography, elevation
  • Production build succeeds

Why this milestone

SyRF's Angular frontend had accumulated design debt across 205 components: a mix of Angular Material M2 theming, legacy Bootstrap CSS shims, hardcoded hex/rgba colors, inline styles, and 10 component theme mixin files using deprecated M2 APIs. This milestone replaced all of it with a unified M3-based theme using SCSS design tokens, enforced by Stylelint in CI, validated by Playwright layout integrity tests and axe-core accessibility audits, and documented with Storybook.

The current styling approach was fragile and inconsistent. Two styling systems (Material + Bootstrap), 62 files with hardcoded colors, and no enforcement meant every PR risked introducing more drift. Angular Material's M3 API is the current standard; M2 APIs will eventually be removed. Consolidating now prevents compounding debt and establishes guardrails that keep the system clean going forward.

Pre-work codebase audit (2026-03-13)

Signal Count Notes
Components (total) 205
SCSS files in app 206
M2 theming API refs 103 mat.m2-* function calls
Files with hardcoded hex colors 62 In src/app/ SCSS files
Files with hardcoded rgba() 24 In src/app/ SCSS files
Templates with Bootstrap classes 22 btn-, panel-body, panel-heading, progress-bar
Legacy Bootstrap SCSS shims 3 legacy-bootstrap/{buttons,panel,progress-bars}.scss
Templates with inline style= 33 Some may be legitimate dynamic bindings
Component theme mixin files 10 Use mat.m2-get-color-from-palette()
OnPush components 25/205 (12%) Deferred — not in scope
Font Awesome refs 0 Already cleaned
Dark theme support CSS class toggle .global-dark-theme in syrf-theme.scss, no UI control
Global style entry points 3 styles.scss, syrf-theme.scss, syrf-load-logo.scss

Research findings (2026-03-13)

M2 → M3 migration

  1. No automated migration path. Angular Material does not provide an automated M2→M3 migrator because design decisions are subjective. Manual migration is required.
  2. Palette generation. The ng generate @angular/material:m3-theme schematic generates M3 HCT-based tonal palettes from a single seed color. Run with --primaryColor=#203457 to get SyRF-branded M3 palettes.
  3. M3 emits CSS custom properties. mat.theme() emits --mat-sys-* custom properties, which aligns with the design token enforcement strategy. This is the foundation for Stylelint enforcement.
  4. Component visual changes. M3 components are generally larger/more spacious than M2. Density settings accept integers from 0 to −5 (each step reduces sizes by 4px). This gives control over compactness.
  5. color input behavior change. In M3 themes, the color input still adds a CSS class but there are no built-in styles targeting these classes. Templates using color="primary" or color="accent" need review.
  6. Dual-support path. Components can support both M2 and M3 themes simultaneously using mat.get-theme-version() to branch. Enables incremental migration.
  7. Dark mode simplification. M3 supports color-scheme: light dark with the light-dark() CSS function, making dark mode significantly simpler than M2's manual palette overrides.

Strategy: generate M3 palettes from #203457 using the official schematic; migrate the global theme first, then component mixins; use density adjustment (−1 or −2) if components feel too large; don't chase visual parity — verify layout integrity instead (overflow, clipping, hidden elements); convert one feature route at a time if needed, using get-theme-version() for dual support.

Stylelint enforcement

  1. stylelint-declaration-strict-value enforces variables, functions, or custom CSS values for specified properties. It can require $token or var(--*) for color, font-size, spacing, etc.
  2. Built-in rules complement the plugin: color-named: never, color-no-hex, function-disallowed-list, declaration-property-unit-allowed-list.
  3. Carbon Design System precedent. IBM's Carbon uses a Stylelint plugin enforcing themes, colors, layout, type, and motion. Validates the approach at scale.
  4. Configuration comments (/* stylelint-disable */) are an intended escape hatch. Use reportDescriptionlessDisables: true to require justification.

Strategy: base config stylelint-config-standard-scss; token enforcement via stylelint-declaration-strict-value for color, background-color, border-color, font-size, spacing properties; allowed non-token values transparent, inherit, currentColor, 0, none; npm run lint:styles as a blocking CI step.

Layout integrity & UI/UX evaluation — three-layer strategy

  1. Layout integrity (Playwright): custom checks on key routes for overflow, clipping, zero-size elements, off-screen positioning, z-index collisions via page.evaluate().
  2. Accessibility (axe-core): WCAG 2.1 AA audit on key routes, integrated into e2e suite via @axe-core/playwright.
  3. Visual regression (Playwright screenshots): baseline captured pre-migration, compared during migration via toHaveScreenshot().

Storybook + Chromatic

  • Official Angular Material Storybook recipe exists (font loading, theme styles, story setup).
  • Chromatic TurboSnap reduces snapshot usage by 60-90%.
  • Story determinism requires mocked API data, fixed dates, disabled animations, consistent test fixtures.
  • Start with shared/core components (~20-30 stories); enable TurboSnap from day one.

Note: Chromatic was ultimately replaced with Playwright-based visual regression during execution (see S06 completion).

Risk assessment (post-research)

Area Pre-research Post-research Key mitigation
Baseline + cleanup high medium Well-defined scope, existing plans, FA already done
M3 theme core high medium Official schematic for palettes; layout integrity tests instead of visual parity; density settings for sizing
Hardcoded value sweep medium medium Mechanical but large (62+ files); Stylelint tooling well-proven
Stylelint enforcement medium low stylelint-declaration-strict-value is battle-tested
Design guidelines + dark mode low low M3 dark mode simpler than M2; guidelines are docs work
Storybook + Chromatic medium medium Official recipe works; TurboSnap for cost management
Layout integrity testing low Playwright is purpose-built for this; axe-core well-established

Slice roadmap (M001)

Six slices executed in order.

S01 — Baseline audit, dead code cleanup, visual baselines — risk:medium depends:[]

After this: audit script quantifies all design debt; dead code and Bootstrap shims removed; app builds and works identically; Playwright visual regression baselines captured for key routes.

Completed: 19 dead components removed.

S02 — M3 theme core & layout integrity — risk:medium depends:[S01]

After this: app renders with M3 theme + SyRF brand palette; all M2 API refs eliminated; 10 component theme mixins migrated; layout integrity tests pass on all key routes (no overflow, clipping, hidden elements).

Completed: HCT palettes adopted; all 10 mixins migrated to var(--mat-sys-*).

S03 — Hardcoded value sweep — risk:medium depends:[S02]

After this: zero hardcoded hex colors, rgba() calls, or inline style= attributes in component styles; all values use design tokens or CSS custom properties; layout integrity and visual regression still pass.

Completed: 64 SCSS files migrated; rgba() → tokens; inline style colors → CSS classes.

S04 — Stylelint enforcement — risk:low depends:[S03]

After this: npm run lint:styles passes with zero violations; CI pipeline rejects PRs with hardcoded color, spacing, or font-size values.

Completed: color-no-hex + color-named rules live; 96 named colors replaced.

S05 — Design guidelines & dark mode — risk:low depends:[S02]

After this: design guidelines doc published in docs/; aspirational M3 dark mode wired with toggle; dark mode layout integrity verified.

Completed: ThemeService drives the toggle with localStorage persistence.

S06 — Storybook, Chromatic & accessibility — risk:medium depends:[S04, S05]

After this: Storybook 10 serves shared/core component catalog (~20-30 stories); visual regression baselines captured in CI; axe-core WCAG 2.1 AA audit passes on key routes.

Completed: axe-core (serious/critical only); Storybook 10 with all stories rendering; Playwright-based visual regression replaces Chromatic.

Slice boundary map

  • S01 → S02 — produces audit script + clean codebase + visual baselines; S02 consumes clean codebase (no Bootstrap conflicts) and visual baselines.
  • S02 → S03 — produces M3 global theme (syrf-theme.scss via mat.theme()), all 10 component mixins on M3 custom properties, SCSS design token files, Playwright layout integrity test suite. S03 consumes the design token system.
  • S03 → S04 — produces zero hardcoded hex/rgba/style= colors. S04 consumes the zero-violation codebase (enforcement is meaningless if violations exist).
  • S04 → S06 — produces .stylelintrc.json + passing npm run lint:styles + CI blocking step. S06 consumes the fully token-enforced codebase.
  • S05 → S06 — produces design guidelines markdown + M3 dark mode toggle. S06 consumes the dark mode theme (stories should render in both light and dark).

Scope (M001)

In scope: dead code and unused component removal; Bootstrap class and SCSS shim removal; M2 → M3 global theme migration with SyRF brand palette; component theme mixin migration (10 files); hardcoded hex, rgba(), and inline style elimination; SCSS design token system; Stylelint CI-blocking enforcement; aspirational M3 dark mode; design guidelines documentation; Storybook 10 component catalog; visual regression CI; layout integrity Playwright tests; axe-core WCAG 2.1 AA accessibility audit.

Out of scope / non-goals: OnPush change detection migration (D001); pixel-for-pixel visual parity with M2 theme (D002); custom design mockups or Figma integration (X001); backend/API changes; new feature development.

Technical constraints: Angular 21, Angular Material M2→M3. Must use ng generate @angular/material:m3-theme schematic for palette generation. Stylelint config must allow transparent, inherit, currentColor, 0, none as non-token values. Storybook must use official Angular Material recipe for font loading and theme styles.

Integration points: CI pipeline — Stylelint as a blocking step; visual regression (Playwright) as a PR check; axe-core and layout integrity tests as part of e2e suite. angular.json — global styles configuration changed (new theme file, legacy-bootstrap removed).

Known limitations (M001, post-completion)

  • Storybook M3 system colors: CSS custom properties from the M3 theme don't apply in Storybook's isolated preview context (expected — Storybook doesn't load the full app theme).
  • Layout/a11y tests cover public routes only: authenticated routes need auth mock infrastructure.
  • image-alt violations: pre-existing — decorative images lack alt text (excluded from a11y tests).
  • Remaining inline style= attributes: 27 templates have layout-only inline styles (not color-related).

M002 — M3 System Variable Integration

The problem

After M001 migrated the Angular Material API from M2 to M3 and replaced hardcoded hex colors with SCSS design tokens, the app had a dual color system: M3's --mat-sys-* CSS custom properties coexisted with 461 custom SCSS token references (tokens.$color-*). The result was a visually broken app: white-on-white toolbar, invisible nav links, unstyled buttons, wrong feature icon colors.

Root causes identified

  1. Toolbar white-on-white (CRITICAL)mat-toolbar had color="primary" in the template. M3 ignores color="primary" on mat-toolbar (confirmed via Angular Material docs: "This API is supported in M2 themes only, it has no effect in M3 themes"). Toolbar rendered with default --mat-sys-surface (white) background; nav text was hardcoded to tokens.$color-white → white text on white background = invisible. Fix: use mat.toolbar-overrides() mixin.
  2. Missing body surface defaults — no body { background: var(--mat-sys-surface); color: var(--mat-sys-on-surface) }. Angular Material M3 docs explicitly recommend this.
  3. Button style loss — SIGN IN / CREATE YOUR ACCOUNT buttons had color="accent" + mat-raised-button. color-variants-backwards-compatibility partially works but .action-button class conflicts. Fix: remove color="accent", use M3 component tokens (--mdc-filled-button-container-color).
  4. Dual color system — 461 refs to custom tokens.$color-* across ~125 SCSS files vs 83 refs to --mat-sys-*. Two competing systems meant inconsistent colors; dark mode didn't work on custom tokens.
  5. Feature icons/text wrong colorhome.component.theme.scss used var(--mat-sys-primary-container) for .feature color. primary-container is a light pastel — should be var(--mat-sys-primary) for the dark brand color.

Strategy

Replace the custom design tokens with M3 system variables (--mat-sys-*) wherever a direct M3 equivalent exists. Keep domain-specific tokens (warning, info, success, annotation colors) as custom CSS properties that layer on top of the M3 foundation.

M3 color role mapping

Mappable to M3 system variables

Custom Token M3 System Variable Context
$color-white (on primary bg) var(--mat-sys-on-primary) Text/icons on primary backgrounds
$color-white (literal) keep as #fff / token Not semantic — just white
$color-black var(--mat-sys-on-surface) Text on surfaces
$color-surface var(--mat-sys-surface) Backgrounds
$color-surface-variant var(--mat-sys-surface-variant) Card/panel backgrounds
$color-text-primary var(--mat-sys-on-surface) Primary body text
$color-text-secondary var(--mat-sys-on-surface-variant) Secondary/subdued text
$color-text-muted* var(--mat-sys-on-surface-variant) Muted/hint text
$color-text-hint var(--mat-sys-on-surface-variant) Placeholder/hint text
$color-text-on-primary var(--mat-sys-on-primary) Text on primary backgrounds
$color-gray var(--mat-sys-on-surface-variant) General gray text
$color-lightgray var(--mat-sys-outline-variant) Borders/dividers
$color-error / $color-error-dark var(--mat-sys-error) Error states
$color-error-light var(--mat-sys-error-container) Error backgrounds
$color-primary-500 var(--mat-sys-primary) Brand primary
$color-primary-tint var(--mat-sys-primary-container) Light primary fills
$color-background var(--mat-sys-surface) Page backgrounds
$color-divider-light var(--mat-sys-outline-variant) Divider lines

Kept as custom tokens (no M3 equivalent)

  • $color-warning* — M3 has no warning color role
  • $color-info* — custom semantic
  • $color-success* / $color-annotation-* — domain-specific
  • $color-dnd-* — drag-and-drop states
  • $color-red, $color-purple, etc. — named semantic colors
  • $color-shadow-* — elevation/shadow custom values
  • All spacing, typography, z-index, breakpoint tokens — keep as-is

Slice roadmap (M002)

S01 — Foundation: M3 global defaults & token bridge — risk:low depends:[]

Set up the M3 foundation layer: toolbar overrides, body surface defaults, system utility classes, and a token bridge that re-exports custom tokens as CSS custom properties so they work with dark mode. Write a migration script that automates the bulk token→system-variable replacement.

Completed (3 commits):

  • Toolbar white-on-white fixed: mat.toolbar-overrides() with primary bg / on-primary text
  • Body surface/text defaults added (background: var(--mat-sys-surface))
  • Nav component: hardcoded white → var(--mat-sys-on-primary)
  • Home buttons: mat-raised-buttonmat-flat-button (M3 filled button)
  • Domain token bridge: warning/info/success/annotation colors exported as --syrf-color-* CSS custom properties with dark-mode overrides
  • Migration script scripts/migrate-tokens-to-m3.sh with --dry-run, --report, --verbose flags

S02 — Automated bulk migration of component styles — risk:medium depends:[S01]

Run the migration script across ~125 changed SCSS files. Manually review and fix edge cases where context matters ($color-white meaning "on-primary text" vs "literal white background").

Completed (1 commit):

  • 200 automated token→M3 replacements across 67 SCSS files
  • Critical regex bug fixed: \b word boundary matched partial tokens (e.g., $color-surface inside $color-surface-tint-subtle). Replaced with (?![\w-]) PCRE negative lookahead.

S03 — Verification & cleanup — risk:low depends:[S02]

Run full build, lint, test suite. Remove unused tokens from _design-tokens.scss. Update documentation.

Completed (1 commit):

  • 11 stylelint color-no-hex violations fixed
  • --syrf-color-error-light added to domain bridge (eliminates hex fallback in var())
  • 25 dead color tokens removed from _design-tokens.scss
  • Migration comments documenting which M3 vars replaced each token group

M002 metrics (before/after)

Metric Before M002 After M002
M3 system variable refs 83 285 (+243%)
Custom SCSS token refs 461 283 (−39%)
Domain bridge refs (--syrf-color-*) 0 12
Tokens defined in _design-tokens.scss ~248 223 (−25 dead removed)
Stylelint errors 11 (new) 0
ng build status

Remaining custom token refs by category (283 refs kept intentionally)

Category Refs Examples
Warning/status ~40 $color-warning, $color-warning-light-bg
Info ~33 $color-info, $color-info-secondary
Success ~18 $color-success, $color-success-dark
White/Black (context-dependent) ~28 $color-white (literal), $color-black
Annotation ~23 $color-annotation-primary, etc.
Surface tints (opacity-based) ~8 $color-surface-tint-light
PDF/DnD/misc ~50 $color-pdf-highlight-*, $color-dnd-*
Borders ~20 $color-border-light, $color-border-muted
Error sub-variants ~10 $color-error-border, $color-error-text
Named colors / env ~53 $color-red, $color-purple, env indicators

M3 practical guidance (reference patterns)

These patterns were referenced throughout M001/M002 and are worth keeping as code-ready examples for future work.

Toolbar override (per https://material.angular.dev/components/toolbar/styling)

:root {
  @include mat.toolbar-overrides((
    container-background-color: var(--mat-sys-primary),
    container-text-color: var(--mat-sys-on-primary),
  ));
}

Gotcha (see D21): mat.theme() re-emits toolbar tokens with default values, undoing the :root override. Must re-apply mat.toolbar-overrides() inside .global-dark-theme after every mat.theme() call.

Body defaults (per https://material.angular.dev/guide/theming-your-components)

body {
  background: var(--mat-sys-surface);
  color: var(--mat-sys-on-surface);
}

Custom component theming

:host {
  background: var(--mat-sys-primary-container);
  color: var(--mat-sys-on-primary-container);
  border: 1px solid var(--mat-sys-outline-variant);
  font: var(--mat-sys-body-large);
}

Available M3 system variables

  • Color: --mat-sys-primary, --mat-sys-on-primary, --mat-sys-primary-container, --mat-sys-on-primary-container, --mat-sys-secondary, --mat-sys-tertiary, --mat-sys-error, --mat-sys-on-error, --mat-sys-error-container, --mat-sys-surface, --mat-sys-on-surface, --mat-sys-on-surface-variant, --mat-sys-surface-variant, --mat-sys-surface-container-*, --mat-sys-outline, --mat-sys-outline-variant, --mat-sys-inverse-surface, --mat-sys-inverse-on-surface, --mat-sys-scrim, --mat-sys-shadow
  • Typography: --mat-sys-body-{sm,md,lg}, --mat-sys-display-{sm,md,lg}, --mat-sys-headline-{sm,md,lg}, --mat-sys-label-{sm,md,lg}, --mat-sys-title-{sm,md,lg}
  • Elevation: --mat-sys-level{0-5}
  • Shape: --mat-sys-corner-{none,extra-small,small,medium,large,extra-large,full}

Context-sensitive migration cases (need human judgment)

  1. $color-white — means "on-primary text" on toolbar, but "literal white background" elsewhere
  2. $color-black — means "on-surface text" in body, but "literal black" for borders
  3. $color-lightgray — sometimes border (outline-variant), sometimes background
  4. Any color used with !important — needs individual review
  5. Colors inside ::ng-deep — may need different scoping

Migration script

scripts/migrate-tokens-to-m3.sh (retained in repo): sed-based bulk migrator for tokens.$color-*var(--mat-sys-*). Supports --dry-run (no writes), --report (per-file summary), --verbose (trace each replacement). Uses PCRE (?![\w-]) negative lookahead for boundary matching (see D23 — \b caused partial matches on hyphenated tokens).


Follow-ups

From the combined M001 + M002 work, these items are explicitly deferred for future milestones (ordered by value/risk ratio).

Short term (before any further theming PR merges)

  1. Tune dark-mode page surface#121316 currently too black; revisit Option C progress palette in dark mode.
  2. Visual spot-check auth-gated pages — log in and scan project overview, screening, annotation, data export pages. Migrations are mechanical but touching 67 SCSS files warrants eyes on the result.
  3. Test dark mode thoroughly — domain bridge has dark-mode overrides but couldn't be verified in all auth-gated contexts.

Medium term (future milestones)

  1. Migrate $color-white/$color-black refs (28 total) — context-dependent, needs per-usage judgment.
  2. Migrate border tokens ($color-border-light, $color-border-muted) → var(--mat-sys-outline-variant) — 9 refs, needs visual review.
  3. Remove color="primary|accent|warn" HTML attrs (147 refs) — M3 ignores these on most components. Replace with M3 component token overrides. This is the path to removing the color-variants-backwards-compatibility shim.
  4. Migrate surface-tint tokens (11 refs) — opacity-based rgba values that don't map directly to M3 surface-container roles. Needs design review.

Long term

  1. Move domain colors to --syrf-color-* bridge — currently warning/info/success components use SCSS tokens directly. Bridge vars would give them dark-mode support.
  2. Remove color-variants-backwards-compatibility mixin — once all color= HTML attrs are gone.
  3. Audit _design-tokens.scss — 223 definitions, many infrastructure tokens aren't referenced yet. Decide: keep as aspirational design system, or trim to what's actually used.
  4. Extend layout integrity + a11y tests to auth-gated routes — requires auth mock infrastructure.

Requirements closure (M001)

Requirement Description Status
R001 Audit script ✅ Validated
R002 Dead code removal ✅ Validated
R003 M3 global theme ✅ Validated
R004 SyRF brand on M3 ✅ Validated
R005 Component mixin migration ✅ Validated
R006 Hex color elimination ✅ Validated
R007 rgba() elimination ✅ Validated
R009 Stylelint enforcement ✅ Validated
R010 Design guidelines ✅ Validated
R011 Dark mode toggle ✅ Validated
R012 axe-core a11y audit ✅ Validated
R013 Storybook catalog ✅ Validated
R014 Visual regression CI ✅ Playwright-based (replaces Chromatic)
R015 Layout integrity ✅ Validated
R016 Layout test suite ✅ Validated
R018 Visual regression baselines ✅ Validated

Decision register (D1–D24)

All decisions made during M001 + M002, preserved verbatim from .gsd/DECISIONS.md. Append-only — if a decision needs revisiting, add a new entry that supersedes it; don't edit history.

# When Scope Decision Choice Rationale Revisable?
1 2026-03-13 M001 Dark mode fidelity Aspirational M3 defaults — no hand-tuned dark palette User confirmed modernisation is the goal; M3's color-scheme support makes dark mode simpler Yes
2 2026-03-13 M001 Design reference M3 defaults + SyRF brand colors (#203457 primary) No custom mockups or Figma — M3 design language with brand theming is sufficient Yes
3 2026-03-13 M001 Stylelint enforcement level CI-blocking — PRs with violations cannot merge Without enforcement, design debt will re-accumulate; warning-only relies on review discipline Yes
4 2026-03-13 M001 OnPush migration Deferred — not part of design system milestone Behavioral change, not a styling concern; avoids scope creep Yes
5 2026-03-13 M001 Visual parity with M2 Not a goal — layout integrity is the acceptance criterion User: "the whole point is to modernise" — verify no overflow, clipping, hidden elements instead of pixel matching No
6 2026-03-13 M001 M3 palette generation Use official ng generate @angular/material:m3-theme --primaryColor=#203457 schematic Generates accessible HCT-based tonal palettes automatically; avoids manual color math Yes
7 2026-03-13 M001 Layout integrity verification Three layers: Playwright custom checks + axe-core WCAG 2.1 AA + Playwright toHaveScreenshot() Custom checks catch overflow/clipping; axe-core catches a11y regressions; screenshots catch visual drift Yes
8 2026-03-13 M001 Storybook scope Start with shared/core components (~20-30 stories), not all 205 Manageable cost (TurboSnap), focused coverage, can expand incrementally Yes
9 2026-03-13 S01/T02 Bootstrap color replacement strategy Use Angular Material color attr for primary/warn/accent; scoped action-*/theme-* classes with hardcoded hex for success/info/danger Material lacks success/info semantic colors — scoped component SCSS with exact Bootstrap hex values preserves visual parity until M3 design tokens replace them Yes
10 2026-03-13 S01/T02 Audit script Bootstrap detection Anchored regex to class="..." attrs with per-file exclusion pipeline Original \b word boundary regex produced 42 false positives from Angular Material element names (mat-expansion-panel, mat-progress-bar) and scoped class names (view-errors-btn) Yes
11 2026-03-13 S01/T03 Visual regression baseline scope Public pages only (home, mission, protocols, faq, contact-us) All project routes require Auth0 auth; mocking is out of scope for baseline capture. 5 public pages provide pre-migration reference for shared chrome (nav, footer, layout) Yes
12 2026-03-13 S01/T03 Playwright dev server strategy Expect dev server running separately; no webServer in playwright.config.ts Avoids coupling Playwright to Angular's build pipeline; simpler config; dev server already running during development Yes
13 2026-03-14 S02 M3 theme API split mat.define-theme() for theme objects, mat.theme() mixin for CSS emission define-theme returns a Sass map for passing to component mixins; theme mixin emits CSS vars and accepts string typography shorthand No
14 2026-03-14 S02 Component mixin strategy CSS custom properties (var(--mat-sys-*)) directly, not mat.get-theme-color() Simpler, no theme parameter threading, automatically adapts to light/dark via CSS cascade Yes
15 2026-03-14 S02 M2→M3 semantic color mapping warnerror, accenttertiary, background/foregroundsurface/on-surface Closest semantic equivalents in M3 color system; warn and error serve the same UX purpose No
16 2026-03-14 S02 T01+T02 simultaneous execution Migrated global theme and all component mixins together, not sequentially M3 theme objects don't contain M2 palette maps — component mixins crash if not migrated at the same time as the global theme No
17 2026-03-14 M002 Token migration strategy Replace mappable custom tokens with M3 system vars; keep domain-specific as CSS custom properties M3 system vars handle light/dark automatically, eliminate dual-color-system, align with official Angular Material guidance No
18 2026-03-14 M002 M3 toolbar override approach Use mat.toolbar-overrides() mixin per Angular Material M3 docs color="primary" on mat-toolbar has no effect in M3 — the only supported approach is the toolbar-overrides mixin or CSS custom properties No
19 2026-03-14 M002 Scripted bulk migration Build a sed-based migration script with dry-run mode for the 461 token replacements Manual file-by-file replacement across 125 SCSS files is error-prone and slow; automated script ensures consistency and is auditable Yes
20 2026-03-13 M002/S01 CTA button type mat-flat-button not mat-raised-button for primary filled CTAs M3 mat-raised-button uses --mdc-protected-button-* (elevated/outlined); mat-flat-button uses --mdc-filled-button-* (filled) which is the correct M3 filled button No
21 2026-03-13 M002/S01 Dark toolbar override pattern Re-apply mat.toolbar-overrides() inside .global-dark-theme after mat.theme() mat.theme() re-emits toolbar tokens with default values, undoing the :root override. Must re-apply after every mat.theme() call No
22 2026-03-13 M002/S01 Domain token bridge namespace --syrf-color-* CSS custom properties on :root Avoids collision with --mat-sys-*, clear provenance, consistent with Angular Material's custom property naming convention Yes
23 2026-03-13 M002/S02 Migration script regex boundary (?![\w-]) negative lookahead instead of \b word boundary \b treats hyphens as word boundaries, causing $color-surface to match inside $color-surface-tint-subtle. PCRE lookahead prevents partial matches No
24 2026-03-13 M002/S03 Error-light domain bridge Add --syrf-color-error-light to domain bridge, remove var() hex fallback Stylelint color-no-hex rule rejects hex values inside var() fallbacks. Bridge var provides the value at :root level, no fallback needed No