Files
jobtrackingapp/docs/redesign/phase-4/p4-02

P4-02 — Named controls and modal state

Implementation and local checkpoint validation complete on 2026-09-11, from 115441a409f23b56fa7db3349897bf23d738a2b3 on codex/ui-redesign-phase4. Scope: the P4-02 row of the approved plan. No P4-03, layouts, navigation, backend contracts or B07. F04 remains open.

Evidence before implementation

  • Eight raw Select call sites display labels without linking them to the control: four in CvCustomizeTab, three in the inline CoverLetterAiAssistant, one per attachment row. Existing labelled Settings, Career Profile, Jobs and Correspondence selects are retained.
  • All six RichTextField callers already supply a visible label; toolbar buttons have translated names and preserve textarea selection. Protect these existing behaviours instead of replacing the component.
  • Confirm/prompt titles already name MUI dialogs. Their explanatory messages are not descriptions; the prompt's textbox is unnamed. Confirmation has no deliberate initial action focus. Preserve MUI focus trapping, Escape and restoration.
  • QuickCommandDialog and its placeholder-only textbox are unnamed. Preserve existing Tab/Enter commands, callbacks, query reset and owner boundary; command expansion and P3-O02 request sequencing are separate work.
  • ViewStateNotice announces initial loading and errors but removes the status region on success. Six loading-aware notices (Dashboard has two resources, plus Companies, Kanban, Reminders and AdminAudit) and JobTable's error-only uses need distinct treatment. Do not announce a completion merely because an idle/error-only component mounted or an editor changed.

Preservation mapping before changes

Contracts Existing behaviour / planned change Existing automated protection P4-02 validation
PRES-C08/C09 Capability-aware CV controls; link four visible labels without changing patches/defaults cvBuilder, cv-builder-deep-link, CvBuilder backend, semantic-foundations browser Named controls, locale, unchanged setting patches; keyboard Select/preview persistence
PRES-W06/W08 Cover-letter AI parameters/review and attachment purpose; link labels only application-assets, attachments, ApplicationAssets/Attachments backend Named controls, exact metadata/AI payloads, accept/reject retained
PRES-E07/E08 Command search, translated dialogs and focus quick-command-preservation, confirm, prompt, dialog-flow, semantic-foundations Keyboard names, safe confirmation focus, Escape/return, unchanged results
PRES-E08/J08/J07/W11/S01/A02 Read loading/empty/error/retry through ViewStateNotice loaded-theme-states, use-view-resource, company/reminder/dashboard/admin suites Persistent polite completion, no repeated editing announcements, error/draft retention
PRES-C04/C05/C07/C10, PRES-W01/W09, PRES-J02/J05/J06, PRES-M01/M04, PRES-S05/A01 Shared dialog callers: rename/delete/discard/restore/send/account/admin actions Existing CV, workspace, correspondence, account export/deletion, admin suites Full regression; shared primitive changes only, no caller operation changes

RichTextField retention also protects PRES-C07 and application-package drafts within PRES-W01. This is a scoped extension of the 57-group matrix, not a claim to visually retest every contract. Completed regression and visual evidence follows below.

Documentation restructure / retained debt

The 2026-09-11 documentation restructure keeps the Phase 2 contracts and approved Phase 3 plan in place. Current open work now belongs in docs/roadmap/todo.md; do not recreate archived docs/todo/work.md. Concurrent documentation/link changes are separate from this implementation.

Connor explicitly requested committing the restructure first. Commit 682a302 contains that work (current guides, recoverable archive, comment/link updates, example placeholders, tracked roadmap TODO), including correction of stale claims that shipped CV features were new work and that B01 socket enforcement was incomplete. Generated dependency/cache README copies remain ignored locally. Local Markdown link targets, staged whitespace and secret scan passed. It contains no P4-02 UI changes; the one overlapping ApplicationAssets comment was staged independently. P4-02 continues on top of that documentation checkpoint without discarding the in-progress UI work.

Implementation decisions

  • Eight production files: src/components/cv/CvCustomizeTab.tsx, src/components/ApplicationAssets.tsx, src/components/Attachments.tsx, src/components/QuickCommandDialog.tsx, src/components/ViewStateNotice.tsx, src/confirm.tsx, src/prompt.tsx, src/i18n/translations.ts (all under job-tracker-ui).
  • Use React useId for existing visible Select labels: four CV controls, three inline cover-letter AI controls, and each attachment purpose row. The attachment's existing AI-consent switch also receives its translated purpose plus filename; retain its switch role. Options, capability filtering, defaults, patch payloads and consent values are unchanged.
  • Keep MUI DialogTitle naming and focus traps. Link confirm/prompt messages with aria-describedby; prompt input uses the visible prompt as aria-labelledby and retains its automatically associated character-count helper, 180-character limit and initial draft. Confirmation initially focuses Cancel; Escape/Cancel still resolve false/null, confirmation still resolves true/the exact entered string, and focus returns through MUI. No modal queue or focus framework added.
  • Name command search's actual dialog paper and textbox with the existing translated Quick Search label. MUI forwards a root aria-label to a presentation wrapper, so paper slot naming is deliberate. No command destinations, debounce, result logic or keyboard model changed.
  • ViewStateNotice retains one visually hidden polite, atomic status region through initial load, success and error. Only a notice which actually observed loading announces Content loaded. / Innhold lastet. on success, including empty success and recovery from initial failure. The region clears on error; the existing visible Alert/retry policy remains. Error-only callers do not invent completion. Subsequent edits leave the live text unchanged; the browser observes DOM mutations to prove this representative case. Background refresh/retry progress flags are not newly wired across the application; per-resource and manual assistive-technology coverage remains bounded below.
  • RichTextField production code is unchanged: six existing caller labels, four translated toolbar actions, Markdown selection/focus restoration and disabled editing remain. Shared tests prove this. No generic form wrapper, form rewrite, dependency, theme/token change, AppShell change, route restructuring or CI change.

Regression additions

  • named-controls.test.tsx: five cases (EN/NB CV label-to-setting changes; EN/NB rich text/Markdown/focus; disabled/separately named fields).
  • modal-state.test.tsx: four cases (EN/NB confirm and prompt cancellation/Escape/accept, initial and returned focus, exact text, helper link and reopen defaults).
  • view-state-announcements.test.tsx: three cases (EN/NB stable loading/error/recovery region; idle consumers and later loading).
  • Existing application-assets, attachments, confirm, prompt and command tests now locate controls by meaningful names/description. The AI explicit apply/reject/save gates and attachment metadata payloads remain asserted.
  • Existing six loaded-theme component cases and eight P4-01 company browser cases now expect a persistent completion status instead of its removal. No cases removed.
  • New e2e/named-controls-modal-state.spec.ts: four representative real-API workflows, each covers commands, company failed-save draft, CV-only override/canonical isolation, inline AI parameter names, attachment purpose/rename/cancelled deletion, modal Tab/Shift+Tab/Escape/focus return. Synthetic non-admin accounts, isolated SQLite, browser outbound traffic blocked; only an explicit company PUT is replaced with 503. No real AI provider calls.

The original complete PRES rows remain the requirements; this record supplies the implementation/test/visual links for the scoped changes above. The commit containing this record is the P4-02 implementation checkpoint (git log -- docs/redesign/phase-4/p4-02), avoiding a self-referential hash.

P4-02-D01 (existing structural accessibility debt, PRES-E07): opening command search now receives a full dialog axe scan. Its unchanged MUI List is a ul containing button div children, violating the list rule. Baseline source at 115441a has the same structure. Retain the full scan evidence and narrowly recognize only this command MuiList-root defect; every other dialog violation remains blocking. Fix list semantics in the later command/shell slice; this naming change does not certify the existing command list as fully accessible. P2-D01 and F04 remain open.

P4-02-D02 (existing save-error accessibility debt, PRES-J08/E08): CompaniesTable keeps its draft after a failed save, but reports the error through ToastProvider outside the MUI dialog. That toast is visually present and excluded from the accessible tree by the modal's background isolation; its 3.5-second dismissal is also insufficient as durable form error feedback. Source is unchanged from the checkpoint. Record the actual 503, retained draft and hidden-alert condition explicitly. A company/form-validation slice should render persistent error feedback inside the dialog; do not weaken modal isolation or globally move toasts during the named-control change. This ticket does not claim that caller's save-error announcement is repaired.

P4-02-D03 (existing mobile workspace accessibility debt, PRES-W01/E08): the two phone-width cover-letter scans report scrollable-region-focusable on the application progress strip. Its existing ApplicationWorkspacePage horizontal Box contains non-interactive stage content without keyboard access to scrolling. Full node evidence is retained in the scan artifact. Fix in its workspace/responsive slice; do not add focusability to unrelated cards or include the workspace layout in P4-02.

P4-02-D04 (existing translation/content defect, PRES-W06/E08): the inline cover-letter context displays a literal {name} in both languages. coverAiUsing contains the placeholder while the unchanged caller appends the CV name separately. Screenshots retain this defect; correct interpolation in the relevant application/AI content slice. It does not change AI source selection or payloads.

Validation baseline

Gate Inherited P4-01 P4-02 result
Backend Release xUnit 916 pass 916 pass, 0 skipped/failed
Frontend Jest 333 pass / 70 suites 345 pass / 73 suites; +12 cases in three new files
General Chromium 35 actual pass + P2-D01 expected failure 39 actual pass + the same P2-D01 expected failure; +4 cases
TypeScript / lint pass pass, including separate checking of both affected E2E files
Frontend production / API Release pass pass; API 0 warnings/errors with warnings as errors
Dedicated rate-limit HTTP regressions pass 9 pass
Dedicated ingress HTTP regressions pass 6 pass
Dedicated shared PDF admission HTTP probe pass 1 pass
Effective Compose / adversarial ingress validation pass 3 pass

Playwright's summary counts 40 expected outcomes as “40 passed.” The JSON report distinguishes 39 passed → passed and one failed → failed. The latter is the unchanged SQLite native package-capture DateTimeOffset failure in redesign-preservation.spec.ts; it is not a successful package workflow. Of the 39 actual passes, 23 pre-P4-01 + 12 P4-01 + 4 P4-02. No expected-failure annotation or baseline case was removed.

The final candidate includes no application changes after the production build and full backend/frontend run. The final complete browser rerun refreshed screenshots after adjusting capture scroll position and returned the results above. The full backend suite includes B01 destination enforcement, B02 ownership/session, B03 admission, B04 limiting and B05 CSV regressions; dedicated HTTP/configuration checks additionally preserve their ingress interactions. These are local regressions, not deployed ingress evidence.

Visual, responsive and accessibility review

The final Chromium run captured 28 screenshots: seven states in each of four combinations. Rendered screenshots were inspected alongside keyboard behaviour; no screenshot-specific application styles were applied. Desktop is 1440 × 900 and phone is 390 × 844. These four representative combinations are not a full eight-combination theme/language/viewport certification.

Theme / language / width Representative rendered evidence
Light / English / desktop Command, CV select, AI controls, prompt, confirm
Dark / Bokmål / desktop Command, CV select, AI controls, prompt, retained draft
Light / Bokmål / phone Command, lower CV controls, AI controls, prompt, confirm
Dark / English / phone CV select, lower CV controls, AI controls, prompt, confirm

Observed results:

  • Prompt/confirm text, messages and character helper remain readable in both themes. P4-01 focus rings remain visible around the edited textbox, returned Select and safe Cancel action without clipping in the captured controls. Confirmation retains warning icon and explanatory text as well as semantic colour.
  • Keyboard tests exercise opening, initial focus, Tab/Shift+Tab wrap, Escape, reopening defaults, acceptance/cancellation and return to the initiating control. The same interactions work in both languages and viewport sizes. Reduced-motion preference is active; no new motion is introduced.
  • CV font controls keep their existing two-column presentation; lower treatment controls remain reachable by scrolling. Mobile Edit/Preview and the desktop document preview are retained. The existing overflow warning is not hidden or repaired. CV-only persistence is checked against unchanged canonical profile data.
  • AI controls keep their inline desktop row and existing stacked phone layout. Free-plan gating remains visible. No extra cards, spacing changes, feature-level colours, branding or theme divergence were introduced. The untranslated placeholder is recorded as D04 rather than cleaned up incidentally.
  • The phone command placeholder remains visually truncated alongside its shortcut caption, but its new accessible name is complete. Existing mobile workspace tabs and wide attachment rows still require their own responsive work. Keyboard access to tested actions is proven; 320px reflow and real touch are not.
  • The failed company-save screenshot retains the user's draft and visible toast; the automated test explicitly proves that the toast is outside the modal's accessible tree (D02), so this evidence must not be described as successful error announcement.

SCAN_RESULTS.json contains all 58 axe scans from the final general suite: 38 inherited P4-01 scans and 20 new P4-02 scans. Among the 20 new scans, all four CV-control, two desktop AI-control, four prompt and four confirmation scans report no violations. Four command scans retain D01 (list); the two phone AI/workspace scans retain D03 (scrollable-region-focusable). Across all 58 scans there are zero color-contrast violations. Incomplete-rule identifiers/counts and offending nodes are retained, so this does not imply manual contrast/assistive-technology certification. Full dialog rules are blocking except the narrowly identified existing command-list defect; loaded-page gates cover naming and inherited contrast while preserving all other scan findings in evidence. No broad rule exclusion was added.

Scope review and acceptance

The bounded P4-02 implementation is complete: evidenced missing names are linked, shared dialogs expose their descriptions and preserve native MUI focus handling, and loading-aware notices have a stable completion region. Existing rich-text behaviour, draft retention, CV/attachment relationships and AI review gates are retained by behavioural tests. The eight production-file diff contains no AppShell, navigation, layout, theme, backend, schema, auth, dependency or CI change. Shared P4-01 semantic/focus styling remains authoritative. Remaining caller-level and structural defects are explicit below and do not become a claim that all forms or assistive technologies are covered.

Commands and reproducibility

From the repository root on Windows (all data is synthetic/local):

$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-02.trx' --results-directory .artifacts/redesign-p4-02 -- 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
npm run typecheck
npm run lint
node node_modules/typescript/bin/tsc --project ../.artifacts/p4-02-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'
$env:P4_02_CAPTURE='true'
$env:PLAYWRIGHT_JSON_OUTPUT_FILE='../.artifacts/p4-02-browser-results.json'
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 semantic-foundations.spec.ts and named-controls-modal-state.spec.ts, disables incremental caching, and uses frontend Node types. No package/CI change was needed. No recurrence of the old TS2571.

Final repository checks: python scripts/supply-chain.py scan-secrets passed with the P4-02 files staged; git diff --check and git diff --cached --check passed. A local Markdown target check covered 116 current/root documents (excluding preserved archive snapshots), with zero missing local targets. Logs, SQLite fixtures and test traces remain ignored under .artifacts/test output; only synthetic screenshots and curated axe findings are committed.

Before-fix regression evidence: eight naming/description failures across the targeted component suites, plus three missing-completion failures; the additional RichTextField selection failure was a test timing assumption (the existing requestAnimationFrame completes selection after focus is already present), corrected by awaiting the selected text. All 44 targeted cases then passed. Further harness corrections: RTL getByRole uses exact string matching without Playwright's exact option; a Next route-announcer alert requires selecting the specific toast; native CV autosave may finish during axe/capture, so await Saved rather than click a disabled Save button. During concurrent docs validation, the shared static output was rebuilt for port 5202: trace requests proved that mismatch; rebuild with 5302 restored the isolated test path. No application workaround or test gate weakening was used for those harness issues.

Limits / next slice

  • All 57 PRES contracts remain unchanged. This is representative named-control/modal proof, not a new coverage percentage for every product workflow.
  • No real AI provider evaluation/delivery, production infrastructure or paid-account operations were attempted. Existing Free/Pro and B02/B04/B03 controls remain independently tested.
  • Narrator/NVDA/mobile screen reader, real touch/virtual keyboard, 200%/400% zoom, 320px reflow and forced-colors verification remain manual. DOM names/live regions and Chromium keyboard tests do not establish how every assistive technology announces them.
  • The load completion region does not add a new background-refresh scheduler, per-resource contextual narration, or field-by-field validation framework. Preserve P4-01 initial loading/error/retry behaviour; broader asynchronous workflow changes need their owner-specific slice.
  • P4-02-D01–D04 remain explicit existing defects, not preservation requirements for broken behaviour. P2-D01 and F04 remain open. The existing CV overflow warning and preview keyboard debt belong to P4-03; mobile tabs/attachment-table reflow belong to their workspace/layout slices. No overflow is hidden as a workaround.
  • Recommended next ticket only: P4-03 — builder preview keyboard boundary, after review of this checkpoint and its existing integrity coordination requirements. It has not started. No B07, broad visual redesign, route/schema/auth change, branding decision or production deployment.