Files
jobtrackingapp/docs/redesign/phase-4/p4-01/README.md
T

14 KiB

P4-01 — semantic colors, focus and loaded-state protection

Source checkpoint: f696f23; dedicated branch: codex/ui-redesign-phase4. Requirement: P4-01 handoff, design architecture, accessibility plan, responsive strategy. Implementation SHA is the commit containing this record (git log -- docs/redesign/phase-4), avoiding a self-referential SHA.

Reproduced causes

The new palette-pair tests were run against the unchanged theme before editing it: 6 failed, 6 passed. MUI's contrast calculation measured light success text on the canvas at 3.04:1; light warning/error at 4.13:1 and info at 4.15:1; dark primary text at 3.70:1. The default 3:1 contrast-text selection also allowed the dark error filled-action pair at 3.07:1. These fail the project's ordinary-text target of 4.5:1. These are palette measurements, not an assertion that every old page used each pair.

The original theme supplied a translucent focus shadow but no consistent shared keyboard outline, and used the decorative divider as the outlined input boundary. Selected list items used the same layer as hover.

A real loaded CV scan independently found editable names in omitted sections at 4.22:1 (#7b7b7d on white). The responsible local style applied 58% opacity to the entire section; omitted entries used 50%. Omission from the PDF does not disable editing. This local contrast cause cannot be repaired by changing a global text token without distorting other consumers.

Implementation and changed owners

File Change and scope
job-tracker-ui/src/theme.ts Existing MUI CSS-variable theme owns semantic surfaces, borders, focus, navigation and action layers; mode-specific semantic tones, readable filled-action foregrounds and shared focus overrides
job-tracker-ui/src/theme.d.ts Small MUI palette/background augmentation for the added roles; no separate token runtime
job-tracker-ui/src/layout/AppShell.tsx Replace existing inverse-sidebar color literals with MUI variable references; scope inverse focus color to drawer content. Same colors, groups, widths, routes and collapse behavior
job-tracker-ui/src/views/CvBuilderEditor.tsx Remove whole-container opacity for omitted sections/entries only. Visibility icon/action names, inclusion settings and render/save logic retained; transient drag opacity unchanged
job-tracker-ui/src/theme-contrast.test.ts 14 pair/essential-boundary contrast cases across light/dark
job-tracker-ui/src/loaded-theme-states.test.tsx Six actual CompaniesTable loading/populated/empty/error-retry cases, both themes; no-write and dialog semantics checks
job-tracker-ui/e2e/semantic-foundations.spec.ts 12 real-browser cases, synthetic isolated accounts, loaded fixtures, actual focus contrast, state scans and screenshots

No dependencies, build scripts, CI, backend contracts, data model, route tree, providers, typography, layout, logo or final brand decision changed.

Token architecture

The existing #5B5BD6 hue remains provisional continuity, not final brand approval. Light primary retains that exact value. Dark primary uses the same hue lightened by 45%, with a dark filled-action foreground. Light success/warning/error/info use darker tones of their existing hues. Their hover tones are darker again; text on filled controls is selected at a 4.5:1 threshold. Browser scans also check the actual hovered primary action.

MUI role Contract
background.default, background.paper, background.elevated Existing canvas/paper retained; elevated overlay surface added and used by dialogs/popovers (menus inherit). Dark elevated surface is #1C1C24; light is white
text.primary/secondary/disabled Existing text hierarchy retained; no new feature-level text palette
divider, border.subtle/default/strong Decorative divider stays subtle. Essential input outlines use the stronger border.default, measured at ≥3:1 on the three application surfaces
primary, secondary, success, warning, error, info Standard MUI semantic colors and foregrounds; no duplicate status token family
action.hover/selected/focus/disabled/disabledBackground Explicit mode-specific hover/selection/focus layers; disabled controls remain disabled; focus is additionally outlined
focus.main/inverse 3px visible keyboard indicator against application or inverse-navigation surface
navigation.* Existing fixed inverse sidebar background, foreground, selection and hover moved intact into the MUI palette

background.paper serves the proposed surface role; there is no redundant background.surface alias. Generated --palette-* variables come from MUI. --app-focus-color is a scoped alias of palette.focus.inverse, not a second source of colors.

Focus and states

Shared ButtonBase covers buttons, icon buttons, tabs, menu items and interactive cards. Links/native focusable controls use :focus-visible; MUI controls also use .Mui-focusVisible. The outline is solid, 3px with 2px offset. Tabs, menu/list items and CardActionArea use an inset outline to avoid clipping inside scroll/overflow containers. InputBase outlines standard controls, including the underline-free command input. Outlined fields/selects use a 3px notched fieldset border, preserving MUI's floating-label cutout: visual inspection found that the initial outer outline crossed the label. Focus does not add tab stops to ordinary cards.

The ring adds a visible boundary rather than relying on a selected color change. Browser tests measure ≥3:1 against the actual adjacent/composited surface, check focus return after dialogs/drawers, and verify a pointer-activated settings button does not acquire keyboard-only focus styling. Outline remains a native CSS outline; no new animation system is added. Existing reduced-motion behavior is preserved.

Loading and errors already had appropriate primitives: polite named loading status/progress and MUI alert with Retry. These implementations are unchanged. New fixtures deliberately hold a read pending, expose an error, retry into empty data, then load actual owner data; editing never repeats the loading status or writes automatically. They preserve error-versus-empty distinctions. A general completion-announcement strategy and manual announcement/repetition review remain P4-02 work; this slice does not claim screen-reader certification.

Status and legacy-color handling

pipeline.ts is unchanged: Saved/Interested/Preparing → neutral; Applied → primary; Waiting/Ghosted → warning; Interview → info; Offer → success; Rejected/Withdrawn → error. Existing labels and visibility/status icons remain, so meaning is not communicated by red/green alone. Unknown/custom statuses keep their raw label and neutral tone. No status schema, color picker or arbitrary custom-color API is added.

Intentional remaining literals include printed CV page white, template/document accent controls, marketing gradients, illustration/logo artwork and specialized chart palettes. Printed paper is independent of application dark mode. These are not silently claimed as migrated. Only the demonstrated CV omission-opacity cause is changed locally; preview/layout/editor architecture is untouched.

Validation results

The final full frontend/browser runs include the notched-focus adjustment. Raw local logs live in ignored .artifacts/p4-01-*; screenshots and a compact scan index are preserved with this package. Browser trace archives contain session details and are not committed.

Gate Phase 3 inherited P4-01 result
Backend Release tests 916 passing 916 passing, 0 failed/skipped
Frontend Jest 313 passing 333 passing in 70 suites (+20), 0 failed
Default browser suite 23 actual passes + 1 expected failure 35 actual passes + 1 unchanged expected failure (+12 actual passes)
Focused loaded/focus browser No P4-specific fixture 12 passing
Standalone TypeScript + E2E file type check Passing Passing
ESLint Passing Passing
API Release build, warnings as errors Passing Passing, 0 warnings/errors
Next production export Passing Passing
Compose validator 3 passing 3 passing
B04 rate limits / production HTTP / PDF admission probes 9 / 6 / 1 passing 9 / 6 / 1 passing

The final browser report contains 38 loaded scans and zero color-contrast violations. It also retains eight aria-input-field-name node occurrences: four existing unnamed Customize controls in each theme. Those are P4-02 naming debt. Axe marked checks incomplete in 20 scan states for color contrast, ten for aria-hidden-focus and ten for aria-prohibited-attr; incomplete checks are not passes. The scan index records these limits. Representative screenshots and keyboard checks supplement the scans; they do not establish whole-product WCAG compliance. The browser runner's total of 36 expected outcomes includes the unchanged P2-D01 failure; it is not 36 successful workflows.

Exact commands (PowerShell; repository root unless Set-Location changes it):

git switch -c codex/ui-redesign-phase4 f696f23
$env:DOTNET_ROOT='C:/Users/Connor/.dotnet'
& C:/Users/Connor/.dotnet/dotnet.exe test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --configuration Release --no-restore --logger 'trx;LogFileName=p4-01.trx' --results-directory .artifacts/redesign-p4-01 -- xUnit.parallelizeTestCollections=false xUnit.maxParallelThreads=1
& C:/Users/Connor/.dotnet/dotnet.exe build JobTrackerApi/JobTrackerApi.csproj --configuration Release --no-restore -warnaserror
python scripts/test-ingress.py
Set-Location job-tracker-ui
npm test -- --watchAll=false --runInBand src/theme-contrast.test.ts
npm test -- --watchAll=false --runInBand
npm run typecheck
npm run lint
node node_modules/typescript/bin/tsc --project ../.artifacts/p4-01-e2e-tsconfig.json
$env:NEXT_PUBLIC_API_BASE_URL='http://localhost:5302/api'
$env:NEXT_TELEMETRY_DISABLED='1'
npm run build
$env:DOTNET_HOST_PATH='C:/Users/Connor/.dotnet/dotnet.exe'
$env:PLAYWRIGHT_STATIC_EXPORT='true'
$env:PLAYWRIGHT_PREBUILT_EXPORT='true'
node node_modules/@playwright/test/cli.js test e2e/semantic-foundations.spec.ts --reporter=list,json
$env:PLAYWRIGHT_JSON_OUTPUT_FILE='../.artifacts/p4-01-browser-results.json'
$env:P4_CAPTURE='true'
node node_modules/@playwright/test/cli.js test --reporter=list,json
node node_modules/@playwright/test/cli.js test --config=playwright.rate-limits.config.ts
node node_modules/@playwright/test/cli.js test --config=playwright.ingress.config.ts
node node_modules/@playwright/test/cli.js test --config=playwright.pdf.config.ts
Set-Location ..
python scripts/supply-chain.py scan-secrets
git diff --check
git diff --cached --check

The ignored E2E type config extends the frontend tsconfig, includes only e2e/semantic-foundations.spec.ts, sets incremental:false, and resolves Node types from the frontend's node_modules. No permanent CI change is needed: standalone frontend checking already exists from Phase 2. TS2571 does not recur.

Fixture corrections during validation: use the existing translated jobTableEdit action; honor server-persisted UI language as well as browser preference; use the sidebar's current button role; follow MUI roving-tab/option keyboard semantics; locate the three actual document-AI review actions. These were test-assumption errors, not product fixes. The real CV opacity finding above was fixed and reverified.

Screenshot investigation: some immediate post-axe PNGs omitted sidebar labels/icons. A temporary two-theme browser probe captured the same page before axe, after axe, after keyboard activity and after two animation frames. Text, geometry, opacity 1, visibility visible and computed white selected text stayed unchanged; the post-paint capture restored the labels without changing the DOM, styles or application. The permanent screenshot helper now allows that paint cycle before capture. Some company PNGs still partially omit sidebar glyphs, so their visual claim is limited to the main loaded state; dedicated Jobs navigation captures and keyboard assertions supply navigation evidence. This does not establish the underlying Chromium/scan capture cause or certify every captured sidebar. The two diagnostic cases passed (node node_modules/@playwright/test/cli.js test --config=../.artifacts/p4-01-nav-probe.config.ts; local log/paired PNGs in .artifacts). The probe's initial server start failed because its temporary config resolved the export directory from .artifacts; setting its server working directory to the actual frontend resolved that harness error. No application workaround was introduced.

Remaining boundaries / next slice

P4-01 is complete within its scoped acceptance criteria: shared semantic/focus foundations, both themes, loaded-state fixtures, preserved regression baseline and representative visual evidence. Remaining manual accessibility proof and later structural repairs are explicitly excluded below. No claim of full accessibility or production verification is made.

  • P4-02 remains unstarted: shared labels, named Customize controls/command input, modal/async announcement audit. Full axe findings are retained in the scan index; the new loaded tests explicitly gate contrast, not all WCAG rules. Existing broad axe tests are unchanged.
  • P4-03 remains unstarted: loaded CV preview keyboard-scroll/viewport behavior. The fixture's existing horizontal-overflow warning is retained; this ticket does not hide it or claim printable-layout repair. Drag alternatives/transient drag opacity require their own interaction review.
  • All 57 preservation contracts remain; the full product cross-product of routes/data/themes/locales/providers is not claimed covered by these representative fixtures. See traceability.
  • Narrator/NVDA, real touch/virtual keyboard, mobile screen reader, zoom/text-spacing and forced-colors manual validation remain explicit gaps. Screenshots and Chromium keyboard tests do not substitute for these.
  • P2-D01 remains the narrowly documented browser expected failure. Package/tailoring restructuring remains blocked on native success evidence.
  • F04 remains open. No deployed Traefik/nginx/firewall evidence was obtained. Feature-branch local tests do not prove remote CI or production release health.
  • No P4-02, route/layout migration, dashboard/CV redesign, B07 or production deployment. Recommended next ticket only: P4-02 after review of this checkpoint.