diff --git a/docs/ui-modernisation/visual-correction/ACTIVITY_MARKER_MOCK_FAILURE.json b/docs/ui-modernisation/visual-correction/ACTIVITY_MARKER_MOCK_FAILURE.json
new file mode 100644
index 00000000..dd435788
--- /dev/null
+++ b/docs/ui-modernisation/visual-correction/ACTIVITY_MARKER_MOCK_FAILURE.json
@@ -0,0 +1 @@
+{"numFailedTestSuites":1,"numFailedTests":1,"numPassedTestSuites":0,"numPassedTests":10,"numPendingTestSuites":0,"numPendingTests":0,"numRuntimeErrorTestSuites":0,"numTodoTests":0,"numTotalTestSuites":1,"numTotalTests":11,"openHandles":[],"snapshot":{"added":0,"didUpdate":false,"failure":false,"filesAdded":0,"filesRemoved":0,"filesRemovedList":[],"filesUnmatched":0,"filesUpdated":0,"matched":0,"total":0,"unchecked":0,"uncheckedKeysByFile":[],"unmatched":0,"updated":0},"startTime":1793772475316,"success":false,"testResults":[{"assertionResults":[{"ancestorTitles":[],"duration":249,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"A1 week glance shows recorded upcoming dates from attention data without inventing missing dates","invocations":1,"location":null,"numPassingAsserts":3,"retryReasons":[],"startAt":1793772478792,"status":"passed","title":"A1 week glance shows recorded upcoming dates from attention data without inventing missing dates"},{"ancestorTitles":[],"duration":160,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"[PRES-S01] exact monthly values, sample, custom statuses and separate salary group remain readable","invocations":1,"location":null,"numPassingAsserts":7,"retryReasons":[],"startAt":1793772479042,"status":"passed","title":"[PRES-S01] exact monthly values, sample, custom statuses and separate salary group remain readable"},{"ancestorTitles":[],"duration":903,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"[PRES-S02] all widget visibility choices save and survive remount","invocations":1,"location":null,"numPassingAsserts":8,"retryReasons":[],"startAt":1793772479202,"status":"passed","title":"[PRES-S02] all widget visibility choices save and survive remount"},{"ancestorTitles":[],"duration":239,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"[PRES-S01] a trends failure retains summary and retries without showing a false zero response rate","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1793772480106,"status":"passed","title":"[PRES-S01] a trends failure retains summary and retries without showing a false zero response rate"},{"ancestorTitles":[],"duration":69,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"[PRES-S01] a summary failure does not suppress independent trends","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1793772480346,"status":"passed","title":"[PRES-S01] a summary failure does not suppress independent trends"},{"ancestorTitles":[],"duration":176,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"[PRES-J05 J09 S01] raw status and company destinations use supported exact query fields","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1793772480416,"status":"passed","title":"[PRES-J05 J09 S01] raw status and company destinations use supported exact query fields"},{"ancestorTitles":[],"duration":92,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"[PRES-S01 S02] period changes only reload trends and retain current snapshot meaning","invocations":1,"location":null,"numPassingAsserts":3,"retryReasons":[],"startAt":1793772480592,"status":"passed","title":"[PRES-S01 S02] period changes only reload trends and retain current snapshot meaning"},{"ancestorTitles":[],"duration":48,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"[PRES-S01] zero activity is an explicit window-empty state without a fabricated response percentage","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1793772480685,"status":"passed","title":"[PRES-S01] zero activity is an explicit window-empty state without a fabricated response percentage"},{"ancestorTitles":[],"duration":73,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"[PRES-S01] activity chart uses one shared zero-based scale and a readable legend","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1793772480733,"status":"passed","title":"[PRES-S01] activity chart uses one shared zero-based scale and a readable legend"},{"ancestorTitles":[],"duration":66,"failing":false,"failureDetails":[{"matcherResult":{"message":"expect(jest.fn()).not.toHaveBeenCalled()\n\nExpected number of calls: 0\nReceived number of calls: 1\n\n1: \"/auth/preferences/dashboard\", {\"revision\": 0, \"version\": 2, \"widgets\": [{\"h\": 9, \"id\": \"attention\", \"size\": \"full\", \"visible\": false, \"w\": 12, \"x\": 0, \"y\": 0}, {\"h\": 8, \"id\": \"agenda\", \"size\": \"standard\", \"visible\": false, \"w\": 8, \"x\": 0, \"y\": 0}, {\"h\": 8, \"id\": \"funnel\", \"size\": \"standard\", \"visible\": false, \"w\": 4, \"x\": 0, \"y\": 0}, {\"h\": 5, \"id\": \"cards\", \"size\": \"full\", \"visible\": false, \"w\": 12, \"x\": 0, \"y\": 0}, {\"h\": 8, \"id\": \"activity\", \"size\": \"full\", \"visible\": false, \"w\": 12, \"x\": 0, \"y\": 0}, {\"h\": 4, \"id\": \"stageAge\", \"size\": \"standard\", \"visible\": false, \"w\": 6, \"x\": 0, \"y\": 0}, {\"h\": 4, \"id\": \"sources\", \"size\": \"standard\", \"visible\": false, \"w\": 6, \"x\": 0, \"y\": 0}, {\"h\": 4, \"id\": \"salary\", \"size\": \"full\", \"visible\": false, \"w\": 12, \"x\": 0, \"y\": 0}, {\"h\": 4, \"id\": \"companies\", \"size\": \"standard\", \"visible\": false, \"w\": 6, \"x\": 0, \"y\": 0}, {\"h\": 4, \"id\": \"skills\", \"size\": \"standard\", \"visible\": false, \"w\": 6, \"x\": 0, \"y\": 0}]}, {\"signal\": {}}","pass":true}}],"failureMessages":["Error: expect(jest.fn()).not.toHaveBeenCalled()\n\nExpected number of calls: 0\nReceived number of calls: 1\n\n1: \"/auth/preferences/dashboard\", {\"revision\": 0, \"version\": 2, \"widgets\": [{\"h\": 9, \"id\": \"attention\", \"size\": \"full\", \"visible\": false, \"w\": 12, \"x\": 0, \"y\": 0}, {\"h\": 8, \"id\": \"agenda\", \"size\": \"standard\", \"visible\": false, \"w\": 8, \"x\": 0, \"y\": 0}, {\"h\": 8, \"id\": \"funnel\", \"size\": \"standard\", \"visible\": false, \"w\": 4, \"x\": 0, \"y\": 0}, {\"h\": 5, \"id\": \"cards\", \"size\": \"full\", \"visible\": false, \"w\": 12, \"x\": 0, \"y\": 0}, {\"h\": 8, \"id\": \"activity\", \"size\": \"full\", \"visible\": false, \"w\": 12, \"x\": 0, \"y\": 0}, {\"h\": 4, \"id\": \"stageAge\", \"size\": \"standard\", \"visible\": false, \"w\": 6, \"x\": 0, \"y\": 0}, {\"h\": 4, \"id\": \"sources\", \"size\": \"standard\", \"visible\": false, \"w\": 6, \"x\": 0, \"y\": 0}, {\"h\": 4, \"id\": \"salary\", \"size\": \"full\", \"visible\": false, \"w\": 12, \"x\": 0, \"y\": 0}, {\"h\": 4, \"id\": \"companies\", \"size\": \"standard\", \"visible\": false, \"w\": 6, \"x\": 0, \"y\": 0}, {\"h\": 4, \"id\": \"skills\", \"size\": \"standard\", \"visible\": false, \"w\": 6, \"x\": 0, \"y\": 0}]}, {\"signal\": {}}\n at Object.toHaveBeenCalled (C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\src\\dashboard-presentation.test.tsx:141:29)"],"fullName":"[PRES-S01] a single recorded month has visible markers on the shared scale and retains exact values","invocations":1,"location":null,"numPassingAsserts":7,"retryReasons":[],"startAt":1793772480806,"status":"failed","title":"[PRES-S01] a single recorded month has visible markers on the shared scale and retains exact values"},{"ancestorTitles":[],"duration":246,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"[P7-02 PRES-S01 S02] setup guidance treats pending and failed summaries as unknown until their owner retries","invocations":1,"location":null,"numPassingAsserts":5,"retryReasons":[],"startAt":1793772480880,"status":"passed","title":"[P7-02 PRES-S01 S02] setup guidance treats pending and failed summaries as unknown until their owner retries"}],"endTime":1793772481136,"message":" ● [PRES-S01] a single recorded month has visible markers on the shared scale and retains exact values\n\n expect(jest.fn()).not.toHaveBeenCalled()\n\n Expected number of calls: 0\n Received number of calls: 1\n\n 1: \"/auth/preferences/dashboard\", {\"revision\": 0, \"version\": 2, \"widgets\": [{\"h\": 9, \"id\": \"attention\", \"size\": \"full\", \"visible\": false, \"w\": 12, \"x\": 0, \"y\": 0}, {\"h\": 8, \"id\": \"agenda\", \"size\": \"standard\", \"visible\": false, \"w\": 8, \"x\": 0, \"y\": 0}, {\"h\": 8, \"id\": \"funnel\", \"size\": \"standard\", \"visible\": false, \"w\": 4, \"x\": 0, \"y\": 0}, {\"h\": 5, \"id\": \"cards\", \"size\": \"full\", \"visible\": false, \"w\": 12, \"x\": 0, \"y\": 0}, {\"h\": 8, \"id\": \"activity\", \"size\": \"full\", \"visible\": false, \"w\": 12, \"x\": 0, \"y\": 0}, {\"h\": 4, \"id\": \"stageAge\", \"size\": \"standard\", \"visible\": false, \"w\": 6, \"x\": 0, \"y\": 0}, {\"h\": 4, \"id\": \"sources\", \"size\": \"standard\", \"visible\": false, \"w\": 6, \"x\": 0, \"y\": 0}, {\"h\": 4, \"id\": \"salary\", \"size\": \"full\", \"visible\": false, \"w\": 12, \"x\": 0, \"y\": 0}, {\"h\": 4, \"id\": \"companies\", \"size\": \"standard\", \"visible\": false, \"w\": 6, \"x\": 0, \"y\": 0}, {\"h\": 4, \"id\": \"skills\", \"size\": \"standard\", \"visible\": false, \"w\": 6, \"x\": 0, \"y\": 0}]}, {\"signal\": {}}\n\n 139 | expect(within(table).getByRole('cell', { name: '4', exact: true })).toBeVisible();\n 140 | expect(within(table).getByRole('cell', { name: '1', exact: true })).toBeVisible();\n > 141 | expect(mockedApi.put).not.toHaveBeenCalled();\n | ^\n 142 | });\n 143 |\n 144 | test('[P7-02 PRES-S01 S02] setup guidance treats pending and failed summaries as unknown until their owner retries', async () => {\n\n at Object.toHaveBeenCalled (src/dashboard-presentation.test.tsx:141:29)\n","name":"C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\src\\dashboard-presentation.test.tsx","startTime":1793772476205,"status":"failed","summary":""}],"wasInterrupted":false}
diff --git a/docs/ui-modernisation/visual-correction/ACTIVITY_MARKER_RED.json b/docs/ui-modernisation/visual-correction/ACTIVITY_MARKER_RED.json
new file mode 100644
index 00000000..0c032ad4
--- /dev/null
+++ b/docs/ui-modernisation/visual-correction/ACTIVITY_MARKER_RED.json
@@ -0,0 +1 @@
+{"numFailedTestSuites":1,"numFailedTests":1,"numPassedTestSuites":0,"numPassedTests":0,"numPendingTestSuites":0,"numPendingTests":10,"numRuntimeErrorTestSuites":0,"numTodoTests":0,"numTotalTestSuites":1,"numTotalTests":11,"openHandles":[],"snapshot":{"added":0,"didUpdate":false,"failure":false,"filesAdded":0,"filesRemoved":0,"filesRemovedList":[],"filesUnmatched":0,"filesUpdated":0,"matched":0,"total":0,"unchecked":0,"uncheckedKeysByFile":[],"unmatched":0,"updated":0},"startTime":1793772446227,"success":false,"testResults":[{"assertionResults":[{"ancestorTitles":[],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"A1 week glance shows recorded upcoming dates from attention data without inventing missing dates","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793772457876,"status":"pending","title":"A1 week glance shows recorded upcoming dates from attention data without inventing missing dates"},{"ancestorTitles":[],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"[PRES-S01] exact monthly values, sample, custom statuses and separate salary group remain readable","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793772457876,"status":"pending","title":"[PRES-S01] exact monthly values, sample, custom statuses and separate salary group remain readable"},{"ancestorTitles":[],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"[PRES-S02] all widget visibility choices save and survive remount","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793772457876,"status":"pending","title":"[PRES-S02] all widget visibility choices save and survive remount"},{"ancestorTitles":[],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"[PRES-S01] a trends failure retains summary and retries without showing a false zero response rate","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793772457876,"status":"pending","title":"[PRES-S01] a trends failure retains summary and retries without showing a false zero response rate"},{"ancestorTitles":[],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"[PRES-S01] a summary failure does not suppress independent trends","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793772457876,"status":"pending","title":"[PRES-S01] a summary failure does not suppress independent trends"},{"ancestorTitles":[],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"[PRES-J05 J09 S01] raw status and company destinations use supported exact query fields","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793772457876,"status":"pending","title":"[PRES-J05 J09 S01] raw status and company destinations use supported exact query fields"},{"ancestorTitles":[],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"[PRES-S01 S02] period changes only reload trends and retain current snapshot meaning","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793772457876,"status":"pending","title":"[PRES-S01 S02] period changes only reload trends and retain current snapshot meaning"},{"ancestorTitles":[],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"[PRES-S01] zero activity is an explicit window-empty state without a fabricated response percentage","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793772457876,"status":"pending","title":"[PRES-S01] zero activity is an explicit window-empty state without a fabricated response percentage"},{"ancestorTitles":[],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"[PRES-S01] activity chart uses one shared zero-based scale and a readable legend","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793772457876,"status":"pending","title":"[PRES-S01] activity chart uses one shared zero-based scale and a readable legend"},{"ancestorTitles":[],"duration":249,"failing":false,"failureDetails":[{"matcherResult":{"message":"expect(received).toHaveLength(expected)\n\nExpected length: 2\nReceived length: 0\nReceived object: []","pass":false}}],"failureMessages":["Error: expect(received).toHaveLength(expected)\n\nExpected length: 2\nReceived length: 0\nReceived object: []\n at Object.toHaveLength (C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\src\\dashboard-presentation.test.tsx:133:19)"],"fullName":"[PRES-S01] a single recorded month has visible markers on the shared scale and retains exact values","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793772457876,"status":"failed","title":"[PRES-S01] a single recorded month has visible markers on the shared scale and retains exact values"},{"ancestorTitles":[],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"[P7-02 PRES-S01 S02] setup guidance treats pending and failed summaries as unknown until their owner retries","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793772458132,"status":"pending","title":"[P7-02 PRES-S01 S02] setup guidance treats pending and failed summaries as unknown until their owner retries"}],"endTime":1793772458141,"message":" ● [PRES-S01] a single recorded month has visible markers on the shared scale and retains exact values\n\n expect(received).toHaveLength(expected)\n\n Expected length: 2\n Received length: 0\n Received object: []\n\n 131 | const activity = await screen.findByRole('region', { name: 'Application activity' });\n 132 | const markers = activity.querySelectorAll('svg circle');\n > 133 | expect(markers).toHaveLength(2);\n | ^\n 134 | expect(markers[0]).toHaveAttribute('cy', '20');\n 135 | expect(markers[1]).toHaveAttribute('cy', '140');\n 136 | for (const marker of markers) expect(Number(marker.getAttribute('r'))).toBeGreaterThan(0);\n\n at Object.toHaveLength (src/dashboard-presentation.test.tsx:133:19)\n","name":"C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\src\\dashboard-presentation.test.tsx","startTime":1793772448287,"status":"failed","summary":""}],"wasInterrupted":false}
diff --git a/docs/ui-modernisation/visual-correction/ACTIVITY_MARKER_TESTS.json b/docs/ui-modernisation/visual-correction/ACTIVITY_MARKER_TESTS.json
new file mode 100644
index 00000000..848b9ea7
--- /dev/null
+++ b/docs/ui-modernisation/visual-correction/ACTIVITY_MARKER_TESTS.json
@@ -0,0 +1 @@
+{"numFailedTestSuites":0,"numFailedTests":0,"numPassedTestSuites":1,"numPassedTests":11,"numPendingTestSuites":0,"numPendingTests":0,"numRuntimeErrorTestSuites":0,"numTodoTests":0,"numTotalTestSuites":1,"numTotalTests":11,"openHandles":[],"snapshot":{"added":0,"didUpdate":false,"failure":false,"filesAdded":0,"filesRemoved":0,"filesRemovedList":[],"filesUnmatched":0,"filesUpdated":0,"matched":0,"total":0,"unchecked":0,"uncheckedKeysByFile":[],"unmatched":0,"updated":0},"startTime":1793772508303,"success":true,"testResults":[{"assertionResults":[{"ancestorTitles":[],"duration":256,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"A1 week glance shows recorded upcoming dates from attention data without inventing missing dates","invocations":1,"location":null,"numPassingAsserts":3,"retryReasons":[],"startAt":1793772511768,"status":"passed","title":"A1 week glance shows recorded upcoming dates from attention data without inventing missing dates"},{"ancestorTitles":[],"duration":157,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"[PRES-S01] exact monthly values, sample, custom statuses and separate salary group remain readable","invocations":1,"location":null,"numPassingAsserts":7,"retryReasons":[],"startAt":1793772512024,"status":"passed","title":"[PRES-S01] exact monthly values, sample, custom statuses and separate salary group remain readable"},{"ancestorTitles":[],"duration":940,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"[PRES-S02] all widget visibility choices save and survive remount","invocations":1,"location":null,"numPassingAsserts":8,"retryReasons":[],"startAt":1793772512181,"status":"passed","title":"[PRES-S02] all widget visibility choices save and survive remount"},{"ancestorTitles":[],"duration":237,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"[PRES-S01] a trends failure retains summary and retries without showing a false zero response rate","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1793772513121,"status":"passed","title":"[PRES-S01] a trends failure retains summary and retries without showing a false zero response rate"},{"ancestorTitles":[],"duration":71,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"[PRES-S01] a summary failure does not suppress independent trends","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1793772513358,"status":"passed","title":"[PRES-S01] a summary failure does not suppress independent trends"},{"ancestorTitles":[],"duration":140,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"[PRES-J05 J09 S01] raw status and company destinations use supported exact query fields","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1793772513430,"status":"passed","title":"[PRES-J05 J09 S01] raw status and company destinations use supported exact query fields"},{"ancestorTitles":[],"duration":95,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"[PRES-S01 S02] period changes only reload trends and retain current snapshot meaning","invocations":1,"location":null,"numPassingAsserts":3,"retryReasons":[],"startAt":1793772513570,"status":"passed","title":"[PRES-S01 S02] period changes only reload trends and retain current snapshot meaning"},{"ancestorTitles":[],"duration":45,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"[PRES-S01] zero activity is an explicit window-empty state without a fabricated response percentage","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1793772513665,"status":"passed","title":"[PRES-S01] zero activity is an explicit window-empty state without a fabricated response percentage"},{"ancestorTitles":[],"duration":60,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"[PRES-S01] activity chart uses one shared zero-based scale and a readable legend","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1793772513710,"status":"passed","title":"[PRES-S01] activity chart uses one shared zero-based scale and a readable legend"},{"ancestorTitles":[],"duration":118,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"[PRES-S01] a single recorded month has visible markers on the shared scale and retains exact values","invocations":1,"location":null,"numPassingAsserts":8,"retryReasons":[],"startAt":1793772513771,"status":"passed","title":"[PRES-S01] a single recorded month has visible markers on the shared scale and retains exact values"},{"ancestorTitles":[],"duration":232,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"[P7-02 PRES-S01 S02] setup guidance treats pending and failed summaries as unknown until their owner retries","invocations":1,"location":null,"numPassingAsserts":5,"retryReasons":[],"startAt":1793772513889,"status":"passed","title":"[P7-02 PRES-S01 S02] setup guidance treats pending and failed summaries as unknown until their owner retries"}],"endTime":1793772514122,"message":"","name":"C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\src\\dashboard-presentation.test.tsx","startTime":1793772509220,"status":"passed","summary":""}],"wasInterrupted":false}
diff --git a/docs/ui-modernisation/visual-correction/CAREER_DOCUMENT_ACTIVITY.md b/docs/ui-modernisation/visual-correction/CAREER_DOCUMENT_ACTIVITY.md
new file mode 100644
index 00000000..8e426f0b
--- /dev/null
+++ b/docs/ui-modernisation/visual-correction/CAREER_DOCUMENT_ACTIVITY.md
@@ -0,0 +1,17 @@
+# Career/document surfaces and single-month activity
+
+4 November 2026. Continue from published and independently verified `79d7bbe0a151472c551dfdc73cce628e92207d5a`, runtimea7d72673. Earlier source-bound evidence remains valid within its scope. New application edits are within approved IM10 visual correction.
+
+Career: remove decorative overview status/import/recent-CV section surfaces and action-card frames. Keep14 edit targets, completion indicators, warning/error/progress states and every link. Open the workspace navigation, profile/import container, import suggestion, history collection and structured editor without moving their state/ref/ID/conditional boundaries. Form fields, import comparison objects, controls, save/conflict/history, canonical profile ownership and legacy readers remain. No extraction engine or PDF renderer change.
+
+Documents: move the existing Jump to documents control before the filter stack so phone/keyboard users can reach results immediately. Preserve the same focus-only handler and return action, all filters and URL semantics. Mobile documents become divided articles; desktop table and explicit empty/error states remain. Download/source/permission owners are untouched.
+
+Activity V15: a nonzero single-month fixture produced move-only SVG paths, leaving the graph blank. Add a filled application point and an outlined response point on the existing common scale. Different radii retain both indicators if values coincide. Recorded values, multi-month paths, zero-data state, legend and accessible exact-value table remain unchanged. No generated/interpolated data is introduced.
+
+Native evidence:81 tests/seven Career/history/import/evidence/editor/completeness/dashboard suites plus33 document tests/one disjoint suite PASSED (114 distinct). Two new EN/NB document assertions reproduced the late jump control before it moved; the existing focus/no-request checks pass. The marker regression reproduced zero markers. Its first full-suite attempt encountered a prior test's retained PUT call; compare the new test's before/after write count, preserving the no-new-write guarantee. Retain both failure artifacts. An unsupported test selector option was removed after TypeScript rejected it; final types/full lint pass. No additional backend or physical PDF checks are required for these frontend-only changes.
+
+Rendered preparation: existing calendar, document/mail and Career pilots passed on priora7d72673 export; those are preserved in *-before directories and do not qualify this successor. Existing verifiers have opt-in nine-width output directories and expected-count assertions. Targeted viewport captures show the edited section/message rather than resetting every screenshot to the page top. Career matrix additionally checks that all14 keyboard edit targets are unobscured. A separate activity-marker mode reuses the dashboard fixture while skipping unaffected onboarding/layout-save flows; their published36/72 evidence remains separate.
+
+Pending: settled source commit/build; pilots;36-configuration Career216, document/mail216, calendar108 and activity36 scans/captures; actual inspection, manifests and publication. These are intended counts, not execution claims. Mail uses intercepted synthetic save/confirmed-send/uncertain-outcome only; no real delivery, provider or model. Full34-route/57-group, complete journeys, human/device/native-language, final integrated security/recovery/release and owner visual acceptance remain OPEN.
+
+Rollback reverts these presentation changes and marker addition; no schema/data migration or document revision rollback. Review capacity limitation: the previous review agent reached its usage limit; no new independent review or security acceptance is claimed for this successor.
diff --git a/docs/ui-modernisation/visual-correction/CAREER_FLAT_ACTIVITY_TESTS.json b/docs/ui-modernisation/visual-correction/CAREER_FLAT_ACTIVITY_TESTS.json
new file mode 100644
index 00000000..976ba19e
--- /dev/null
+++ b/docs/ui-modernisation/visual-correction/CAREER_FLAT_ACTIVITY_TESTS.json
@@ -0,0 +1 @@
+{"numFailedTestSuites":0,"numFailedTests":0,"numPassedTestSuites":7,"numPassedTests":81,"numPendingTestSuites":0,"numPendingTests":0,"numRuntimeErrorTestSuites":0,"numTodoTests":0,"numTotalTestSuites":7,"numTotalTests":81,"openHandles":[],"snapshot":{"added":0,"didUpdate":false,"failure":false,"filesAdded":0,"filesRemoved":0,"filesRemovedList":[],"filesUnmatched":0,"filesUpdated":0,"matched":0,"total":0,"unchecked":0,"uncheckedKeysByFile":[],"unmatched":0,"updated":0},"startTime":1793772667879,"success":true,"testResults":[{"assertionResults":[{"ancestorTitles":[],"duration":434,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"saves factual evidence with provenance and tags","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1793772671463,"status":"passed","title":"saves factual evidence with provenance and tags"},{"ancestorTitles":[],"duration":252,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"pending evidence reads announce loading without claiming an empty bank or allowing a raced save","invocations":1,"location":null,"numPassingAsserts":10,"retryReasons":[],"startAt":1793772671897,"status":"passed","title":"pending evidence reads announce loading without claiming an empty bank or allowing a raced save"},{"ancestorTitles":[],"duration":343,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"failed list reads remain distinct from empty state and retry only GET while preserving the draft","invocations":1,"location":null,"numPassingAsserts":9,"retryReasons":[],"startAt":1793772672149,"status":"passed","title":"failed list reads remain distinct from empty state and retry only GET while preserving the draft"},{"ancestorTitles":[],"duration":415,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"failed saves retain factual inputs and retry the same explicit save without reloading the list","invocations":1,"location":null,"numPassingAsserts":12,"retryReasons":[],"startAt":1793772672493,"status":"passed","title":"failed saves retain factual inputs and retry the same explicit save without reloading the list"},{"ancestorTitles":[],"duration":325,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"a pending save prevents duplicate writes and prevents edits being cleared by the response","invocations":1,"location":null,"numPassingAsserts":7,"retryReasons":[],"startAt":1793772672909,"status":"passed","title":"a pending save prevents duplicate writes and prevents edits being cleared by the response"},{"ancestorTitles":[],"duration":527,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"contextual deletion is named and cancellation performs no writes or draft loss","invocations":1,"location":null,"numPassingAsserts":10,"retryReasons":[],"startAt":1793772673234,"status":"passed","title":"contextual deletion is named and cancellation performs no writes or draft loss"},{"ancestorTitles":[],"duration":644,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"failed deletion keeps the row and draft, then reconfirms the same delete without list reload","invocations":1,"location":null,"numPassingAsserts":10,"retryReasons":[],"startAt":1793772673761,"status":"passed","title":"failed deletion keeps the row and draft, then reconfirms the same delete without list reload"},{"ancestorTitles":[],"duration":82,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"evidence sections and factual fields have semantic headings and required labels in Bokmål","invocations":1,"location":null,"numPassingAsserts":11,"retryReasons":[],"startAt":1793772674405,"status":"passed","title":"evidence sections and factual fields have semantic headings and required labels in Bokmål"}],"endTime":1793772674490,"message":"","name":"C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\src\\career-evidence.test.tsx","startTime":1793772668845,"status":"passed","summary":""},{"assertionResults":[{"ancestorTitles":[],"duration":4923,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"a locale update retains dirty structured and raw drafts without reading their source again","invocations":1,"location":null,"numPassingAsserts":9,"retryReasons":[],"startAt":1793772675873,"status":"passed","title":"a locale update retains dirty structured and raw drafts without reading their source again"},{"ancestorTitles":[],"duration":2005,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"edits en canonical projects and certifications through the existing versioned Save without changing their IDs or sibling facts","invocations":1,"location":null,"numPassingAsserts":12,"retryReasons":[],"startAt":1793772680796,"status":"passed","title":"edits en canonical projects and certifications through the existing versioned Save without changing their IDs or sibling facts"},{"ancestorTitles":[],"duration":2071,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"edits nb canonical projects and certifications through the existing versioned Save without changing their IDs or sibling facts","invocations":1,"location":null,"numPassingAsserts":12,"retryReasons":[],"startAt":1793772682801,"status":"passed","title":"edits nb canonical projects and certifications through the existing versioned Save without changing their IDs or sibling facts"},{"ancestorTitles":[],"duration":3997,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"project and certification add/remove controls keep sibling entries and focus before the explicit canonical Save","invocations":1,"location":null,"numPassingAsserts":9,"retryReasons":[],"startAt":1793772684872,"status":"passed","title":"project and certification add/remove controls keep sibling entries and focus before the explicit canonical Save"},{"ancestorTitles":[],"duration":652,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"a pending initial read keeps one request and reports its failure in the latest locale without language writes","invocations":1,"location":null,"numPassingAsserts":15,"retryReasons":[],"startAt":1793772688869,"status":"passed","title":"a pending initial read keeps one request and reports its failure in the latest locale without language writes"},{"ancestorTitles":[],"duration":4812,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"a held Save locks every editor mutation, raw input and history restore until its snapshot settles","invocations":1,"location":null,"numPassingAsserts":52,"retryReasons":[],"startAt":1793772689521,"status":"passed","title":"a held Save locks every editor mutation, raw input and history restore until its snapshot settles"},{"ancestorTitles":[],"duration":1504,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"two Save activations before React commits pending state send only one canonical PUT","invocations":1,"location":null,"numPassingAsserts":4,"retryReasons":[],"startAt":1793772694333,"status":"passed","title":"two Save activations before React commits pending state send only one canonical PUT"},{"ancestorTitles":[],"duration":3460,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"a failed Save retains a scoped localized error and draft for retry through the same Save button","invocations":1,"location":null,"numPassingAsserts":22,"retryReasons":[],"startAt":1793772695837,"status":"passed","title":"a failed Save retains a scoped localized error and draft for retry through the same Save button"},{"ancestorTitles":[],"duration":1211,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"the editor exposes named H2/H3 groups and contextual repeated-item remove controls","invocations":1,"location":null,"numPassingAsserts":21,"retryReasons":[],"startAt":1793772699297,"status":"passed","title":"the editor exposes named H2/H3 groups and contextual repeated-item remove controls"},{"ancestorTitles":[],"duration":4789,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"manual edits preserve long-tail data, custom links, opaque IDs and modeled fields without editor controls","invocations":1,"location":null,"numPassingAsserts":7,"retryReasons":[],"startAt":1793772700508,"status":"passed","title":"manual edits preserve long-tail data, custom links, opaque IDs and modeled fields without editor controls"},{"ancestorTitles":[],"duration":2966,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"a rejected save (409) keeps the draft and its original revision for deliberate retry","invocations":1,"location":null,"numPassingAsserts":9,"retryReasons":[],"startAt":1793772705297,"status":"passed","title":"a rejected save (409) keeps the draft and its original revision for deliberate retry"},{"ancestorTitles":[],"duration":3196,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"a rejected save (0) keeps the draft and its original revision for deliberate retry","invocations":1,"location":null,"numPassingAsserts":9,"retryReasons":[],"startAt":1793772708263,"status":"passed","title":"a rejected save (0) keeps the draft and its original revision for deliberate retry"},{"ancestorTitles":[],"duration":3256,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"consecutive acknowledged career saves advance the revision returned with the saved snapshot","invocations":1,"location":null,"numPassingAsserts":5,"retryReasons":[],"startAt":1793772711459,"status":"passed","title":"consecutive acknowledged career saves advance the revision returned with the saved snapshot"},{"ancestorTitles":[],"duration":2588,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"a delayed prior lifecycle read cannot replace edits in the current career editor","invocations":1,"location":null,"numPassingAsserts":4,"retryReasons":[],"startAt":1793772714715,"status":"passed","title":"a delayed prior lifecycle read cannot replace edits in the current career editor"},{"ancestorTitles":[],"duration":2057,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"a read-only provider cannot edit canonical fields or trigger canonical writes","invocations":1,"location":null,"numPassingAsserts":45,"retryReasons":[],"startAt":1793772717303,"status":"passed","title":"a read-only provider cannot edit canonical fields or trigger canonical writes"}],"endTime":1793772719361,"message":"","name":"C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\src\\career-editor.test.tsx","startTime":1793772674509,"status":"passed","summary":""},{"assertionResults":[{"ancestorTitles":[],"duration":939,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"disabled CV worker upload gives actionable en recovery without changing the profile","invocations":1,"location":null,"numPassingAsserts":8,"retryReasons":[],"startAt":1793772720118,"status":"passed","title":"disabled CV worker upload gives actionable en recovery without changing the profile"},{"ancestorTitles":[],"duration":637,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"disabled CV worker upload gives actionable nb recovery without changing the profile","invocations":1,"location":null,"numPassingAsserts":8,"retryReasons":[],"startAt":1793772721057,"status":"passed","title":"disabled CV worker upload gives actionable nb recovery without changing the profile"},{"ancestorTitles":[],"duration":1086,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"failed history reads have a scoped Retry that preserves unsaved source fields","invocations":1,"location":null,"numPassingAsserts":9,"retryReasons":[],"startAt":1793772721694,"status":"passed","title":"failed history reads have a scoped Retry that preserves unsaved source fields"},{"ancestorTitles":[],"duration":1101,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"a dirty canonical profile cannot be replaced through history restore","invocations":1,"location":null,"numPassingAsserts":6,"retryReasons":[],"startAt":1793772722780,"status":"passed","title":"a dirty canonical profile cannot be replaced through history restore"},{"ancestorTitles":[],"duration":1087,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"each failed comparison can retry its own run without refetching canonical data or losing another comparison","invocations":1,"location":null,"numPassingAsserts":10,"retryReasons":[],"startAt":1793772723881,"status":"passed","title":"each failed comparison can retry its own run without refetching canonical data or losing another comparison"},{"ancestorTitles":[],"duration":580,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"review shows actual current and imported values and discloses fields outside the comparison","invocations":1,"location":null,"numPassingAsserts":7,"retryReasons":[],"startAt":1793772724968,"status":"passed","title":"review shows actual current and imported values and discloses fields outside the comparison"},{"ancestorTitles":[],"duration":1082,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"selecting then deselecting low-confidence data sends the explicit empty selection","invocations":1,"location":null,"numPassingAsserts":7,"retryReasons":[],"startAt":1793772725548,"status":"passed","title":"selecting then deselecting low-confidence data sends the explicit empty selection"},{"ancestorTitles":[],"duration":1688,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"held acceptance locks review choices and all canonical editors and prevents duplicate or overlapping writes","invocations":1,"location":null,"numPassingAsserts":13,"retryReasons":[],"startAt":1793772726630,"status":"passed","title":"held acceptance locks review choices and all canonical editors and prevents duplicate or overlapping writes"},{"ancestorTitles":[],"duration":1415,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"failed acceptance keeps the chosen stable IDs and shows durable translated recovery without automatically resending","invocations":1,"location":null,"numPassingAsserts":11,"retryReasons":[],"startAt":1793772728318,"status":"passed","title":"failed acceptance keeps the chosen stable IDs and shows durable translated recovery without automatically resending"},{"ancestorTitles":[],"duration":1724,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"discarding an import preserves unsaved structured and raw drafts and does not reload the canonical profile","invocations":1,"location":null,"numPassingAsserts":9,"retryReasons":[],"startAt":1793772729733,"status":"passed","title":"discarding an import preserves unsaved structured and raw drafts and does not reload the canonical profile"},{"ancestorTitles":[],"duration":1550,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"saving canonical changes refreshes history and comparisons and clears choices made against the earlier source","invocations":1,"location":null,"numPassingAsserts":8,"retryReasons":[],"startAt":1793772731457,"status":"passed","title":"saving canonical changes refreshes history and comparisons and clears choices made against the earlier source"},{"ancestorTitles":[],"duration":1762,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"a failed restore keeps the original source and known history with an explicit retry","invocations":1,"location":null,"numPassingAsserts":10,"retryReasons":[],"startAt":1793772733007,"status":"passed","title":"a failed restore keeps the original source and known history with an explicit retry"},{"ancestorTitles":[],"duration":1123,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"an operation cancellation is guarded while pending and leaves an unsaved source draft intact","invocations":1,"location":null,"numPassingAsserts":8,"retryReasons":[],"startAt":1793772734769,"status":"passed","title":"an operation cancellation is guarded while pending and leaves an unsaved source draft intact"},{"ancestorTitles":[],"duration":979,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"a failed operation retry stays scoped and only resends the same operation on explicit user retry","invocations":1,"location":null,"numPassingAsserts":8,"retryReasons":[],"startAt":1793772735892,"status":"passed","title":"a failed operation retry stays scoped and only resends the same operation on explicit user retry"},{"ancestorTitles":[],"duration":692,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"Bokmål localizes comparison labels while preserving imported facts named like UI labels or object properties","invocations":1,"location":null,"numPassingAsserts":6,"retryReasons":[],"startAt":1793772736871,"status":"passed","title":"Bokmål localizes comparison labels while preserving imported facts named like UI labels or object properties"},{"ancestorTitles":[],"duration":716,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"the known low-confidence contact placeholder is localized","invocations":1,"location":null,"numPassingAsserts":5,"retryReasons":[],"startAt":1793772737563,"status":"passed","title":"the known low-confidence contact placeholder is localized"}],"endTime":1793772738280,"message":"","name":"C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\src\\career-import-review.test.tsx","startTime":1793772719370,"status":"passed","summary":""},{"assertionResults":[{"ancestorTitles":[],"duration":117,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"first-run workspace presents concise profile, import, general CV and job-specific actions","invocations":1,"location":null,"numPassingAsserts":8,"retryReasons":[],"startAt":1793772739027,"status":"passed","title":"first-run workspace presents concise profile, import, general CV and job-specific actions"},{"ancestorTitles":[],"duration":77,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"career workspace overview and import state render in Bokmål","invocations":1,"location":null,"numPassingAsserts":5,"retryReasons":[],"startAt":1793772739144,"status":"passed","title":"career workspace overview and import state render in Bokmål"},{"ancestorTitles":[],"duration":94,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"returning workspace shows missing profile information and recent general and job-specific CVs","invocations":1,"location":null,"numPassingAsserts":6,"retryReasons":[],"startAt":1793772739221,"status":"passed","title":"returning workspace shows missing profile information and recent general and job-specific CVs"},{"ancestorTitles":[],"duration":44,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"workspace exposes the pending_review import state as a resumable action","invocations":1,"location":null,"numPassingAsserts":3,"retryReasons":[],"startAt":1793772739316,"status":"passed","title":"workspace exposes the pending_review import state as a resumable action"},{"ancestorTitles":[],"duration":54,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"workspace exposes the queued import state as a resumable action","invocations":1,"location":null,"numPassingAsserts":3,"retryReasons":[],"startAt":1793772739360,"status":"passed","title":"workspace exposes the queued import state as a resumable action"},{"ancestorTitles":[],"duration":55,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"workspace exposes the failed import state as a resumable action","invocations":1,"location":null,"numPassingAsserts":3,"retryReasons":[],"startAt":1793772739414,"status":"passed","title":"workspace exposes the failed import state as a resumable action"},{"ancestorTitles":[],"duration":45,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"workspace represents loading and profile-load errors without inventing status","invocations":1,"location":null,"numPassingAsserts":4,"retryReasons":[],"startAt":1793772739469,"status":"passed","title":"workspace represents loading and profile-load errors without inventing status"},{"ancestorTitles":[],"duration":62,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"a settled failed profile read hides the completeness score and retries only through its existing owner","invocations":1,"location":null,"numPassingAsserts":5,"retryReasons":[],"startAt":1793772739514,"status":"passed","title":"a settled failed profile read hides the completeness score and retries only through its existing owner"},{"ancestorTitles":[],"duration":78,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"an unknown import read cannot claim no imports, completion or first-run guidance","invocations":1,"location":null,"numPassingAsserts":4,"retryReasons":[],"startAt":1793772739576,"status":"passed","title":"an unknown import read cannot claim no imports, completion or first-run guidance"},{"ancestorTitles":[],"duration":63,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"a failed import read stays unknown and keeps its own retry separate from the profile read","invocations":1,"location":null,"numPassingAsserts":5,"retryReasons":[],"startAt":1793772739654,"status":"passed","title":"a failed import read stays unknown and keeps its own retry separate from the profile read"},{"ancestorTitles":[],"duration":143,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"failed recent-CV reads do not imply first-run and retry the same list without writes","invocations":1,"location":null,"numPassingAsserts":7,"retryReasons":[],"startAt":1793772739717,"status":"passed","title":"failed recent-CV reads do not imply first-run and retry the same list without writes"},{"ancestorTitles":[],"duration":3851,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"en overview opens every canonical section directly without saving or reloading the profile","invocations":1,"location":null,"numPassingAsserts":59,"retryReasons":[],"startAt":1793772739860,"status":"passed","title":"en overview opens every canonical section directly without saving or reloading the profile"},{"ancestorTitles":[],"duration":3925,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"nb overview opens every canonical section directly without saving or reloading the profile","invocations":1,"location":null,"numPassingAsserts":59,"retryReasons":[],"startAt":1793772743711,"status":"passed","title":"nb overview opens every canonical section directly without saving or reloading the profile"},{"ancestorTitles":[],"duration":605,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"en section indicators retain server scoring and stop claiming saved status for an unsaved draft","invocations":1,"location":null,"numPassingAsserts":6,"retryReasons":[],"startAt":1793772747636,"status":"passed","title":"en section indicators retain server scoring and stop claiming saved status for an unsaved draft"},{"ancestorTitles":[],"duration":375,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"nb section indicators retain server scoring and stop claiming saved status for an unsaved draft","invocations":1,"location":null,"numPassingAsserts":6,"retryReasons":[],"startAt":1793772748241,"status":"passed","title":"nb section indicators retain server scoring and stop claiming saved status for an unsaved draft"},{"ancestorTitles":[],"duration":157,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"the parent exposes failed import-read retry without canonical saves or history restores","invocations":1,"location":null,"numPassingAsserts":6,"retryReasons":[],"startAt":1793772748616,"status":"passed","title":"the parent exposes failed import-read retry without canonical saves or history restores"},{"ancestorTitles":[],"duration":715,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"changing language in overview cannot refetch or overwrite a dirty canonical draft","invocations":1,"location":null,"numPassingAsserts":4,"retryReasons":[],"startAt":1793772748773,"status":"passed","title":"changing language in overview cannot refetch or overwrite a dirty canonical draft"},{"ancestorTitles":[],"duration":466,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"visited evidence keeps its draft across sections without an unvisited or repeated list request","invocations":1,"location":null,"numPassingAsserts":8,"retryReasons":[],"startAt":1793772749489,"status":"passed","title":"visited evidence keeps its draft across sections without an unvisited or repeated list request"}],"endTime":1793772749955,"message":"","name":"C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\src\\career-workspace-page.test.tsx","startTime":1793772738288,"status":"passed","summary":""},{"assertionResults":[{"ancestorTitles":[],"duration":164,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"A1 week glance shows recorded upcoming dates from attention data without inventing missing dates","invocations":1,"location":null,"numPassingAsserts":3,"retryReasons":[],"startAt":1793772750808,"status":"passed","title":"A1 week glance shows recorded upcoming dates from attention data without inventing missing dates"},{"ancestorTitles":[],"duration":157,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"[PRES-S01] exact monthly values, sample, custom statuses and separate salary group remain readable","invocations":1,"location":null,"numPassingAsserts":7,"retryReasons":[],"startAt":1793772750973,"status":"passed","title":"[PRES-S01] exact monthly values, sample, custom statuses and separate salary group remain readable"},{"ancestorTitles":[],"duration":819,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"[PRES-S02] all widget visibility choices save and survive remount","invocations":1,"location":null,"numPassingAsserts":8,"retryReasons":[],"startAt":1793772751130,"status":"passed","title":"[PRES-S02] all widget visibility choices save and survive remount"},{"ancestorTitles":[],"duration":201,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"[PRES-S01] a trends failure retains summary and retries without showing a false zero response rate","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1793772751949,"status":"passed","title":"[PRES-S01] a trends failure retains summary and retries without showing a false zero response rate"},{"ancestorTitles":[],"duration":62,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"[PRES-S01] a summary failure does not suppress independent trends","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1793772752150,"status":"passed","title":"[PRES-S01] a summary failure does not suppress independent trends"},{"ancestorTitles":[],"duration":225,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"[PRES-J05 J09 S01] raw status and company destinations use supported exact query fields","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1793772752212,"status":"passed","title":"[PRES-J05 J09 S01] raw status and company destinations use supported exact query fields"},{"ancestorTitles":[],"duration":166,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"[PRES-S01 S02] period changes only reload trends and retain current snapshot meaning","invocations":1,"location":null,"numPassingAsserts":3,"retryReasons":[],"startAt":1793772752437,"status":"passed","title":"[PRES-S01 S02] period changes only reload trends and retain current snapshot meaning"},{"ancestorTitles":[],"duration":46,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"[PRES-S01] zero activity is an explicit window-empty state without a fabricated response percentage","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1793772752603,"status":"passed","title":"[PRES-S01] zero activity is an explicit window-empty state without a fabricated response percentage"},{"ancestorTitles":[],"duration":77,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"[PRES-S01] activity chart uses one shared zero-based scale and a readable legend","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1793772752649,"status":"passed","title":"[PRES-S01] activity chart uses one shared zero-based scale and a readable legend"},{"ancestorTitles":[],"duration":84,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"[PRES-S01] a single recorded month has visible markers on the shared scale and retains exact values","invocations":1,"location":null,"numPassingAsserts":8,"retryReasons":[],"startAt":1793772752726,"status":"passed","title":"[PRES-S01] a single recorded month has visible markers on the shared scale and retains exact values"},{"ancestorTitles":[],"duration":140,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"[P7-02 PRES-S01 S02] setup guidance treats pending and failed summaries as unknown until their owner retries","invocations":1,"location":null,"numPassingAsserts":5,"retryReasons":[],"startAt":1793772752810,"status":"passed","title":"[P7-02 PRES-S01 S02] setup guidance treats pending and failed summaries as unknown until their owner retries"}],"endTime":1793772752950,"message":"","name":"C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\src\\dashboard-presentation.test.tsx","startTime":1793772749966,"status":"passed","summary":""},{"assertionResults":[{"ancestorTitles":[],"duration":16,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"the first history read announces progress without claiming an empty history","invocations":1,"location":null,"numPassingAsserts":5,"retryReasons":[],"startAt":1793772753597,"status":"passed","title":"the first history read announces progress without claiming an empty history"},{"ancestorTitles":[],"duration":74,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"a failed history read retains known rows and retries only through its history owner","invocations":1,"location":null,"numPassingAsserts":12,"retryReasons":[],"startAt":1793772753614,"status":"passed","title":"a failed history read retains known rows and retries only through its history owner"},{"ancestorTitles":[],"duration":8,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"a settled zero-version history is a named empty section rather than a hidden capability","invocations":1,"location":null,"numPassingAsserts":4,"retryReasons":[],"startAt":1793772753688,"status":"passed","title":"a settled zero-version history is a named empty section rather than a hidden capability"},{"ancestorTitles":[],"duration":7,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"one current version remains visible in Bokmål without offering a restore of the current source","invocations":1,"location":null,"numPassingAsserts":4,"retryReasons":[],"startAt":1793772753696,"status":"passed","title":"one current version remains visible in Bokmål without offering a restore of the current source"},{"ancestorTitles":[],"duration":59,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"the collapsed history keeps twelve rows and contextual restore actions pass the exact selected version","invocations":1,"location":null,"numPassingAsserts":9,"retryReasons":[],"startAt":1793772753703,"status":"passed","title":"the collapsed history keeps twelve rows and contextual restore actions pass the exact selected version"},{"ancestorTitles":[],"duration":36,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"dirty source state explains the restore guard and re-enables the same version after the parent clears it","invocations":1,"location":null,"numPassingAsserts":7,"retryReasons":[],"startAt":1793772753762,"status":"passed","title":"dirty source state explains the restore guard and re-enables the same version after the parent clears it"},{"ancestorTitles":[],"duration":28,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"the existing overall loading guard still blocks history restores","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1793772753798,"status":"passed","title":"the existing overall loading guard still blocks history restores"},{"ancestorTitles":[],"duration":37,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"restore failure stays visible with the known history until the parent clears it and retry is explicit","invocations":1,"location":null,"numPassingAsserts":10,"retryReasons":[],"startAt":1793772753827,"status":"passed","title":"restore failure stays visible with the known history until the parent clears it and retry is explicit"}],"endTime":1793772753865,"message":"","name":"C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\src\\career-history-view.test.tsx","startTime":1793772752958,"status":"passed","summary":""},{"assertionResults":[{"ancestorTitles":[],"duration":1,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"preserves unknown completeness label constructor as data","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1793772754072,"status":"passed","title":"preserves unknown completeness label constructor as data"},{"ancestorTitles":[],"duration":0,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"preserves unknown completeness label toString as data","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1793772754073,"status":"passed","title":"preserves unknown completeness label toString as data"},{"ancestorTitles":[],"duration":0,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"preserves unknown completeness label __proto__ as data","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1793772754073,"status":"passed","title":"preserves unknown completeness label __proto__ as data"},{"ancestorTitles":[],"duration":1,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"preserves unknown completeness label Custom section as data","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1793772754073,"status":"passed","title":"preserves unknown completeness label Custom section as data"},{"ancestorTitles":[],"duration":0,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"translates a known completeness label using its catalogue key","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1793772754074,"status":"passed","title":"translates a known completeness label using its catalogue key"}],"endTime":1793772754074,"message":"","name":"C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\src\\career-completeness-labels.test.ts","startTime":1793772753871,"status":"passed","summary":""}],"wasInterrupted":false}
diff --git a/docs/ui-modernisation/visual-correction/DOCUMENT_FLAT_TESTS.json b/docs/ui-modernisation/visual-correction/DOCUMENT_FLAT_TESTS.json
new file mode 100644
index 00000000..2cfd4c4f
--- /dev/null
+++ b/docs/ui-modernisation/visual-correction/DOCUMENT_FLAT_TESTS.json
@@ -0,0 +1 @@
+{"numFailedTestSuites":0,"numFailedTests":0,"numPassedTestSuites":1,"numPassedTests":33,"numPendingTestSuites":0,"numPendingTests":0,"numRuntimeErrorTestSuites":0,"numTodoTests":0,"numTotalTestSuites":1,"numTotalTests":33,"openHandles":[],"snapshot":{"added":0,"didUpdate":false,"failure":false,"filesAdded":0,"filesRemoved":0,"filesRemovedList":[],"filesUnmatched":0,"filesUpdated":0,"matched":0,"total":0,"unchecked":0,"uncheckedKeysByFile":[],"unmatched":0,"updated":0},"startTime":1793772792177,"success":true,"testResults":[{"assertionResults":[{"ancestorTitles":["en unknown attachment purposes"],"duration":257,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"en unknown attachment purposes uses the localized Other label for constructor","invocations":1,"location":null,"numPassingAsserts":5,"retryReasons":[],"startAt":1793772795992,"status":"passed","title":"uses the localized Other label for constructor"},{"ancestorTitles":["en unknown attachment purposes"],"duration":108,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"en unknown attachment purposes uses the localized Other label for toString","invocations":1,"location":null,"numPassingAsserts":5,"retryReasons":[],"startAt":1793772796249,"status":"passed","title":"uses the localized Other label for toString"},{"ancestorTitles":["en unknown attachment purposes"],"duration":111,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"en unknown attachment purposes uses the localized Other label for __proto__","invocations":1,"location":null,"numPassingAsserts":5,"retryReasons":[],"startAt":1793772796357,"status":"passed","title":"uses the localized Other label for __proto__"},{"ancestorTitles":["nb unknown attachment purposes"],"duration":124,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"nb unknown attachment purposes uses the localized Other label for constructor","invocations":1,"location":null,"numPassingAsserts":5,"retryReasons":[],"startAt":1793772796468,"status":"passed","title":"uses the localized Other label for constructor"},{"ancestorTitles":["nb unknown attachment purposes"],"duration":122,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"nb unknown attachment purposes uses the localized Other label for toString","invocations":1,"location":null,"numPassingAsserts":5,"retryReasons":[],"startAt":1793772796592,"status":"passed","title":"uses the localized Other label for toString"},{"ancestorTitles":["nb unknown attachment purposes"],"duration":125,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"nb unknown attachment purposes uses the localized Other label for __proto__","invocations":1,"location":null,"numPassingAsserts":5,"retryReasons":[],"startAt":1793772796714,"status":"passed","title":"uses the localized Other label for __proto__"},{"ancestorTitles":["en uploaded wall-clock dates"],"duration":108,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"en uploaded wall-clock dates does not invent a timestamp from 2026-02-30T09:15:00","invocations":1,"location":null,"numPassingAsserts":6,"retryReasons":[],"startAt":1793772796839,"status":"passed","title":"does not invent a timestamp from 2026-02-30T09:15:00"},{"ancestorTitles":["en uploaded wall-clock dates"],"duration":77,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"en uploaded wall-clock dates does not invent a timestamp from 2026-02-12T24:15:00","invocations":1,"location":null,"numPassingAsserts":6,"retryReasons":[],"startAt":1793772796947,"status":"passed","title":"does not invent a timestamp from 2026-02-12T24:15:00"},{"ancestorTitles":["en uploaded wall-clock dates"],"duration":116,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"en uploaded wall-clock dates does not invent a timestamp from 2026-02-12T09:61:00","invocations":1,"location":null,"numPassingAsserts":6,"retryReasons":[],"startAt":1793772797024,"status":"passed","title":"does not invent a timestamp from 2026-02-12T09:61:00"},{"ancestorTitles":["en uploaded wall-clock dates"],"duration":114,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"en uploaded wall-clock dates does not invent a timestamp from 2026-02-12T09:15:00junk","invocations":1,"location":null,"numPassingAsserts":6,"retryReasons":[],"startAt":1793772797140,"status":"passed","title":"does not invent a timestamp from 2026-02-12T09:15:00junk"},{"ancestorTitles":["en uploaded wall-clock dates"],"duration":123,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"en uploaded wall-clock dates preserves fractional-second wall-clock metadata without timezone conversion","invocations":1,"location":null,"numPassingAsserts":6,"retryReasons":[],"startAt":1793772797254,"status":"passed","title":"preserves fractional-second wall-clock metadata without timezone conversion"},{"ancestorTitles":["nb uploaded wall-clock dates"],"duration":140,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"nb uploaded wall-clock dates does not invent a timestamp from 2026-02-30T09:15:00","invocations":1,"location":null,"numPassingAsserts":6,"retryReasons":[],"startAt":1793772797378,"status":"passed","title":"does not invent a timestamp from 2026-02-30T09:15:00"},{"ancestorTitles":["nb uploaded wall-clock dates"],"duration":123,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"nb uploaded wall-clock dates does not invent a timestamp from 2026-02-12T24:15:00","invocations":1,"location":null,"numPassingAsserts":6,"retryReasons":[],"startAt":1793772797518,"status":"passed","title":"does not invent a timestamp from 2026-02-12T24:15:00"},{"ancestorTitles":["nb uploaded wall-clock dates"],"duration":122,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"nb uploaded wall-clock dates does not invent a timestamp from 2026-02-12T09:61:00","invocations":1,"location":null,"numPassingAsserts":6,"retryReasons":[],"startAt":1793772797641,"status":"passed","title":"does not invent a timestamp from 2026-02-12T09:61:00"},{"ancestorTitles":["nb uploaded wall-clock dates"],"duration":108,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"nb uploaded wall-clock dates does not invent a timestamp from 2026-02-12T09:15:00junk","invocations":1,"location":null,"numPassingAsserts":6,"retryReasons":[],"startAt":1793772797763,"status":"passed","title":"does not invent a timestamp from 2026-02-12T09:15:00junk"},{"ancestorTitles":["nb uploaded wall-clock dates"],"duration":74,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"nb uploaded wall-clock dates preserves fractional-second wall-clock metadata without timezone conversion","invocations":1,"location":null,"numPassingAsserts":6,"retryReasons":[],"startAt":1793772797871,"status":"passed","title":"preserves fractional-second wall-clock metadata without timezone conversion"},{"ancestorTitles":[],"duration":152,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"en document filters and results support request-free keyboard navigation","invocations":1,"location":null,"numPassingAsserts":8,"retryReasons":[],"startAt":1793772797945,"status":"passed","title":"en document filters and results support request-free keyboard navigation"},{"ancestorTitles":[],"duration":152,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"nb document filters and results support request-free keyboard navigation","invocations":1,"location":null,"numPassingAsserts":8,"retryReasons":[],"startAt":1793772798097,"status":"passed","title":"nb document filters and results support request-free keyboard navigation"},{"ancestorTitles":[],"duration":123,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"[P7-07 documents] validates URL filters and reads one metadata page with literal Nordic search","invocations":1,"location":null,"numPassingAsserts":10,"retryReasons":[],"startAt":1793772798249,"status":"passed","title":"[P7-07 documents] validates URL filters and reads one metadata page with literal Nordic search"},{"ancestorTitles":[],"duration":580,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"[P7-07 documents] server pagination and filter history preserve unknown query, hash and router state","invocations":1,"location":null,"numPassingAsserts":15,"retryReasons":[],"startAt":1793772798372,"status":"passed","title":"[P7-07 documents] server pagination and filter history preserve unknown query, hash and router state"},{"ancestorTitles":[],"duration":219,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"[P7-07 documents] literal search all remains free text and preserves unrelated navigation context","invocations":1,"location":null,"numPassingAsserts":12,"retryReasons":[],"startAt":1793772798952,"status":"passed","title":"[P7-07 documents] literal search all remains free text and preserves unrelated navigation context"},{"ancestorTitles":[],"duration":475,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"[P7-07 documents] a batched sort selection and immediate search preserve both filters and navigation context","invocations":1,"location":null,"numPassingAsserts":23,"retryReasons":[],"startAt":1793772799171,"status":"passed","title":"[P7-07 documents] a batched sort selection and immediate search preserve both filters and navigation context"},{"ancestorTitles":[],"duration":242,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"[P7-07 documents] incompatible pending and failed queries hide old actions and late results cannot replace current metadata","invocations":1,"location":null,"numPassingAsserts":12,"retryReasons":[],"startAt":1793772799646,"status":"passed","title":"[P7-07 documents] incompatible pending and failed queries hide old actions and late results cannot replace current metadata"},{"ancestorTitles":[],"duration":107,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"[P7-07 documents] Retry is one guarded GET and a completion after leaving cannot publish or start follow-up work","invocations":1,"location":null,"numPassingAsserts":10,"retryReasons":[],"startAt":1793772799888,"status":"passed","title":"[P7-07 documents] Retry is one guarded GET and a completion after leaving cannot publish or start follow-up work"},{"ancestorTitles":[],"duration":308,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"[P7-07 documents] a true empty index, filtered no-match and request failure remain distinct","invocations":1,"location":null,"numPassingAsserts":10,"retryReasons":[],"startAt":1793772799995,"status":"passed","title":"[P7-07 documents] a true empty index, filtered no-match and request failure remain distinct"},{"ancestorTitles":[],"duration":215,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"[P7-07 documents] English and Bokmål presentation changes preserve query context without a metadata refetch","invocations":1,"location":null,"numPassingAsserts":11,"retryReasons":[],"startAt":1793772800303,"status":"passed","title":"[P7-07 documents] English and Bokmål presentation changes preserve query context without a metadata refetch"},{"ancestorTitles":[],"duration":256,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"[P7-07 documents] an out-of-range page offers explicit first-page recovery without claiming an empty index","invocations":1,"location":null,"numPassingAsserts":17,"retryReasons":[],"startAt":1793772800518,"status":"passed","title":"[P7-07 documents] an out-of-range page offers explicit first-page recovery without claiming an empty index"},{"ancestorTitles":[],"duration":185,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"[P7-07 documents] seven resource kinds retain exact source actions, frozen provenance and honest date labels","invocations":1,"location":null,"numPassingAsserts":24,"retryReasons":[],"startAt":1793772800774,"status":"passed","title":"[P7-07 documents] seven resource kinds retain exact source actions, frozen provenance and honest date labels"},{"ancestorTitles":[],"duration":169,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"[P7-07 documents] attachment download is explicit, API-relative and single-flight with original MIME, filename and URL disposal","invocations":1,"location":null,"numPassingAsserts":13,"retryReasons":[],"startAt":1793772800959,"status":"passed","title":"[P7-07 documents] attachment download is explicit, API-relative and single-flight with original MIME, filename and URL disposal"},{"ancestorTitles":[],"duration":199,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"[P7-07 documents] a completed download preserves deliberate Search focus instead of restoring the old button","invocations":1,"location":null,"numPassingAsserts":12,"retryReasons":[],"startAt":1793772801128,"status":"passed","title":"[P7-07 documents] a completed download preserves deliberate Search focus instead of restoring the old button"},{"ancestorTitles":[],"duration":291,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"[P7-07 documents] download errors retry only that file and late download completion after leaving creates no URL","invocations":1,"location":null,"numPassingAsserts":11,"retryReasons":[],"startAt":1793772801327,"status":"passed","title":"[P7-07 documents] download errors retry only that file and late download completion after leaving creates no URL"},{"ancestorTitles":[],"duration":104,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"[P7-07 documents] a published historical CV retains its percent-encoded Nordic public slug without fetching its body","invocations":1,"location":null,"numPassingAsserts":8,"retryReasons":[],"startAt":1793772801618,"status":"passed","title":"[P7-07 documents] a published historical CV retains its percent-encoded Nordic public slug without fetching its body"},{"ancestorTitles":[],"duration":95,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"[P7-07 documents] malformed external and mismatched resource paths never become source, public or download actions","invocations":1,"location":null,"numPassingAsserts":15,"retryReasons":[],"startAt":1793772801722,"status":"passed","title":"[P7-07 documents] malformed external and mismatched resource paths never become source, public or download actions"}],"endTime":1793772801819,"message":"","name":"C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\src\\documents-page.test.tsx","startTime":1793772793122,"status":"passed","summary":""}],"wasInterrupted":false}
diff --git a/docs/ui-modernisation/visual-correction/DOCUMENT_JUMP_RED.json b/docs/ui-modernisation/visual-correction/DOCUMENT_JUMP_RED.json
new file mode 100644
index 00000000..3bde96d0
--- /dev/null
+++ b/docs/ui-modernisation/visual-correction/DOCUMENT_JUMP_RED.json
@@ -0,0 +1 @@
+{"numFailedTestSuites":1,"numFailedTests":2,"numPassedTestSuites":0,"numPassedTests":0,"numPendingTestSuites":0,"numPendingTests":31,"numRuntimeErrorTestSuites":0,"numTodoTests":0,"numTotalTestSuites":1,"numTotalTests":33,"openHandles":[],"snapshot":{"added":0,"didUpdate":false,"failure":false,"filesAdded":0,"filesRemoved":0,"filesRemovedList":[],"filesUnmatched":0,"filesUpdated":0,"matched":0,"total":0,"unchecked":0,"uncheckedKeysByFile":[],"unmatched":0,"updated":0},"startTime":1793772769479,"success":false,"testResults":[{"assertionResults":[{"ancestorTitles":["en unknown attachment purposes"],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"en unknown attachment purposes uses the localized Other label for constructor","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793772772951,"status":"pending","title":"uses the localized Other label for constructor"},{"ancestorTitles":["en unknown attachment purposes"],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"en unknown attachment purposes uses the localized Other label for toString","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793772772951,"status":"pending","title":"uses the localized Other label for toString"},{"ancestorTitles":["en unknown attachment purposes"],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"en unknown attachment purposes uses the localized Other label for __proto__","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793772772951,"status":"pending","title":"uses the localized Other label for __proto__"},{"ancestorTitles":["nb unknown attachment purposes"],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"nb unknown attachment purposes uses the localized Other label for constructor","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793772772951,"status":"pending","title":"uses the localized Other label for constructor"},{"ancestorTitles":["nb unknown attachment purposes"],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"nb unknown attachment purposes uses the localized Other label for toString","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793772772951,"status":"pending","title":"uses the localized Other label for toString"},{"ancestorTitles":["nb unknown attachment purposes"],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"nb unknown attachment purposes uses the localized Other label for __proto__","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793772772951,"status":"pending","title":"uses the localized Other label for __proto__"},{"ancestorTitles":["en uploaded wall-clock dates"],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"en uploaded wall-clock dates does not invent a timestamp from 2026-02-30T09:15:00","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793772772951,"status":"pending","title":"does not invent a timestamp from 2026-02-30T09:15:00"},{"ancestorTitles":["en uploaded wall-clock dates"],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"en uploaded wall-clock dates does not invent a timestamp from 2026-02-12T24:15:00","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793772772951,"status":"pending","title":"does not invent a timestamp from 2026-02-12T24:15:00"},{"ancestorTitles":["en uploaded wall-clock dates"],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"en uploaded wall-clock dates does not invent a timestamp from 2026-02-12T09:61:00","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793772772951,"status":"pending","title":"does not invent a timestamp from 2026-02-12T09:61:00"},{"ancestorTitles":["en uploaded wall-clock dates"],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"en uploaded wall-clock dates does not invent a timestamp from 2026-02-12T09:15:00junk","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793772772951,"status":"pending","title":"does not invent a timestamp from 2026-02-12T09:15:00junk"},{"ancestorTitles":["en uploaded wall-clock dates"],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"en uploaded wall-clock dates preserves fractional-second wall-clock metadata without timezone conversion","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793772772951,"status":"pending","title":"preserves fractional-second wall-clock metadata without timezone conversion"},{"ancestorTitles":["nb uploaded wall-clock dates"],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"nb uploaded wall-clock dates does not invent a timestamp from 2026-02-30T09:15:00","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793772772951,"status":"pending","title":"does not invent a timestamp from 2026-02-30T09:15:00"},{"ancestorTitles":["nb uploaded wall-clock dates"],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"nb uploaded wall-clock dates does not invent a timestamp from 2026-02-12T24:15:00","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793772772951,"status":"pending","title":"does not invent a timestamp from 2026-02-12T24:15:00"},{"ancestorTitles":["nb uploaded wall-clock dates"],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"nb uploaded wall-clock dates does not invent a timestamp from 2026-02-12T09:61:00","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793772772951,"status":"pending","title":"does not invent a timestamp from 2026-02-12T09:61:00"},{"ancestorTitles":["nb uploaded wall-clock dates"],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"nb uploaded wall-clock dates does not invent a timestamp from 2026-02-12T09:15:00junk","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793772772951,"status":"pending","title":"does not invent a timestamp from 2026-02-12T09:15:00junk"},{"ancestorTitles":["nb uploaded wall-clock dates"],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"nb uploaded wall-clock dates preserves fractional-second wall-clock metadata without timezone conversion","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793772772951,"status":"pending","title":"preserves fractional-second wall-clock metadata without timezone conversion"},{"ancestorTitles":[],"duration":293,"failing":false,"failureDetails":[{"matcherResult":{"message":"expect(received).toBeTruthy()\n\nReceived: 0","pass":false}}],"failureMessages":["Error: expect(received).toBeTruthy()\n\nReceived: 0\n at toBeTruthy (C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\src\\documents-page.test.tsx:105:83)"],"fullName":"en document filters and results support request-free keyboard navigation","invocations":1,"location":null,"numPassingAsserts":4,"retryReasons":[],"startAt":1793772772952,"status":"failed","title":"en document filters and results support request-free keyboard navigation"},{"ancestorTitles":[],"duration":127,"failing":false,"failureDetails":[{"matcherResult":{"message":"expect(received).toBeTruthy()\n\nReceived: 0","pass":false}}],"failureMessages":["Error: expect(received).toBeTruthy()\n\nReceived: 0\n at toBeTruthy (C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\src\\documents-page.test.tsx:105:83)"],"fullName":"nb document filters and results support request-free keyboard navigation","invocations":1,"location":null,"numPassingAsserts":4,"retryReasons":[],"startAt":1793772773257,"status":"failed","title":"nb document filters and results support request-free keyboard navigation"},{"ancestorTitles":[],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"[P7-07 documents] validates URL filters and reads one metadata page with literal Nordic search","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793772773384,"status":"pending","title":"[P7-07 documents] validates URL filters and reads one metadata page with literal Nordic search"},{"ancestorTitles":[],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"[P7-07 documents] server pagination and filter history preserve unknown query, hash and router state","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793772773384,"status":"pending","title":"[P7-07 documents] server pagination and filter history preserve unknown query, hash and router state"},{"ancestorTitles":[],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"[P7-07 documents] literal search all remains free text and preserves unrelated navigation context","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793772773384,"status":"pending","title":"[P7-07 documents] literal search all remains free text and preserves unrelated navigation context"},{"ancestorTitles":[],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"[P7-07 documents] a batched sort selection and immediate search preserve both filters and navigation context","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793772773384,"status":"pending","title":"[P7-07 documents] a batched sort selection and immediate search preserve both filters and navigation context"},{"ancestorTitles":[],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"[P7-07 documents] incompatible pending and failed queries hide old actions and late results cannot replace current metadata","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793772773384,"status":"pending","title":"[P7-07 documents] incompatible pending and failed queries hide old actions and late results cannot replace current metadata"},{"ancestorTitles":[],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"[P7-07 documents] Retry is one guarded GET and a completion after leaving cannot publish or start follow-up work","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793772773384,"status":"pending","title":"[P7-07 documents] Retry is one guarded GET and a completion after leaving cannot publish or start follow-up work"},{"ancestorTitles":[],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"[P7-07 documents] a true empty index, filtered no-match and request failure remain distinct","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793772773384,"status":"pending","title":"[P7-07 documents] a true empty index, filtered no-match and request failure remain distinct"},{"ancestorTitles":[],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"[P7-07 documents] English and Bokmål presentation changes preserve query context without a metadata refetch","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793772773384,"status":"pending","title":"[P7-07 documents] English and Bokmål presentation changes preserve query context without a metadata refetch"},{"ancestorTitles":[],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"[P7-07 documents] an out-of-range page offers explicit first-page recovery without claiming an empty index","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793772773384,"status":"pending","title":"[P7-07 documents] an out-of-range page offers explicit first-page recovery without claiming an empty index"},{"ancestorTitles":[],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"[P7-07 documents] seven resource kinds retain exact source actions, frozen provenance and honest date labels","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793772773384,"status":"pending","title":"[P7-07 documents] seven resource kinds retain exact source actions, frozen provenance and honest date labels"},{"ancestorTitles":[],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"[P7-07 documents] attachment download is explicit, API-relative and single-flight with original MIME, filename and URL disposal","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793772773384,"status":"pending","title":"[P7-07 documents] attachment download is explicit, API-relative and single-flight with original MIME, filename and URL disposal"},{"ancestorTitles":[],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"[P7-07 documents] a completed download preserves deliberate Search focus instead of restoring the old button","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793772773384,"status":"pending","title":"[P7-07 documents] a completed download preserves deliberate Search focus instead of restoring the old button"},{"ancestorTitles":[],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"[P7-07 documents] download errors retry only that file and late download completion after leaving creates no URL","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793772773384,"status":"pending","title":"[P7-07 documents] download errors retry only that file and late download completion after leaving creates no URL"},{"ancestorTitles":[],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"[P7-07 documents] a published historical CV retains its percent-encoded Nordic public slug without fetching its body","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793772773384,"status":"pending","title":"[P7-07 documents] a published historical CV retains its percent-encoded Nordic public slug without fetching its body"},{"ancestorTitles":[],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"[P7-07 documents] malformed external and mismatched resource paths never become source, public or download actions","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793772773384,"status":"pending","title":"[P7-07 documents] malformed external and mismatched resource paths never become source, public or download actions"}],"endTime":1793772773397,"message":" ● en document filters and results support request-free keyboard navigation\n\n expect(received).toBeTruthy()\n\n Received: 0\n\n 103 | const jump = screen.getByRole('button', { name: copy('documentsJumpResults', {}, language) });\n 104 | const search = screen.getByRole('textbox', { name: copy('documentsSearch', {}, language) });\n > 105 | expect(jump.compareDocumentPosition(search) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy();\n | ^\n 106 | fireEvent.click(jump);\n 107 | expect(screen.getByRole('region', { name: copy('documentsYourDocuments', {}, language) })).toHaveFocus();\n 108 | fireEvent.click(screen.getByRole('button', { name: copy('documentsReturnFilters', {}, language) }));\n\n at toBeTruthy (src/documents-page.test.tsx:105:83)\n\n ● nb document filters and results support request-free keyboard navigation\n\n expect(received).toBeTruthy()\n\n Received: 0\n\n 103 | const jump = screen.getByRole('button', { name: copy('documentsJumpResults', {}, language) });\n 104 | const search = screen.getByRole('textbox', { name: copy('documentsSearch', {}, language) });\n > 105 | expect(jump.compareDocumentPosition(search) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy();\n | ^\n 106 | fireEvent.click(jump);\n 107 | expect(screen.getByRole('region', { name: copy('documentsYourDocuments', {}, language) })).toHaveFocus();\n 108 | fireEvent.click(screen.getByRole('button', { name: copy('documentsReturnFilters', {}, language) }));\n\n at toBeTruthy (src/documents-page.test.tsx:105:83)\n","name":"C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\src\\documents-page.test.tsx","startTime":1793772770386,"status":"failed","summary":""}],"wasInterrupted":false}
diff --git a/docs/ui-modernisation/visual-correction/evidence/calendar-before/PILOT.json b/docs/ui-modernisation/visual-correction/evidence/calendar-before/PILOT.json
new file mode 100644
index 00000000..3171b831
--- /dev/null
+++ b/docs/ui-modernisation/visual-correction/evidence/calendar-before/PILOT.json
@@ -0,0 +1,74 @@
+{
+ "screens": [
+ {
+ "width": 320,
+ "language": "en",
+ "mode": "light",
+ "state": "agenda",
+ "violations": []
+ },
+ {
+ "width": 320,
+ "language": "en",
+ "mode": "light",
+ "state": "month",
+ "violations": []
+ },
+ {
+ "width": 320,
+ "language": "en",
+ "mode": "light",
+ "state": "linked-application",
+ "violations": []
+ }
+ ],
+ "journeys": [
+ {
+ "width": 320,
+ "language": "en",
+ "mode": "light",
+ "agendaMonthSelectionPersistedPreferenceBoundedMonthNavigationLinkedDeadline": "PASS"
+ }
+ ],
+ "errors": [],
+ "unexpectedWrites": [],
+ "reads": [
+ {
+ "width": 320,
+ "language": "en",
+ "mode": "light",
+ "from": "2026-11-01",
+ "to": "2026-11-30"
+ },
+ {
+ "width": 320,
+ "language": "en",
+ "mode": "light",
+ "from": "2026-12-01",
+ "to": "2026-12-31"
+ },
+ {
+ "width": 320,
+ "language": "en",
+ "mode": "light",
+ "from": "2026-11-01",
+ "to": "2026-11-30"
+ },
+ {
+ "width": 320,
+ "language": "en",
+ "mode": "light",
+ "from": "2026-11-01",
+ "to": "2026-11-30"
+ }
+ ],
+ "blockedExternal": [
+ "https://fonts.googleapis.com",
+ "https://fonts.googleapis.com"
+ ],
+ "limits": [
+ "Controlled API fixtures, not a live backend/provider or reminder delivery.",
+ "Date-level recorded deadline/follow-up display; no fabricated interview appointments or source timezone reconstruction.",
+ "No native-device, physical rendering, final security or release qualification."
+ ]
+}
\ No newline at end of file
diff --git a/docs/ui-modernisation/visual-correction/evidence/calendar-before/agenda-en-light-320.png b/docs/ui-modernisation/visual-correction/evidence/calendar-before/agenda-en-light-320.png
new file mode 100644
index 00000000..8294459f
Binary files /dev/null and b/docs/ui-modernisation/visual-correction/evidence/calendar-before/agenda-en-light-320.png differ
diff --git a/docs/ui-modernisation/visual-correction/evidence/calendar-before/linked-application-en-light-320.png b/docs/ui-modernisation/visual-correction/evidence/calendar-before/linked-application-en-light-320.png
new file mode 100644
index 00000000..a09fb043
Binary files /dev/null and b/docs/ui-modernisation/visual-correction/evidence/calendar-before/linked-application-en-light-320.png differ
diff --git a/docs/ui-modernisation/visual-correction/evidence/calendar-before/month-en-light-320.png b/docs/ui-modernisation/visual-correction/evidence/calendar-before/month-en-light-320.png
new file mode 100644
index 00000000..c3e9ffb2
Binary files /dev/null and b/docs/ui-modernisation/visual-correction/evidence/calendar-before/month-en-light-320.png differ
diff --git a/docs/ui-modernisation/visual-correction/evidence/career-before/PILOT.json b/docs/ui-modernisation/visual-correction/evidence/career-before/PILOT.json
new file mode 100644
index 00000000..bb2f5d91
--- /dev/null
+++ b/docs/ui-modernisation/visual-correction/evidence/career-before/PILOT.json
@@ -0,0 +1,99 @@
+{
+ "screens": [
+ {
+ "width": 320,
+ "language": "en",
+ "mode": "light",
+ "state": "overview",
+ "violations": []
+ },
+ {
+ "width": 320,
+ "language": "en",
+ "mode": "light",
+ "state": "profile",
+ "violations": []
+ },
+ {
+ "width": 320,
+ "language": "en",
+ "mode": "light",
+ "state": "import-review",
+ "violations": []
+ },
+ {
+ "width": 320,
+ "language": "en",
+ "mode": "light",
+ "state": "library",
+ "violations": []
+ },
+ {
+ "width": 320,
+ "language": "en",
+ "mode": "light",
+ "state": "cv-editor",
+ "violations": []
+ },
+ {
+ "width": 320,
+ "language": "en",
+ "mode": "light",
+ "state": "letter",
+ "violations": []
+ }
+ ],
+ "journeys": [
+ {
+ "width": 320,
+ "language": "en",
+ "mode": "light",
+ "all14KeyboardEditTargets": "PASS",
+ "canonicalSaveStableIds": "PASS",
+ "legacyImportComparisonSelectionNoApply": "PASS",
+ "libraryKeyboardOpen": "PASS",
+ "cvResponsiveRename": "PASS",
+ "letterResponsiveManualSave": "PASS"
+ }
+ ],
+ "errors": [],
+ "unexpectedWrites": [],
+ "mockedWrites": [
+ {
+ "width": 320,
+ "language": "en",
+ "mode": "light",
+ "action": "canonicalProjectSave"
+ },
+ {
+ "width": 320,
+ "language": "en",
+ "mode": "light",
+ "action": "variantRename"
+ },
+ {
+ "width": 320,
+ "language": "en",
+ "mode": "light",
+ "action": "manualLetterSave"
+ }
+ ],
+ "renderReads": 1,
+ "unavailableReads": [
+ "/api/companies",
+ "/api/companies",
+ "/api/cv/themes/modern/preview",
+ "/api/jobapplications/42/cv",
+ "/api/companies"
+ ],
+ "blockedExternal": [
+ "https://fonts.googleapis.com",
+ "https://fonts.googleapis.com",
+ "https://fonts.googleapis.com"
+ ],
+ "limits": [
+ "All API and preview responses are controlled fixtures; not backend end-to-end or physical PDF qualification.",
+ "Legacy import comparison and selection only; no parser, model, import acceptance or source mutation.",
+ "No native device, screen reader, provider, integrated security, deployment or release acceptance."
+ ]
+}
\ No newline at end of file
diff --git a/docs/ui-modernisation/visual-correction/evidence/career-before/cv-editor-en-light-320.png b/docs/ui-modernisation/visual-correction/evidence/career-before/cv-editor-en-light-320.png
new file mode 100644
index 00000000..24ca9f6f
Binary files /dev/null and b/docs/ui-modernisation/visual-correction/evidence/career-before/cv-editor-en-light-320.png differ
diff --git a/docs/ui-modernisation/visual-correction/evidence/career-before/import-review-en-light-320.png b/docs/ui-modernisation/visual-correction/evidence/career-before/import-review-en-light-320.png
new file mode 100644
index 00000000..24133529
Binary files /dev/null and b/docs/ui-modernisation/visual-correction/evidence/career-before/import-review-en-light-320.png differ
diff --git a/docs/ui-modernisation/visual-correction/evidence/career-before/letter-en-light-320.png b/docs/ui-modernisation/visual-correction/evidence/career-before/letter-en-light-320.png
new file mode 100644
index 00000000..833b906e
Binary files /dev/null and b/docs/ui-modernisation/visual-correction/evidence/career-before/letter-en-light-320.png differ
diff --git a/docs/ui-modernisation/visual-correction/evidence/career-before/library-en-light-320.png b/docs/ui-modernisation/visual-correction/evidence/career-before/library-en-light-320.png
new file mode 100644
index 00000000..749cbcd5
Binary files /dev/null and b/docs/ui-modernisation/visual-correction/evidence/career-before/library-en-light-320.png differ
diff --git a/docs/ui-modernisation/visual-correction/evidence/career-before/overview-en-light-320.png b/docs/ui-modernisation/visual-correction/evidence/career-before/overview-en-light-320.png
new file mode 100644
index 00000000..ba99c13a
Binary files /dev/null and b/docs/ui-modernisation/visual-correction/evidence/career-before/overview-en-light-320.png differ
diff --git a/docs/ui-modernisation/visual-correction/evidence/career-before/profile-en-light-320.png b/docs/ui-modernisation/visual-correction/evidence/career-before/profile-en-light-320.png
new file mode 100644
index 00000000..5e206643
Binary files /dev/null and b/docs/ui-modernisation/visual-correction/evidence/career-before/profile-en-light-320.png differ
diff --git a/docs/ui-modernisation/visual-correction/evidence/documents-inbox-before/PILOT.json b/docs/ui-modernisation/visual-correction/evidence/documents-inbox-before/PILOT.json
new file mode 100644
index 00000000..d3fed53f
--- /dev/null
+++ b/docs/ui-modernisation/visual-correction/evidence/documents-inbox-before/PILOT.json
@@ -0,0 +1,81 @@
+{
+ "screens": [
+ {
+ "width": 320,
+ "language": "en",
+ "mode": "light",
+ "state": "documents",
+ "violations": []
+ },
+ {
+ "width": 320,
+ "language": "en",
+ "mode": "light",
+ "state": "document-filtered",
+ "violations": []
+ },
+ {
+ "width": 320,
+ "language": "en",
+ "mode": "light",
+ "state": "inbox",
+ "violations": []
+ },
+ {
+ "width": 320,
+ "language": "en",
+ "mode": "light",
+ "state": "message",
+ "violations": []
+ },
+ {
+ "width": 320,
+ "language": "en",
+ "mode": "light",
+ "state": "send-confirmation",
+ "violations": []
+ },
+ {
+ "width": 320,
+ "language": "en",
+ "mode": "light",
+ "state": "uncertain-send",
+ "violations": []
+ }
+ ],
+ "journeys": [
+ {
+ "width": 320,
+ "language": "en",
+ "mode": "light",
+ "documentFilterFocusDownloadSource": "PASS",
+ "inboxFocusPlainTextReplySaveExplicitConfirmUncertainNoRetry": "PASS"
+ }
+ ],
+ "errors": [],
+ "unexpectedWrites": [],
+ "mockedWrites": [
+ {
+ "width": 320,
+ "language": "en",
+ "mode": "light",
+ "action": "saveDraft"
+ },
+ {
+ "width": 320,
+ "language": "en",
+ "mode": "light",
+ "action": "confirmedSendFixtureUncertain"
+ }
+ ],
+ "unavailableReads": [],
+ "blockedExternal": [
+ "https://fonts.googleapis.com",
+ "https://fonts.googleapis.com"
+ ],
+ "limits": [
+ "Synthetic metadata/binary/mail/provider fixtures; not live storage, mailbox, OAuth or delivery.",
+ "Browser covers draft save, explicit confirmation and controlled uncertain outcome only; other recovery/conflict paths remain targeted tests.",
+ "No real sending, native device, final integrated security or release acceptance."
+ ]
+}
\ No newline at end of file
diff --git a/docs/ui-modernisation/visual-correction/evidence/documents-inbox-before/document-filtered-en-light-320.png b/docs/ui-modernisation/visual-correction/evidence/documents-inbox-before/document-filtered-en-light-320.png
new file mode 100644
index 00000000..d287b642
Binary files /dev/null and b/docs/ui-modernisation/visual-correction/evidence/documents-inbox-before/document-filtered-en-light-320.png differ
diff --git a/docs/ui-modernisation/visual-correction/evidence/documents-inbox-before/documents-en-light-320.png b/docs/ui-modernisation/visual-correction/evidence/documents-inbox-before/documents-en-light-320.png
new file mode 100644
index 00000000..50acf769
Binary files /dev/null and b/docs/ui-modernisation/visual-correction/evidence/documents-inbox-before/documents-en-light-320.png differ
diff --git a/docs/ui-modernisation/visual-correction/evidence/documents-inbox-before/inbox-en-light-320.png b/docs/ui-modernisation/visual-correction/evidence/documents-inbox-before/inbox-en-light-320.png
new file mode 100644
index 00000000..e0eaf978
Binary files /dev/null and b/docs/ui-modernisation/visual-correction/evidence/documents-inbox-before/inbox-en-light-320.png differ
diff --git a/docs/ui-modernisation/visual-correction/evidence/documents-inbox-before/message-en-light-320.png b/docs/ui-modernisation/visual-correction/evidence/documents-inbox-before/message-en-light-320.png
new file mode 100644
index 00000000..578cecca
Binary files /dev/null and b/docs/ui-modernisation/visual-correction/evidence/documents-inbox-before/message-en-light-320.png differ
diff --git a/docs/ui-modernisation/visual-correction/evidence/documents-inbox-before/send-confirmation-en-light-320.png b/docs/ui-modernisation/visual-correction/evidence/documents-inbox-before/send-confirmation-en-light-320.png
new file mode 100644
index 00000000..a1c35a9d
Binary files /dev/null and b/docs/ui-modernisation/visual-correction/evidence/documents-inbox-before/send-confirmation-en-light-320.png differ
diff --git a/docs/ui-modernisation/visual-correction/evidence/documents-inbox-before/uncertain-send-en-light-320.png b/docs/ui-modernisation/visual-correction/evidence/documents-inbox-before/uncertain-send-en-light-320.png
new file mode 100644
index 00000000..92491188
Binary files /dev/null and b/docs/ui-modernisation/visual-correction/evidence/documents-inbox-before/uncertain-send-en-light-320.png differ
diff --git a/job-tracker-ui/scripts/verify-a3-calendar.mjs b/job-tracker-ui/scripts/verify-a3-calendar.mjs
index 358e1309..f42728a5 100644
--- a/job-tracker-ui/scripts/verify-a3-calendar.mjs
+++ b/job-tracker-ui/scripts/verify-a3-calendar.mjs
@@ -7,12 +7,15 @@ import path from 'node:path';
import assert from 'node:assert/strict';
const compiled = ts.transpileModule(fs.readFileSync(new URL('../src/i18n/translations.ts', import.meta.url), 'utf8'), { compilerOptions: { module: ts.ModuleKind.ESNext } }).outputText;
const { translations } = await import(`data:text/javascript;base64,${Buffer.from(compiled).toString('base64')}`);
-const out = path.resolve(import.meta.dirname, '../../docs/ui-modernisation/evidence/calendar/browser'); fs.mkdirSync(out, { recursive: true });
+const visualMatrix = process.env.A3_VISUAL_MATRIX === '1';
+const widths = process.env.A3_PILOT ? [320] : visualMatrix ? [320, 360, 390, 430, 768, 1024, 1280, 1440, 1920] : [320, 390, 768, 1440];
+const expectedJourneys = widths.length * (process.env.A3_PILOT ? 1 : 4);
+const out = path.resolve(import.meta.dirname, visualMatrix ? '../../docs/ui-modernisation/visual-correction/evidence/calendar' : '../../docs/ui-modernisation/evidence/calendar/browser'); fs.mkdirSync(out, { recursive: true });
const report = { screens: [], journeys: [], errors: [], unexpectedWrites: [], reads: [], blockedExternal: [] };
const now = new Date(), month = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}`;
const browser = await chromium.launch();
try {
- for (const width of (process.env.A3_PILOT ? [320] : [320, 390, 768, 1440])) for (const language of (process.env.A3_PILOT ? ['en'] : ['en', 'nb'])) for (const mode of (process.env.A3_PILOT ? ['light'] : ['light', 'dark'])) {
+ for (const width of widths) for (const language of (process.env.A3_PILOT ? ['en'] : ['en', 'nb'])) for (const mode of (process.env.A3_PILOT ? ['light'] : ['light', 'dark'])) {
console.log(`Checking ${width}/${language}/${mode}`);
const t = (key, params = {}) => translations[language][key].replace(/\{(\w+)\}/g, (_, name) => String(params[name] ?? `{${name}}`));
const context = await browser.newContext({ viewport: { width, height: 1000 }, reducedMotion: 'reduce' }); const page = await context.newPage();
@@ -39,7 +42,7 @@ try {
const scan = async state => {
assert.equal(await page.evaluate(() => document.documentElement.scrollWidth > innerWidth + 1), false, state);
const axe = await new AxeBuilder({ page }).withTags(['wcag2a', 'wcag2aa', 'wcag21aa', 'wcag22aa']).analyze(); report.screens.push({ width, language, mode, state, violations: axe.violations }); assert.deepEqual(axe.violations, []);
- await page.evaluate(() => window.scrollTo(0, 0)); await page.screenshot({ path: path.join(out, `${state}-${language}-${mode}-${width}.png`), fullPage: true });
+ await page.evaluate(() => window.scrollTo(0, 0)); await page.screenshot({ path: path.join(out, `${state}-${language}-${mode}-${width}.png`), fullPage: width >= 768 });
};
await page.goto('http://localhost:4338/reminders');
const calendar = page.getByRole('region', { name: t('calendarAgendaTitle'), exact: true }); await calendar.getByRole('link', { name: /Electrical engineer/ }).waitFor();
@@ -61,6 +64,7 @@ try {
assert.equal((await page.locator('main').innerText()).includes('Invalid Date'), false); await scan('linked-application');
report.journeys.push({ width, language, mode, agendaMonthSelectionPersistedPreferenceBoundedMonthNavigationLinkedDeadline: 'PASS' }); await context.close();
}
+ assert.equal(report.journeys.length, expectedJourneys); assert.equal(report.screens.length, expectedJourneys * 3);
assert.deepEqual(report.errors, []); assert.deepEqual(report.unexpectedWrites, []); assert.ok(report.blockedExternal.every(origin => origin === 'https://fonts.googleapis.com'));
console.log(`PASS:${report.journeys.length} calendar journeys,${report.screens.length} axe scans/captures,zero writes.`);
} finally { fs.writeFileSync(path.join(out, process.env.A3_PILOT ? 'PILOT.json' : 'BROWSER.json'), JSON.stringify({ ...report, limits: ['Controlled API fixtures, not a live backend/provider or reminder delivery.', 'Date-level recorded deadline/follow-up display; no fabricated interview appointments or source timezone reconstruction.', 'No native-device, physical rendering, final security or release qualification.'] }, null, 2)); await browser.close(); }
diff --git a/job-tracker-ui/scripts/verify-a3-career.mjs b/job-tracker-ui/scripts/verify-a3-career.mjs
index 58f1a493..0e18a4d1 100644
--- a/job-tracker-ui/scripts/verify-a3-career.mjs
+++ b/job-tracker-ui/scripts/verify-a3-career.mjs
@@ -7,12 +7,13 @@ import fs from 'node:fs';
import path from 'node:path';
const compiled = ts.transpileModule(fs.readFileSync(new URL('../src/i18n/translations.ts', import.meta.url), 'utf8'), { compilerOptions: { module: ts.ModuleKind.ESNext } }).outputText;
const { translations } = await import(`data:text/javascript;base64,${Buffer.from(compiled).toString('base64')}`);
-const out = path.resolve(import.meta.dirname, '../../docs/ui-modernisation/evidence/career/browser');
+const visualMatrix = process.env.A3_VISUAL_MATRIX === '1';
+const out = path.resolve(import.meta.dirname, visualMatrix ? '../../docs/ui-modernisation/visual-correction/evidence/career' : '../../docs/ui-modernisation/evidence/career/browser');
fs.mkdirSync(out, { recursive: true });
const report = { screens: [], journeys: [], errors: [], unexpectedWrites: [], mockedWrites: [], renderReads: 0, unavailableReads: [], blockedExternal: [] };
const browser = await chromium.launch();
try {
- const configurations = process.env.A3_BOUNDARY ? [320, 390, 768, 1000, 1001, 1440].map(width => [width, 'en', 'light']) : process.env.A3_PILOT ? [[320, 'en', 'light']] : [320, 390, 768, 1440].flatMap(width => ['en', 'nb'].flatMap(language => ['light', 'dark'].map(mode => [width, language, mode])));
+ const configurations = process.env.A3_BOUNDARY ? [320, 390, 768, 1000, 1001, 1440].map(width => [width, 'en', 'light']) : process.env.A3_PILOT ? [[320, 'en', 'light']] : (visualMatrix ? [320, 360, 390, 430, 768, 1024, 1280, 1440, 1920] : [320, 390, 768, 1440]).flatMap(width => ['en', 'nb'].flatMap(language => ['light', 'dark'].map(mode => [width, language, mode])));
for (const [width, language, mode] of configurations) {
console.log(`Checking ${width}/${language}/${mode}`);
const t = key => { assert.ok(translations[language][key], key); return translations[language][key]; };
@@ -66,11 +67,13 @@ try {
if (Object.hasOwn(fixtures, p)) return answer(fixtures[p]);
report.unavailableReads.push(p); return route.fulfill({ status: 404, contentType: 'application/json', body: '{}' });
});
- const scan = async state => {
+ const scan = async (state, target) => {
+ if (target) await target.evaluate(node => node.scrollIntoView({ block: 'center', behavior: 'instant' }));
+ else await page.evaluate(() => window.scrollTo(0, 0));
assert.equal(await page.evaluate(() => document.documentElement.scrollWidth > innerWidth + 1), false, `overflow:${state}`);
const result = await new AxeBuilder({ page }).withTags(['wcag2a', 'wcag2aa', 'wcag21aa', 'wcag22aa']).analyze();
- report.screens.push({ width, language, mode, state, violations: result.violations }); assert.deepEqual(result.violations, [], `${state} accessibility`);
- await page.evaluate(() => window.scrollTo(0, 0)); await page.screenshot({ path: path.join(out, `${state}-${language}-${mode}-${width}.png`), fullPage: true });
+ report.screens.push({ width, language, mode, state, capture: target ? 'targeted viewport' : width >= 768 ? 'full page at top' : 'viewport at top', violations: result.violations }); assert.deepEqual(result.violations, [], `${state} accessibility`);
+ await page.screenshot({ path: path.join(out, `${state}-${language}-${mode}-${width}.png`), fullPage: width >= 768 && !target });
};
const careerSection = async key => {
if (width >= 600) await page.getByRole('tab', { name: t(key), exact: true }).click();
@@ -81,7 +84,7 @@ try {
for (const key of ['contact', 'summary', 'skills', 'interests', 'awards', 'publications', 'organisations', 'references', 'languages', 'jobs', 'education', 'certifications', 'projects', 'otherSections']) {
await page.locator(`[data-edit-career-section="${key}"]`).press('Enter');
await expect.poll(() => page.locator(`#career-section-${key}`).evaluate(node => node.contains(document.activeElement))).toBe(true);
- if (process.env.A3_BOUNDARY) await expect.poll(async () => page.evaluate(() => {
+ if (process.env.A3_BOUNDARY || visualMatrix) await expect.poll(async () => page.evaluate(() => {
const active = document.activeElement, rect = active.getBoundingClientRect();
const x = Math.min(innerWidth - 1, Math.max(0, rect.x + rect.width / 2)), y = Math.min(innerHeight - 1, Math.max(0, rect.y + rect.height / 2));
const hit = document.elementFromPoint(x, y);
@@ -91,10 +94,10 @@ try {
}
await page.locator('[data-edit-career-section="projects"]').click();
const projectName = page.locator('#career-section-projects input').first(); await projectName.fill('Updated community project');
- await page.getByRole('button', { name: t('profileSaveChanges'), exact: true }).click(); await expect.poll(() => career.revision).toBe(8); await expect(projectName).toBeEnabled(); await scan('profile');
+ await page.getByRole('button', { name: t('profileSaveChanges'), exact: true }).click(); await expect.poll(() => career.revision).toBe(8); await expect(projectName).toBeEnabled(); await scan('profile', projectName);
await careerSection('careerWorkspaceImportTab');
const run = page.locator('#career-import-run-41'); await expect(run).toContainText('A2'); await expect(run).toContainText('B1');
- await run.getByRole('checkbox').check(); await expect(run.getByRole('checkbox')).toBeChecked(); await scan('import-review');
+ await run.getByRole('checkbox').check(); await expect(run.getByRole('checkbox')).toBeChecked(); await scan('import-review', run);
assert.equal(report.mockedWrites.filter(row => row.width === width && row.language === language && row.mode === mode).length, 1);
await page.goto('http://localhost:4338/career/builder');
const cvCard = page.getByRole('link', { name: t('cvDashboardOpenNamed').replace('{name}', 'Teacher CV'), exact: true });
@@ -107,7 +110,7 @@ try {
assert.ok(width > 1000 ? right.x >= left.x + left.width - 2 : right.y >= left.y + left.height - 2, 'CV pane geometry');
}
const cvName = page.getByRole('textbox', { name: t('cvEditorName'), exact: true }); await cvName.fill('Updated teacher CV');
- await expect.poll(() => variant.name, { timeout: 15000 }).toBe('Updated teacher CV'); await scan('cv-editor');
+ await expect.poll(() => variant.name, { timeout: 15000 }).toBe('Updated teacher CV'); await scan('cv-editor', cvName);
await page.goto('http://localhost:4338/jobs/42?section=application&sub=letter');
const text = page.getByRole('textbox', { name: t('jobDetailsCoverLetter'), exact: true }); await expect(text).toBeEnabled();
await text.fill('Dear team\n\nUpdated teaching experience.');
@@ -117,7 +120,7 @@ try {
await expect(letterPreview).toContainText('Updated teaching experience.'); const panes = page.locator('[data-letter-studio-layout] > div'); const left = await panes.nth(0).boundingBox(), right = await panes.nth(1).boundingBox();
assert.ok(width > 1000 ? right.x >= left.x + left.width - 2 : right.y >= left.y + left.height - 2, 'Letter pane geometry');
}
- await page.getByRole('button', { name: t('save'), exact: true }).click(); await expect.poll(() => letter.revision).toBe(8); await expect(text).toBeEnabled(); await scan('letter');
+ await page.getByRole('button', { name: t('save'), exact: true }).click(); await expect.poll(() => letter.revision).toBe(8); await expect(text).toBeEnabled(); await scan('letter', text);
report.journeys.push({ width, language, mode, all14KeyboardEditTargets: 'PASS', canonicalSaveStableIds: 'PASS', legacyImportComparisonSelectionNoApply: 'PASS', libraryKeyboardOpen: 'PASS', cvResponsiveRename: 'PASS', letterResponsiveManualSave: 'PASS' });
await context.close();
}
diff --git a/job-tracker-ui/scripts/verify-a3-dashboard.mjs b/job-tracker-ui/scripts/verify-a3-dashboard.mjs
index 30327b1f..342e8e97 100644
--- a/job-tracker-ui/scripts/verify-a3-dashboard.mjs
+++ b/job-tracker-ui/scripts/verify-a3-dashboard.mjs
@@ -5,11 +5,12 @@ import assert from 'node:assert/strict';
import fs from 'node:fs';
import path from 'node:path';
const visualMatrix = process.env.A3_VISUAL_MATRIX === '1';
+const markerOnly = process.env.A3_ACTIVITY_MARKER === '1';
const widths = process.env.A3_PILOT ? [320] : visualMatrix ? [320, 360, 390, 430, 768, 1024, 1280, 1440, 1920] : [320, 390, 768, 1440];
const languages = process.env.A3_PILOT ? ['en'] : ['en', 'nb'];
const modes = process.env.A3_PILOT ? ['light'] : ['light', 'dark'];
const expectedJourneys = widths.length * languages.length * modes.length;
-const out = path.resolve(import.meta.dirname, visualMatrix ? '../../docs/ui-modernisation/visual-correction/evidence/dashboard' : '../../docs/ui-modernisation/evidence/dashboard/browser');
+const out = path.resolve(import.meta.dirname, markerOnly ? '../../docs/ui-modernisation/visual-correction/evidence/activity-markers' : visualMatrix ? '../../docs/ui-modernisation/visual-correction/evidence/dashboard' : '../../docs/ui-modernisation/evidence/dashboard/browser');
fs.mkdirSync(out, { recursive: true });
const report = { screens: [], journeys: [], errors: [], unexpectedWrites: [], mockedWrites: [], blockedExternal: [] };
const browser = await chromium.launch();
@@ -68,6 +69,18 @@ try {
const a = document.querySelector('[data-dashboard-widget=agenda]').getBoundingClientRect(), b = document.querySelector('[data-dashboard-widget=funnel]').getBoundingClientRect();
return Math.abs(a.top - b.top) < 2 && a.left < b.left;
});
+ if (markerOnly) {
+ const activity = page.locator('[data-dashboard-widget=activity]');
+ await page.waitForFunction(() => document.querySelectorAll('[data-dashboard-widget=activity] svg circle').length === 2);
+ const markers = activity.locator('svg circle');
+ assert.equal(await markers.nth(0).getAttribute('cy'), '20'); assert.equal(await markers.nth(1).getAttribute('cy'), '140');
+ await activity.scrollIntoViewIfNeeded();
+ const panel = activity.getByRole('group'); if (await panel.count()) await panel.evaluate(node => { node.scrollTop = 0; });
+ const scan = await new AxeBuilder({ page }).withTags(['wcag2a', 'wcag2aa', 'wcag21aa', 'wcag22aa']).analyze();
+ report.screens.push({ width, language, mode, state: 'single-month-markers', violations: scan.violations }); assert.deepEqual(scan.violations, []);
+ await page.screenshot({ path: path.join(out, `single-month-${language}-${mode}-${width}.png`) });
+ report.journeys.push({ width, language, mode, recordedSingleMonthMarkersSharedScale: 'PASS' }); await context.close(); continue;
+ }
const scan = await new AxeBuilder({ page }).withTags(['wcag2a', 'wcag2aa', 'wcag21aa', 'wcag22aa']).analyze();
report.screens.push({ width, language, mode, violations: scan.violations }); assert.deepEqual(scan.violations, []);
await page.screenshot({ path: path.join(out, `dashboard-${language}-${mode}-${width}.png`), fullPage: true });
@@ -100,9 +113,9 @@ try {
}
assert.deepEqual(report.errors, []); assert.deepEqual(report.unexpectedWrites, []);
assert.ok(report.blockedExternal.every(origin => origin === 'https://fonts.googleapis.com'));
- assert.equal(report.mockedWrites.length, expectedJourneys);
+ assert.equal(report.mockedWrites.length, markerOnly ? 0 : expectedJourneys);
assert.equal(report.journeys.length, expectedJourneys);
- assert.equal(report.screens.length, expectedJourneys * 2);
+ assert.equal(report.screens.length, expectedJourneys * (markerOnly ? 1 : 2));
console.log(`PASS:${report.journeys.length} dashboard journeys,${report.screens.length} scoped axe sweeps/captures; controlled API persistence only.`);
} finally {
fs.writeFileSync(path.join(out, process.env.A3_PILOT ? 'PILOT.json' : 'BROWSER.json'), JSON.stringify({ ...report, limits: ['synthetic API fixtures, not real backend end-to-end','no native device/screen reader/model/provider/security/release qualification'] }, null, 2));
diff --git a/job-tracker-ui/scripts/verify-a3-documents-inbox.mjs b/job-tracker-ui/scripts/verify-a3-documents-inbox.mjs
index cb24410a..cbfe4bd7 100644
--- a/job-tracker-ui/scripts/verify-a3-documents-inbox.mjs
+++ b/job-tracker-ui/scripts/verify-a3-documents-inbox.mjs
@@ -7,11 +7,14 @@ import path from 'node:path';
import assert from 'node:assert/strict';
const compiled = ts.transpileModule(fs.readFileSync(new URL('../src/i18n/translations.ts', import.meta.url), 'utf8'), { compilerOptions: { module: ts.ModuleKind.ESNext } }).outputText;
const { translations } = await import(`data:text/javascript;base64,${Buffer.from(compiled).toString('base64')}`);
-const out = path.resolve(import.meta.dirname, '../../docs/ui-modernisation/evidence/documents-inbox/browser'); fs.mkdirSync(out, { recursive: true });
+const visualMatrix = process.env.A3_VISUAL_MATRIX === '1';
+const widths = process.env.A3_PILOT ? [320] : visualMatrix ? [320, 360, 390, 430, 768, 1024, 1280, 1440, 1920] : [320, 390, 768, 1440];
+const expectedJourneys = widths.length * (process.env.A3_PILOT ? 1 : 4);
+const out = path.resolve(import.meta.dirname, visualMatrix ? '../../docs/ui-modernisation/visual-correction/evidence/documents-inbox' : '../../docs/ui-modernisation/evidence/documents-inbox/browser'); fs.mkdirSync(out, { recursive: true });
const report = { screens: [], journeys: [], errors: [], unexpectedWrites: [], mockedWrites: [], unavailableReads: [], blockedExternal: [] };
const browser = await chromium.launch();
try {
- for (const width of (process.env.A3_PILOT ? [320] : [320, 390, 768, 1440])) for (const language of (process.env.A3_PILOT ? ['en'] : ['en', 'nb'])) for (const mode of (process.env.A3_PILOT ? ['light'] : ['light', 'dark'])) {
+ for (const width of widths) for (const language of (process.env.A3_PILOT ? ['en'] : ['en', 'nb'])) for (const mode of (process.env.A3_PILOT ? ['light'] : ['light', 'dark'])) {
console.log(`Checking ${width}/${language}/${mode}`);
const t = key => { assert.ok(translations[language][key], key); return translations[language][key]; };
const context = await browser.newContext({ viewport: { width, height: 1000 }, reducedMotion: 'reduce', acceptDownloads: true }); const page = await context.newPage();
@@ -46,10 +49,12 @@ try {
};
if (Object.hasOwn(fixtures, p)) return answer(fixtures[p]); report.unavailableReads.push(p); return route.fulfill({ status: 404, contentType: 'application/json', body: '{}' });
});
- const scan = async state => {
+ const scan = async (state, target) => {
+ if (target) await target.evaluate(node => node.scrollIntoView({ block: 'center', behavior: 'instant' }));
+ else await page.evaluate(() => window.scrollTo(0, 0));
assert.equal(await page.evaluate(() => document.documentElement.scrollWidth > innerWidth + 1), false, state);
- const axe = await new AxeBuilder({ page }).withTags(['wcag2a', 'wcag2aa', 'wcag21aa', 'wcag22aa']).analyze(); report.screens.push({ width, language, mode, state, violations: axe.violations }); assert.deepEqual(axe.violations, []);
- await page.evaluate(() => window.scrollTo(0, 0)); await page.screenshot({ path: path.join(out, `${state}-${language}-${mode}-${width}.png`), fullPage: true });
+ const axe = await new AxeBuilder({ page }).withTags(['wcag2a', 'wcag2aa', 'wcag21aa', 'wcag22aa']).analyze(); report.screens.push({ width, language, mode, state, capture: target ? 'targeted viewport' : width >= 768 ? 'full page at top' : 'viewport at top', violations: axe.violations }); assert.deepEqual(axe.violations, []);
+ await page.screenshot({ path: path.join(out, `${state}-${language}-${mode}-${width}.png`), fullPage: width >= 768 && !target });
};
await page.goto('http://localhost:4338/documents');
const docs = page.getByRole('region', { name: t('documentsYourDocuments'), exact: true }); await docs.getByText(document.name, { exact: true }).waitFor(); await scan('documents');
@@ -57,18 +62,18 @@ try {
await docs.getByRole('button', { name: t('documentsReturnFilters'), exact: true }).click(); const search = page.getByRole('textbox', { name: t('documentsSearch'), exact: true }); await expect(search).toBeFocused(); assert.equal(documentReads, reads);
await search.fill('certificate'); await expect(docs.getByText(cv.name, { exact: true })).toHaveCount(0); await docs.getByText(document.name, { exact: true }).waitFor();
const download = page.waitForEvent('download'); await docs.getByRole('button', { name: t('documentsDownloadAttachment'), exact: true }).click(); const file = await download; assert.equal(file.suggestedFilename(), document.name); assert.equal(fs.readFileSync(await file.path(), 'utf8'), 'Controlled certificate fixture');
- assert.equal(await docs.getByRole('link', { name: t('documentsOpenSource'), exact: true }).getAttribute('href'), document.sourceHref); assert.equal(await docs.locator('time').getAttribute('datetime'), document.recordedAt); await scan('document-filtered');
+ assert.equal(await docs.getByRole('link', { name: t('documentsOpenSource'), exact: true }).getAttribute('href'), document.sourceHref); assert.equal(await docs.locator('time').getAttribute('datetime'), document.recordedAt); await scan('document-filtered', docs.getByText(document.name, { exact: true }));
await page.goto('http://localhost:4338/correspondence'); const messages = page.getByRole('region', { name: t('emailInboxLinkedMessages'), exact: true }); await messages.getByText(mail.subject, { exact: true }).waitFor(); await scan('inbox');
const initialMailReads = mailReads; await page.getByRole('button', { name: t('emailInboxJumpMessages'), exact: true }).press('Enter'); await expect(messages).toBeFocused(); await messages.getByRole('button', { name: t('emailInboxReturnFilters'), exact: true }).click(); await expect(page.getByRole('textbox', { name: t('correspondenceSearch'), exact: true })).toBeFocused(); assert.equal(mailReads, initialMailReads);
- await messages.getByRole('button', { name: t('emailInboxViewMessage'), exact: true }).click(); await messages.getByText('Please choose an interview time. ', { exact: true }).waitFor(); await scan('message');
+ await messages.getByRole('button', { name: t('emailInboxViewMessage'), exact: true }).click(); const messageBody = messages.getByText('Please choose an interview time. ', { exact: true }); await messageBody.waitFor(); await scan('message', messageBody);
await messages.getByRole('button', { name: t('emailInboxReply'), exact: true }).click(); const compose = page.getByRole('region', { name: t('emailInboxDraftTitle'), exact: true });
await compose.getByRole('textbox', { name: t('emailInboxRecipient'), exact: true }).fill('recruiter@example.test'); await compose.getByRole('textbox', { name: t('emailInboxMessage'), exact: true }).fill('Thank you. Please confirm the available interview times.');
await compose.getByRole('button', { name: t('emailInboxSaveDraft'), exact: true }).click(); await expect.poll(() => draft?.revision).toBe(4);
- await compose.getByRole('button', { name: t('emailInboxReviewSend'), exact: true }).click(); const dialog = page.getByRole('dialog', { name: t('emailInboxConfirmSendTitle'), exact: true }); await dialog.waitFor(); assert.equal(sends, 0); await scan('send-confirmation');
+ await compose.getByRole('button', { name: t('emailInboxReviewSend'), exact: true }).click(); const dialog = page.getByRole('dialog', { name: t('emailInboxConfirmSendTitle'), exact: true }); await dialog.waitFor(); assert.equal(sends, 0); await scan('send-confirmation', dialog);
await dialog.getByRole('button', { name: t('emailInboxSendEmail'), exact: true }).click(); await compose.getByText(t('emailInboxDeliveryUncertain'), { exact: true }).waitFor(); await expect(compose.getByRole('button', { name: t('emailInboxReviewSend'), exact: true })).toBeDisabled();
- assert.equal(sends, 1); await expect(compose.getByRole('textbox', { name: t('emailInboxMessage'), exact: true })).toHaveValue(draft.bodyText); await scan('uncertain-send');
+ assert.equal(sends, 1); await expect(compose.getByRole('textbox', { name: t('emailInboxMessage'), exact: true })).toHaveValue(draft.bodyText); await scan('uncertain-send', compose.getByRole('textbox', { name: t('emailInboxMessage'), exact: true }));
report.journeys.push({ width, language, mode, documentFilterFocusDownloadSource: 'PASS', inboxFocusPlainTextReplySaveExplicitConfirmUncertainNoRetry: 'PASS' }); await context.close();
}
- assert.equal(report.screens.length, report.journeys.length * 6); assert.equal(report.mockedWrites.length, report.journeys.length * 2); assert.deepEqual(report.errors, []); assert.deepEqual(report.unexpectedWrites, []); assert.ok(report.blockedExternal.every(origin => origin === 'https://fonts.googleapis.com'));
+ assert.equal(report.journeys.length, expectedJourneys); assert.equal(report.screens.length, report.journeys.length * 6); assert.equal(report.mockedWrites.length, report.journeys.length * 2); assert.deepEqual(report.errors, []); assert.deepEqual(report.unexpectedWrites, []); assert.ok(report.blockedExternal.every(origin => origin === 'https://fonts.googleapis.com'));
console.log(`PASS:${report.journeys.length} document/mail journeys,${report.screens.length} axe scans/captures,${report.mockedWrites.length} fixture-only save/send requests.`);
} finally { fs.writeFileSync(path.join(out, process.env.A3_PILOT ? 'PILOT.json' : 'BROWSER.json'), JSON.stringify({ ...report, limits: ['Synthetic metadata/binary/mail/provider fixtures; not live storage, mailbox, OAuth or delivery.', 'Browser covers draft save, explicit confirmation and controlled uncertain outcome only; other recovery/conflict paths remain targeted tests.', 'No real sending, native device, final integrated security or release acceptance.'] }, null, 2)); await browser.close(); }
diff --git a/job-tracker-ui/src/components/DashboardView.tsx b/job-tracker-ui/src/components/DashboardView.tsx
index 68c6e814..1a541163 100644
--- a/job-tracker-ui/src/components/DashboardView.tsx
+++ b/job-tracker-ui/src/components/DashboardView.tsx
@@ -248,6 +248,10 @@ export default function DashboardView({ mode = "dashboard" }: { mode?: "dashboar
{[0, 0.5, 1].map(fraction => )}
+ {analytics.length === 1 ? <>
+
+
+ > : null}
{monthLabel(analytics[0].month)}
diff --git a/job-tracker-ui/src/dashboard-presentation.test.tsx b/job-tracker-ui/src/dashboard-presentation.test.tsx
index 0a854d54..86dda7f9 100644
--- a/job-tracker-ui/src/dashboard-presentation.test.tsx
+++ b/job-tracker-ui/src/dashboard-presentation.test.tsx
@@ -125,6 +125,23 @@ test('[PRES-S01] activity chart uses one shared zero-based scale and a readable
expect(firstY(paths[0])).toBeLessThan(firstY(paths[1]));
});
+test('[PRES-S01] a single recorded month has visible markers on the shared scale and retains exact values', async () => {
+ const writesBefore = mockedApi.put.mock.calls.length;
+ mockedApi.get.mockImplementation((url: string) => Promise.resolve({ data: url.endsWith('/analytics') ? [monthly[0]] : data(url) }) as any);
+ open();
+ const activity = await screen.findByRole('region', { name: 'Application activity' });
+ const markers = activity.querySelectorAll('svg circle');
+ expect(markers).toHaveLength(2);
+ expect(markers[0]).toHaveAttribute('cy', '20');
+ expect(markers[1]).toHaveAttribute('cy', '140');
+ for (const marker of markers) expect(Number(marker.getAttribute('r'))).toBeGreaterThan(0);
+ fireEvent.click(within(activity).getByText('View monthly values'));
+ const table = within(activity).getByRole('table');
+ expect(within(table).getByRole('cell', { name: '4' })).toBeVisible();
+ expect(within(table).getByRole('cell', { name: '1' })).toBeVisible();
+ expect(mockedApi.put).toHaveBeenCalledTimes(writesBefore);
+});
+
test('[P7-02 PRES-S01 S02] setup guidance treats pending and failed summaries as unknown until their owner retries', async () => {
let resolveStats!: (response: { data: typeof stats }) => void;
let fail = false;
diff --git a/job-tracker-ui/src/documents-page.test.tsx b/job-tracker-ui/src/documents-page.test.tsx
index 9e322e26..66accf22 100644
--- a/job-tracker-ui/src/documents-page.test.tsx
+++ b/job-tracker-ui/src/documents-page.test.tsx
@@ -100,7 +100,10 @@ test.each(['en', 'nb'] as const)('%s document filters and results support reques
localStorage.setItem('uiLanguage', language); renderPage();
await screen.findByText('Metadata CV ÆØÅ', { exact: true });
const count = mockedApi.get.mock.calls.length;
- fireEvent.click(screen.getByRole('button', { name: copy('documentsJumpResults', {}, language) }));
+ const jump = screen.getByRole('button', { name: copy('documentsJumpResults', {}, language) });
+ const search = screen.getByRole('textbox', { name: copy('documentsSearch', {}, language) });
+ expect(jump.compareDocumentPosition(search) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy();
+ fireEvent.click(jump);
expect(screen.getByRole('region', { name: copy('documentsYourDocuments', {}, language) })).toHaveFocus();
fireEvent.click(screen.getByRole('button', { name: copy('documentsReturnFilters', {}, language) }));
expect(screen.getByRole('textbox', { name: copy('documentsSearch', {}, language) })).toHaveFocus();
diff --git a/job-tracker-ui/src/views/CareerProfilePage.tsx b/job-tracker-ui/src/views/CareerProfilePage.tsx
index fd1b9d64..bf879f42 100644
--- a/job-tracker-ui/src/views/CareerProfilePage.tsx
+++ b/job-tracker-ui/src/views/CareerProfilePage.tsx
@@ -1,6 +1,6 @@
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
-import { Accordion, AccordionDetails, AccordionSummary, Alert, Avatar, Box, Button, Checkbox, Chip, FormControl, FormControlLabel, InputLabel, LinearProgress, MenuItem, Paper, Select, Tab, Tabs, TextField, Typography } from "@mui/material";
+import { Accordion, AccordionDetails, AccordionSummary, Alert, Avatar, Box, Button, Checkbox, Chip, FormControl, FormControlLabel, InputLabel, LinearProgress, MenuItem, Select, Tab, Tabs, TextField, Typography } from "@mui/material";
import DeleteOutlineIcon from "@mui/icons-material/DeleteOutline";
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
@@ -522,8 +522,8 @@ export default function CareerProfilePage() {
: change.id === "Contact" && change.confidence === "Low" && change.label === "Contact details" ? t("careerImportContactDetails") : change.label;
return (
-
-
+
+
{t("careerWorkspaceNavigation")}
+
{workspaceSection === "overview" ? (
: null}
- {workspaceSection === "profile" || workspaceSection === "import" ?
+ {workspaceSection === "profile" || workspaceSection === "import" ?
) : null}
- {workspaceSection === "profile" ?
+ {workspaceSection === "profile" ?
{t("careerProfileImportTitle")}
@@ -659,7 +659,7 @@ export default function CareerProfilePage() {
{t("careerProfileImportReady")}
: null}
{!canUseAi ? {t("profileProImportBody")} : null}
- : null}
+ : null}
{workspaceSection === "profile" ?
@@ -843,7 +843,7 @@ export default function CareerProfilePage() {
-
+
{runsLoading ? : null}
{runsLoadError !== null ? void loadExtractionRuns()}>{t("retry")}}>{t("careerOverviewImportUnavailable")} : null}
{latestRun ? (
@@ -1011,7 +1011,7 @@ export default function CareerProfilePage() {
-
+
- : null}
+ : null}
);
}
diff --git a/job-tracker-ui/src/views/DocumentsPage.tsx b/job-tracker-ui/src/views/DocumentsPage.tsx
index caf8bc9c..7c9a43f3 100644
--- a/job-tracker-ui/src/views/DocumentsPage.tsx
+++ b/job-tracker-ui/src/views/DocumentsPage.tsx
@@ -274,7 +274,10 @@ export default function DocumentsPage() {
;
return
- } onClick={refresh} disabled={loading} sx={buttonSx}>{t("documentsRefresh")}} />
+
+
+ } onClick={refresh} disabled={loading} sx={buttonSx}>{t("documentsRefresh")}
+ } />
updateQuery({ q: event.target.value, page: "1" }, true)} slotProps={{ htmlInput: { maxLength: 200 } }} />
@@ -286,7 +289,6 @@ export default function DocumentsPage() {
{jobsError ? { resultsRef.current?.focus(); return loadJobs(); }} /> : null}
{t("documentsDateOrderHelp")}
-
@@ -301,7 +303,7 @@ export default function DocumentsPage() {
{t("documentsName")}{t("documentsJobFilter")}{t("documentsState")}{t("documentsRecordedDate")}{t("documentsActions")}
{items.map((item) => {details(item)}{job(item)}{badges(item)}{date(item)}{actions(item)})}
-
: {items.map((item) => {details(item)}{job(item)}{badges(item)}{date(item)}{actions(item)})}}
+ : {items.map((item) => {details(item)}{job(item)}{badges(item)}{date(item)}{actions(item)})}}
{result.totalPages > 1 ? { resultsRef.current?.focus(); updateQuery({ page: String(next) }); }} aria-label={t("documentsPages")} getItemAriaLabel={(kind, target) => t(kind === "page" ? "documentsGoToPage" : kind === "previous" ? "documentsPreviousPage" : kind === "next" ? "documentsNextPage" : kind === "first" ? "documentsFirstPage" : "documentsLastPage", { page: target ?? 1 })} sx={{ mt: 2, "& .MuiPagination-ul": { flexWrap: "wrap", gap: 0.5 } }} /> : null}
> : null}
diff --git a/job-tracker-ui/src/views/career/CareerWorkspaceOverview.tsx b/job-tracker-ui/src/views/career/CareerWorkspaceOverview.tsx
index 83b7fbbc..56cb59c1 100644
--- a/job-tracker-ui/src/views/career/CareerWorkspaceOverview.tsx
+++ b/job-tracker-ui/src/views/career/CareerWorkspaceOverview.tsx
@@ -1,6 +1,6 @@
import React, { useEffect, useMemo, useRef, useState } from "react";
-import { Alert, Box, Button, Chip, LinearProgress, Paper, Skeleton, Stack, Typography } from "@mui/material";
+import { Alert, Box, Button, Chip, LinearProgress, Skeleton, Stack, Typography } from "@mui/material";
import DescriptionOutlinedIcon from "@mui/icons-material/DescriptionOutlined";
import PersonOutlineIcon from "@mui/icons-material/PersonOutline";
import UploadFileOutlinedIcon from "@mui/icons-material/UploadFileOutlined";
@@ -43,7 +43,7 @@ function ActionCard({ title, body, href, label, icon, onNavigate }: {
onNavigate?: () => void;
}) {
return (
-
+
{icon}
{title}
@@ -57,7 +57,7 @@ function ActionCard({ title, body, href, label, icon, onNavigate }: {
>
{label}
-
+
);
}
@@ -142,7 +142,7 @@ export default function CareerWorkspaceOverview({ completeness, runs, loading, l
{isFirstRun ? {t("careerOverviewFirstRun")} : null}
-
+
{t("careerOverviewCompleteness")}
{loading ? : profileKnown ? {completeness.percent}% : null}
@@ -157,7 +157,7 @@ export default function CareerWorkspaceOverview({ completeness, runs, loading, l
) : null}
-
+
{importUnknown ? {t("loading")}
: runsLoadError ? { importLinkRef.current?.focus(); onRetryRuns(); }}>{t("retry")} : undefined}>{t("careerOverviewImportUnavailable")}
: {importState.message}}
@@ -186,7 +186,7 @@ export default function CareerWorkspaceOverview({ completeness, runs, loading, l
} />
- {t("careerOverviewViewAll")}}>
+ {t("careerOverviewViewAll")}}>
{recentLoading ? (
{t("loading")}
) : recentError ? (