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 enforced —
color-no-hex+color-namedrules, 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¶
- No automated migration path. Angular Material does not provide an automated M2→M3 migrator because design decisions are subjective. Manual migration is required.
- Palette generation. The
ng generate @angular/material:m3-themeschematic generates M3 HCT-based tonal palettes from a single seed color. Run with--primaryColor=#203457to get SyRF-branded M3 palettes. - 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. - 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.
colorinput behavior change. In M3 themes, thecolorinput still adds a CSS class but there are no built-in styles targeting these classes. Templates usingcolor="primary"orcolor="accent"need review.- Dual-support path. Components can support both M2 and M3 themes simultaneously using
mat.get-theme-version()to branch. Enables incremental migration. - Dark mode simplification. M3 supports
color-scheme: light darkwith thelight-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¶
stylelint-declaration-strict-valueenforces variables, functions, or custom CSS values for specified properties. It can require$tokenorvar(--*)for color, font-size, spacing, etc.- Built-in rules complement the plugin:
color-named: never,color-no-hex,function-disallowed-list,declaration-property-unit-allowed-list. - Carbon Design System precedent. IBM's Carbon uses a Stylelint plugin enforcing themes, colors, layout, type, and motion. Validates the approach at scale.
- Configuration comments (
/* stylelint-disable */) are an intended escape hatch. UsereportDescriptionlessDisables: trueto 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¶
- Layout integrity (Playwright): custom checks on key routes for overflow, clipping, zero-size elements, off-screen positioning, z-index collisions via
page.evaluate(). - Accessibility (axe-core): WCAG 2.1 AA audit on key routes, integrated into e2e suite via
@axe-core/playwright. - 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.scssviamat.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+ passingnpm 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¶
- Toolbar white-on-white (CRITICAL) —
mat-toolbarhadcolor="primary"in the template. M3 ignorescolor="primary"onmat-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 totokens.$color-white→ white text on white background = invisible. Fix: usemat.toolbar-overrides()mixin. - Missing body surface defaults — no
body { background: var(--mat-sys-surface); color: var(--mat-sys-on-surface) }. Angular Material M3 docs explicitly recommend this. - Button style loss — SIGN IN / CREATE YOUR ACCOUNT buttons had
color="accent"+mat-raised-button.color-variants-backwards-compatibilitypartially works but.action-buttonclass conflicts. Fix: removecolor="accent", use M3 component tokens (--mdc-filled-button-container-color). - 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. - Feature icons/text wrong color —
home.component.theme.scssusedvar(--mat-sys-primary-container)for.featurecolor.primary-containeris a light pastel — should bevar(--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-button→mat-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.shwith--dry-run,--report,--verboseflags
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:
\bword boundary matched partial tokens (e.g.,$color-surfaceinside$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-hexviolations fixed --syrf-color-error-lightadded to domain bridge (eliminates hex fallback invar())- 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:rootoverride. Must re-applymat.toolbar-overrides()inside.global-dark-themeafter everymat.theme()call.
Body defaults (per https://material.angular.dev/guide/theming-your-components)¶
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)¶
$color-white— means "on-primary text" on toolbar, but "literal white background" elsewhere$color-black— means "on-surface text" in body, but "literal black" for borders$color-lightgray— sometimes border (outline-variant), sometimes background- Any color used with
!important— needs individual review - 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)¶
- Tune dark-mode page surface —
#121316currently too black; revisit Option C progress palette in dark mode. - 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.
- Test dark mode thoroughly — domain bridge has dark-mode overrides but couldn't be verified in all auth-gated contexts.
Medium term (future milestones)¶
- Migrate
$color-white/$color-blackrefs (28 total) — context-dependent, needs per-usage judgment. - Migrate border tokens (
$color-border-light,$color-border-muted) →var(--mat-sys-outline-variant)— 9 refs, needs visual review. - 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 thecolor-variants-backwards-compatibilityshim. - 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¶
- Move domain colors to
--syrf-color-*bridge — currently warning/info/success components use SCSS tokens directly. Bridge vars would give them dark-mode support. - Remove
color-variants-backwards-compatibilitymixin — once allcolor=HTML attrs are gone. - 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. - 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 | warn→error, accent→tertiary, background/foreground→surface/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 |