diff --git a/JobTrackerApi.Tests/DashboardPreferencesTests.cs b/JobTrackerApi.Tests/DashboardPreferencesTests.cs index ef403e1a..ee81eb24 100644 --- a/JobTrackerApi.Tests/DashboardPreferencesTests.cs +++ b/JobTrackerApi.Tests/DashboardPreferencesTests.cs @@ -28,6 +28,18 @@ public sealed class DashboardPreferencesTests private const string Route = "/api/auth/preferences/dashboard"; private const string Layout = "{\"version\":1,\"revision\":0,\"widgets\":[{\"id\":\"cards\",\"visible\":true,\"size\":\"full\"}]}"; + [Fact] + public void Agenda_widget_uses_existing_schema_two_without_weakening_unknown_id_or_overlap_checks() + { + const string json = "{\"version\":2,\"revision\":0,\"widgets\":[{\"id\":\"agenda\",\"visible\":true,\"size\":\"standard\",\"x\":0,\"y\":0,\"w\":8,\"h\":8},{\"id\":\"funnel\",\"visible\":true,\"size\":\"standard\",\"x\":8,\"y\":0,\"w\":4,\"h\":8}]}"; + using var valid = JsonDocument.Parse(json); + Assert.NotNull(DashboardLayout.Parse(valid.RootElement)); + using var unknown = JsonDocument.Parse(json.Replace("agenda", "unrecognised")); + Assert.Null(DashboardLayout.Parse(unknown.RootElement)); + using var overlap = JsonDocument.Parse(json.Replace("\"x\":8", "\"x\":4")); + Assert.Null(DashboardLayout.Parse(overlap.RootElement)); + } + [Fact] public async Task Geometry_roundtrip_preserves_hidden_positions_and_blocks_legacy_downgrade() { diff --git a/JobTrackerApi/Models/DashboardLayout.cs b/JobTrackerApi/Models/DashboardLayout.cs index 9fee99ba..3bdcb8ab 100644 --- a/JobTrackerApi/Models/DashboardLayout.cs +++ b/JobTrackerApi/Models/DashboardLayout.cs @@ -15,7 +15,7 @@ public sealed record DashboardLayout(int Version, long Revision, IReadOnlyList KnownIds = new(StringComparer.Ordinal) - { "cards", "attention", "funnel", "activity", "stageAge", "sources", "salary", "companies", "skills" }; + { "cards", "attention", "agenda", "funnel", "activity", "stageAge", "sources", "salary", "companies", "skills" }; public static DashboardLayout? Parse(JsonElement root, bool reading = false, long revision = 0) { diff --git a/docs/ui-modernisation/DASHBOARD_VALIDATION.md b/docs/ui-modernisation/DASHBOARD_VALIDATION.md new file mode 100644 index 00000000..7f3e9101 --- /dev/null +++ b/docs/ui-modernisation/DASHBOARD_VALIDATION.md @@ -0,0 +1,39 @@ +# A3-IM03 dashboard and onboarding qualification + +3 November 2026. Owner accepted navigation992608c7 and explicitly answered **"Approve navigation and A3-IM03"**. This authorises this increment; the next milestone still requires Gate F review under the approved A3 implementation proposal. Publication receipt follows after committing. + +## Implemented scope + +New/reset defaults put Today first, Your week left and pipeline right, then metrics and retained analytics. Saved layouts retain order, hidden positions and geometry. All nine original widget IDs remain; one additive `agenda` ID is accepted by the existing schema2 server validator. Existing saved layouts append it hidden. No migration, endpoint or dependency change. Personal greetings use recorded first/display names only; unnamed accounts retain Dashboard. + +Your week uses strictly parsed, recorded deadlines and follow-ups within seven local calendar days, from the existing attention feed. Date-only deadlines retain their calendar date and raw datetime attribute. Missing/free-text/invalid dates are omitted. It displays at most three entries and links to Reminders; it does not claim complete calendar or interview coverage. Full calendar remains A3-IM07 work. + +Setup moves below the task dashboard. Existing dismissal keys still work; dismissal exposes a resume/walkthrough entry. Individual skips persist per owner and never increase completion. Completion remains based on the existing account/Career/mail/job reads. Walkthrough steps link to existing destinations, explain optional import/mail consent, support previous/next/close and focus return. No guidance action writes source data, grants access, or blocks manual tracking. Existing loading/error/retry remains. + +## Fresh evidence + +| Check | Result and boundary | +|---|---| +| Baseline | 71 frontend tests /9 suites PASSED before changes | +| Test-first new behavior | Six frontend failures reproduced before implementation; backend agenda acceptance failed before whitelist addition | +| Final selected frontend | 95 tests /12 suites PASSED, zero failed; dashboard/grid/CAS hook/customisation/presentation/onboarding/dates/control-loop/navigation/metadata/catalogue/provider. Baseline, red runs and reviewer45 overlap; do not add totals | +| Backend layout | 36 DashboardPreferencesTests PASSED, zero failed/skipped; isolated SQLite HTTP host, real owner/CAS/bounds/collision/legacy tests. New agenda test rejects unknown IDs and overlap. Focused green1 overlaps36 | +| Typecheck / full lint / build | PASSED on combined UI03 + localisation5e8a07d1 tree; zero-warning lint, fresh static export | +| Actual React browser | 16 configurations: widths320/390/768/1440 × EN/NB × light/dark; 32 whole-page axe scans, zero returned violations;32 PNGs | +| Connected journeys | All16: week dates/order, no overflow, desktop week/pipeline row, skip/reload/resume, dismiss/reload/resume, guide next/close/focus, hide agenda/save/reload | +| Write boundary | 16 explicit layout PUTs fulfilled by in-memory fixtures; zero unexpected writes/page errors. Guidance causes zero writes. Every external request aborted; Google Fonts attempts are not provider validation | +| Independent review | No actionable findings. Reviewer45 tests /3 suites PASSED, overlapping95; did not independently rerun backend/browser/build | + +Native JSON/TRX, captures and source-bound hashes are in [evidence/dashboard](evidence/dashboard). Browser harness: `rtk proxy node scripts/verify-a3-dashboard.mjs` from job-tracker-ui against the static export on localhost4338. Browser persistence is controlled in-memory API behavior, separate from real backend SQLite tests; this is not an integrated database E2E pass. Screenshots use synthetic nurse, teacher and engineer data; English job descriptions in Bokmål UI are source content, not translated UI labels. Fixed-header placement in full-page scroll captures is a browser capture artifact, not a native-device result. + +New catalogue keys are paired EN/NB and covered by catalogue QA. The existing widget-title dynamic domain now has ten IDs, including agenda, each backed by both catalogues. All188 historical dynamic reviews remain accounted for; full European localisation is still open. The independent mobile build-metadata fix is now combined and included in selected tests. Prior navigation/foundation evidence remains historical source-bound evidence, not a fresh full rerun. + +## Preservation, recovery and open gates + +Schema2 bounds, collisions, CAS and ownership remain in their existing owners. No route declarations or permission flags changed. Career Profile, CV/letter revisions, null-as-inherit overrides and RD21 owners are untouched. No physical document renderer changes warrant reopening accepted PDF metrics/visuals. + +Rollback: revert this increment on the feature branch, retaining localisation5e8a07d1. No source job/profile data or database schema is changed. An old build may ignore/omit the new agenda preference on a subsequent layout save; all nine old widgets remain compatible. Production rollout/rollback qualification is still required before deployment. Existing layouts are never automatically reset. + +NOT RUN: MariaDB, real backend/browser integrated journeys, hosted CI, native device/touch/screen reader/actual zoom, real provider/mail/model workflows, final integrated security, live recovery or release acceptance. No production services were accessed. Phase10 final acceptance and the wider roadmap remain open. The broader calendar and remaining A3 screens are not complete. + +Next protected dependency: owner Gate F review of this dashboard/onboarding increment before A3-IM04 B1 application table/Kanban/detail implementation. Other independently eligible work remains authorised. diff --git a/docs/ui-modernisation/UI-03.md b/docs/ui-modernisation/UI-03.md new file mode 100644 index 00000000..15394d39 --- /dev/null +++ b/docs/ui-modernisation/UI-03.md @@ -0,0 +1,20 @@ +# A3-IM03 — A1 dashboard and optional onboarding + +Status: IMPLEMENTED / VALIDATED; publication and owner Gate F review tracked in DASHBOARD_VALIDATION.md. Owner explicitly answered the navigation Gate F question: **"Approve navigation and A3-IM03"** on 3 November 2026. Approved navigation checkpoint992608c7 remains immutable. Branch codex/a3-ui-03 starts there, with the independently validated small localisation patch d4b938fa applied as5e8a07d1. No wholesale merge of the older i18n/theme tree. Combined checks qualify this integration only for the stated scope. + +## Scope / task ledger + +- [x] Read existing DashboardView, OnboardingChecklist, DashboardGrid/layout/hook/editor, source-backed completion tests, backend DashboardLayout reader/validation and current A3 contracts. +- [x] Baseline dashboard/grid/layout/onboarding and combined navigation/metadata checks. +- [x] Task-first A1 defaults for new/reset layouts; existing geometry, schema2/CAS and controls preserved. Additive agenda ID is the only server contract change (ruling below); no migration. +- [x] Quiet hierarchy, personal greeting when a recorded name exists, movable week glance beside pipeline; reporting retained. Week coverage limited to recorded attention-feed dates. +- [x] Optional owner-scoped setup, skip/resume and on-demand walkthrough; skips never count as completed data. Guidance controls do not mutate source data or grant consent. +- [x] Targeted regression, types/lint/build and fixture-backed browser journeys across languages/themes/viewports; physical renderer unchanged. +- [x] Independent read-only review: no actionable findings; reviewer45 tests overlap final95. +- [ ] Commit/publish/read-back and next Gate F owner review. Do not begin A3-IM04 before approval; continue unrelated eligible work. + +Preservation:57 groups/34 routes remain; all nine existing widget IDs remain supported. Career Profile stays canonical, account identity remains separate, old dismissal keys remain compatible, owner disposal prevents cross-account state. Existing layout persistence and rendering logic stay in their owners. Legacy version1 preferences keep their recorded order; schema2 coordinates are not repacked on load. API/provider/model/backend/production qualification remains distinct from controlled browser evidence. + +Ruling: the week panel must be separately movable/hideable to reproduce A1's week-left/progress-right row. Add one `agenda` widget ID to the existing catalogue and server JSON whitelist; no schema migration or new endpoint. Keep all nine old widgets and their saved coordinates. Existing saved layouts append the new widget hidden; only new/reset defaults show it. Backend parser/CAS/bounds/collision protections remain unchanged and require targeted regression. The earlier plan's "no backend contract change" assumption is superseded by this small additive ID; no new calendar/provider service is introduced. + +Next dependency after review: A3-IM04 / approved B1 application tracking/detail work. No automatic advance through a future protected owner gate. diff --git a/docs/ui-modernisation/evidence/dashboard/BACKEND.log b/docs/ui-modernisation/evidence/dashboard/BACKEND.log new file mode 100644 index 00000000..7039aae1 --- /dev/null +++ b/docs/ui-modernisation/evidence/dashboard/BACKEND.log @@ -0,0 +1,11 @@ + JobTrackerApi -> C:\Users\Connor\.codex\worktrees\direct-roadmap-integration\Job tracker\JobTrackerApi\bin\Debug\net9.0\JobTrackerApi.dll + PdfBrowserStub -> C:\Users\Connor\.codex\worktrees\direct-roadmap-integration\Job tracker\JobTrackerApi.Tests\PdfBrowserStub\bin\Debug\net9.0\PdfBrowserStub.dll + JobTrackerApi.Tests -> C:\Users\Connor\.codex\worktrees\direct-roadmap-integration\Job tracker\JobTrackerApi.Tests\bin\Debug\net9.0\JobTrackerApi.Tests.dll +Test run for C:\Users\Connor\.codex\worktrees\direct-roadmap-integration\Job tracker\JobTrackerApi.Tests\bin\Debug\net9.0\JobTrackerApi.Tests.dll (.NETCoreApp,Version=v9.0) +VSTest version 17.14.1 (x64) + +Starting test execution, please wait... +A total of 1 test files matched the specified pattern. +Results File: C:\Users\Connor\.codex\worktrees\direct-roadmap-integration\Job tracker\docs\ui-modernisation\evidence\dashboard\backend\a3-dashboard-preferences.trx + +Passed! - Failed: 0, Passed: 36, Skipped: 0, Total: 36, Duration: 36 s - JobTrackerApi.Tests.dll (net9.0) diff --git a/docs/ui-modernisation/evidence/dashboard/BASELINE.json b/docs/ui-modernisation/evidence/dashboard/BASELINE.json new file mode 100644 index 00000000..35c6e242 --- /dev/null +++ b/docs/ui-modernisation/evidence/dashboard/BASELINE.json @@ -0,0 +1 @@ +{"numFailedTestSuites":0,"numFailedTests":0,"numPassedTestSuites":9,"numPassedTests":71,"numPendingTestSuites":0,"numPendingTests":0,"numRuntimeErrorTestSuites":0,"numTodoTests":0,"numTotalTestSuites":9,"numTotalTests":71,"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":1793693190625,"success":true,"testResults":[{"assertionResults":[{"ancestorTitles":[],"duration":140,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"uses structured career data and connected inbox status for four source-backed onboarding steps","invocations":1,"location":null,"numPassingAsserts":15,"retryReasons":[],"startAt":1793693194015,"status":"passed","title":"uses structured career data and connected inbox status for four source-backed onboarding steps"},{"ancestorTitles":[],"duration":41,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"a stored /microsoft-graph/status connection counts without connecting or syncing","invocations":1,"location":null,"numPassingAsserts":7,"retryReasons":[],"startAt":1793693194155,"status":"passed","title":"a stored /microsoft-graph/status connection counts without connecting or syncing"},{"ancestorTitles":[],"duration":30,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"a stored /imap/status connection counts without connecting or syncing","invocations":1,"location":null,"numPassingAsserts":7,"retryReasons":[],"startAt":1793693194196,"status":"passed","title":"a stored /imap/status connection counts without connecting or syncing"},{"ancestorTitles":[],"duration":31,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"manual skills preserves the existing career-content criterion without claiming a CV import","invocations":1,"location":null,"numPassingAsserts":7,"retryReasons":[],"startAt":1793693194226,"status":"passed","title":"manual skills preserves the existing career-content criterion without claiming a CV import"},{"ancestorTitles":[],"duration":30,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"raw career text preserves the existing career-content criterion without claiming a CV import","invocations":1,"location":null,"numPassingAsserts":7,"retryReasons":[],"startAt":1793693194257,"status":"passed","title":"raw career text preserves the existing career-content criterion without claiming a CV import"},{"ancestorTitles":[],"duration":33,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"contact only preserves the existing career-content criterion without claiming a CV import","invocations":1,"location":null,"numPassingAsserts":7,"retryReasons":[],"startAt":1793693194287,"status":"passed","title":"contact only preserves the existing career-content criterion without claiming a CV import"},{"ancestorTitles":[],"duration":31,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"career names and account email alone do not count as account identity details","invocations":1,"location":null,"numPassingAsserts":6,"retryReasons":[],"startAt":1793693194320,"status":"passed","title":"career names and account email alone do not count as account identity details"},{"ancestorTitles":[],"duration":46,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"unknown job summary has indeterminate progress, then responds to known jobs without refetching sources","invocations":1,"location":null,"numPassingAsserts":12,"retryReasons":[],"startAt":1793693194351,"status":"passed","title":"unknown job summary has indeterminate progress, then responds to known jobs without refetching sources"},{"ancestorTitles":[],"duration":6,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"all four recorded steps hide the optional checklist after the sources settle","invocations":1,"location":null,"numPassingAsserts":6,"retryReasons":[],"startAt":1793693194397,"status":"passed","title":"all four recorded steps hide the optional checklist after the sources settle"},{"ancestorTitles":[],"duration":87,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"failed /auth/me status stays unknown and retries only the same source reads","invocations":1,"location":null,"numPassingAsserts":16,"retryReasons":[],"startAt":1793693194403,"status":"passed","title":"failed /auth/me status stays unknown and retries only the same source reads"},{"ancestorTitles":[],"duration":61,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"failed /career/profile status stays unknown and retries only the same source reads","invocations":1,"location":null,"numPassingAsserts":16,"retryReasons":[],"startAt":1793693194490,"status":"passed","title":"failed /career/profile status stays unknown and retries only the same source reads"},{"ancestorTitles":[],"duration":61,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"failed /gmail/status status stays unknown and retries only the same source reads","invocations":1,"location":null,"numPassingAsserts":16,"retryReasons":[],"startAt":1793693194551,"status":"passed","title":"failed /gmail/status status stays unknown and retries only the same source reads"},{"ancestorTitles":[],"duration":61,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"failed /microsoft-graph/status status stays unknown and retries only the same source reads","invocations":1,"location":null,"numPassingAsserts":16,"retryReasons":[],"startAt":1793693194612,"status":"passed","title":"failed /microsoft-graph/status status stays unknown and retries only the same source reads"},{"ancestorTitles":[],"duration":77,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"failed /imap/status status stays unknown and retries only the same source reads","invocations":1,"location":null,"numPassingAsserts":16,"retryReasons":[],"startAt":1793693194673,"status":"passed","title":"failed /imap/status status stays unknown and retries only the same source reads"},{"ancestorTitles":[],"duration":42,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"onboardingStepProfileAction keeps its source-owned destination","invocations":1,"location":null,"numPassingAsserts":5,"retryReasons":[],"startAt":1793693194751,"status":"passed","title":"onboardingStepProfileAction keeps its source-owned destination"},{"ancestorTitles":[],"duration":44,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"onboardingStepCvAction keeps its source-owned destination","invocations":1,"location":null,"numPassingAsserts":5,"retryReasons":[],"startAt":1793693194793,"status":"passed","title":"onboardingStepCvAction keeps its source-owned destination"},{"ancestorTitles":[],"duration":47,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"onboardingStepEmailAction keeps its source-owned destination","invocations":1,"location":null,"numPassingAsserts":5,"retryReasons":[],"startAt":1793693194837,"status":"passed","title":"onboardingStepEmailAction keeps its source-owned destination"},{"ancestorTitles":[],"duration":46,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"onboardingStepJobAction keeps its source-owned destination","invocations":1,"location":null,"numPassingAsserts":5,"retryReasons":[],"startAt":1793693194884,"status":"passed","title":"onboardingStepJobAction keeps its source-owned destination"},{"ancestorTitles":[],"duration":2,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"a legacy owner dismissal skips all reads on mount","invocations":1,"location":null,"numPassingAsserts":6,"retryReasons":[],"startAt":1793693194930,"status":"passed","title":"a legacy owner dismissal skips all reads on mount"},{"ancestorTitles":[],"duration":63,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"dismissal survives reload for its owner and stays separate from another owner's checklist and Dashboard flags","invocations":1,"location":null,"numPassingAsserts":12,"retryReasons":[],"startAt":1793693194932,"status":"passed","title":"dismissal survives reload for its owner and stays separate from another owner's checklist and Dashboard flags"},{"ancestorTitles":[],"duration":33,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"unavailable dismissal storage still permits setup and an in-memory dismissal","invocations":1,"location":null,"numPassingAsserts":6,"retryReasons":[],"startAt":1793693194995,"status":"passed","title":"unavailable dismissal storage still permits setup and an in-memory dismissal"},{"ancestorTitles":[],"duration":11,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"loading is announced without guessed progress and dismissal prevents late failure from reopening the checklist","invocations":1,"location":null,"numPassingAsserts":10,"retryReasons":[],"startAt":1793693195028,"status":"passed","title":"loading is announced without guessed progress and dismissal prevents late failure from reopening the checklist"},{"ancestorTitles":[],"duration":39,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"Bokmål guidance keeps semantic list and progress names without additional source reads","invocations":1,"location":null,"numPassingAsserts":11,"retryReasons":[],"startAt":1793693195039,"status":"passed","title":"Bokmål guidance keeps semantic list and progress names without additional source reads"}],"endTime":1793693195079,"message":"","name":"C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\src\\onboarding-checklist.test.tsx","startTime":1793693191520,"status":"passed","summary":""},{"assertionResults":[{"ancestorTitles":[],"duration":186,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"dashboard attention card opens follow-up workspace","invocations":1,"location":null,"numPassingAsserts":3,"retryReasons":[],"startAt":1793693196979,"status":"passed","title":"dashboard attention card opens follow-up workspace"},{"ancestorTitles":[],"duration":100,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"empty dashboard prioritizes onboarding and suppresses meaningless analytics","invocations":1,"location":null,"numPassingAsserts":8,"retryReasons":[],"startAt":1793693197165,"status":"passed","title":"empty dashboard prioritizes onboarding and suppresses meaningless analytics"},{"ancestorTitles":[],"duration":69,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"reminders open action routes tailored-cv gaps into the tailored cv workspace","invocations":1,"location":null,"numPassingAsserts":4,"retryReasons":[],"startAt":1793693197265,"status":"passed","title":"reminders open action routes tailored-cv gaps into the tailored cv workspace"},{"ancestorTitles":[],"duration":649,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"job table urgency signals and next actions route into the shared workspace flow","invocations":1,"location":null,"numPassingAsserts":4,"retryReasons":[],"startAt":1793693197335,"status":"passed","title":"job table urgency signals and next actions route into the shared workspace flow"}],"endTime":1793693197985,"message":"","name":"C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\src\\daily-control-loop.test.tsx","startTime":1793693195090,"status":"passed","summary":""},{"assertionResults":[{"ancestorTitles":[],"duration":172,"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":1793693198661,"status":"passed","title":"[PRES-S01] exact monthly values, sample, custom statuses and separate salary group remain readable"},{"ancestorTitles":[],"duration":977,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"[PRES-S02] all widget visibility choices save and survive remount","invocations":1,"location":null,"numPassingAsserts":8,"retryReasons":[],"startAt":1793693198833,"status":"passed","title":"[PRES-S02] all widget visibility choices save and survive remount"},{"ancestorTitles":[],"duration":186,"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":1793693199810,"status":"passed","title":"[PRES-S01] a trends failure retains summary and retries without showing a false zero response rate"},{"ancestorTitles":[],"duration":64,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"[PRES-S01] a summary failure does not suppress independent trends","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1793693199996,"status":"passed","title":"[PRES-S01] a summary failure does not suppress independent trends"},{"ancestorTitles":[],"duration":193,"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":1793693200060,"status":"passed","title":"[PRES-J05 J09 S01] raw status and company destinations use supported exact query fields"},{"ancestorTitles":[],"duration":185,"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":1793693200253,"status":"passed","title":"[PRES-S01 S02] period changes only reload trends and retain current snapshot meaning"},{"ancestorTitles":[],"duration":89,"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":1793693200438,"status":"passed","title":"[PRES-S01] zero activity is an explicit window-empty state without a fabricated response percentage"},{"ancestorTitles":[],"duration":89,"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":1793693200527,"status":"passed","title":"[PRES-S01] activity chart uses one shared zero-based scale and a readable legend"},{"ancestorTitles":[],"duration":177,"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":1793693200616,"status":"passed","title":"[P7-02 PRES-S01 S02] setup guidance treats pending and failed summaries as unknown until their owner retries"}],"endTime":1793693200793,"message":"","name":"C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\src\\dashboard-presentation.test.tsx","startTime":1793693197991,"status":"passed","summary":""},{"assertionResults":[{"ancestorTitles":[],"duration":806,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"visibility is a preview; cancel restores the saved layout without a write","invocations":1,"location":null,"numPassingAsserts":4,"retryReasons":[],"startAt":1793693201465,"status":"passed","title":"visibility is a preview; cancel restores the saved layout without a write"},{"ancestorTitles":[],"duration":942,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"failed save keeps the draft and retry persists visibility, order and size","invocations":1,"location":null,"numPassingAsserts":5,"retryReasons":[],"startAt":1793693202271,"status":"passed","title":"failed save keeps the draft and retry persists visibility, order and size"},{"ancestorTitles":[],"duration":775,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"all hidden widgets leave an accessible recovery and reset remains cancellable","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1793693203213,"status":"passed","title":"all hidden widgets leave an accessible recovery and reset remains cancellable"},{"ancestorTitles":[],"duration":531,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"keyboard ordering keeps focus and announces the resulting position without refetching reports","invocations":1,"location":null,"numPassingAsserts":3,"retryReasons":[],"startAt":1793693203988,"status":"passed","title":"keyboard ordering keeps focus and announces the resulting position without refetching reports"},{"ancestorTitles":[],"duration":595,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"dirty route navigation uses the existing confirmation and can stay on the dashboard","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1793693204519,"status":"passed","title":"dirty route navigation uses the existing confirmation and can stay on the dashboard"},{"ancestorTitles":[],"duration":637,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"navigation waits for an in-flight save instead of promising to discard a possible commit","invocations":1,"location":null,"numPassingAsserts":4,"retryReasons":[],"startAt":1793693205114,"status":"passed","title":"navigation waits for an in-flight save instead of promising to discard a possible commit"}],"endTime":1793693205752,"message":"","name":"C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\src\\dashboard-customisation.test.tsx","startTime":1793693200800,"status":"passed","summary":""},{"assertionResults":[{"ancestorTitles":[],"duration":75,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"loads once, migrates only owner legacy flags, and cancels without writing","invocations":1,"location":null,"numPassingAsserts":7,"retryReasons":[],"startAt":1793693205982,"status":"passed","title":"loads once, migrates only owner legacy flags, and cancels without writing"},{"ancestorTitles":[],"duration":79,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"save waits for acknowledgement and retains draft on conflict until explicit reload","invocations":1,"location":null,"numPassingAsserts":11,"retryReasons":[],"startAt":1793693206057,"status":"passed","title":"save waits for acknowledgement and retains draft on conflict until explicit reload"},{"ancestorTitles":[],"duration":75,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"failed or future-version reads cannot enable editing or saves","invocations":1,"location":null,"numPassingAsserts":5,"retryReasons":[],"startAt":1793693206136,"status":"passed","title":"failed or future-version reads cannot enable editing or saves"},{"ancestorTitles":[],"duration":154,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"old save handlers and delayed reads cannot affect a new owner","invocations":1,"location":null,"numPassingAsserts":6,"retryReasons":[],"startAt":1793693206211,"status":"passed","title":"old save handlers and delayed reads cannot affect a new owner"},{"ancestorTitles":[],"duration":156,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"pending save acknowledgement is ignored after identity changes","invocations":1,"location":null,"numPassingAsserts":7,"retryReasons":[],"startAt":1793693206365,"status":"passed","title":"pending save acknowledgement is ignored after identity changes"},{"ancestorTitles":[],"duration":78,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"does not import anonymous legacy flags or replace saved server choices","invocations":1,"location":null,"numPassingAsserts":3,"retryReasons":[],"startAt":1793693206521,"status":"passed","title":"does not import anonymous legacy flags or replace saved server choices"}],"endTime":1793693206599,"message":"","name":"C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\src\\hooks\\useDashboardLayout.test.tsx","startTime":1793693205761,"status":"passed","summary":""},{"assertionResults":[{"ancestorTitles":[],"duration":106,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"library collision handling resolves resize overlaps but leaves deliberate gaps unchanged","invocations":1,"location":null,"numPassingAsserts":3,"retryReasons":[],"startAt":1793693207258,"status":"passed","title":"library collision handling resolves resize overlaps but leaves deliberate gaps unchanged"},{"ancestorTitles":[],"duration":81,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"mount and compaction do not save; completed resize keeps hidden geometry and uses the draft owner revision","invocations":1,"location":null,"numPassingAsserts":6,"retryReasons":[],"startAt":1793693207364,"status":"passed","title":"mount and compaction do not save; completed resize keeps hidden geometry and uses the draft owner revision"},{"ancestorTitles":[],"duration":233,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"narrow viewport retains desktop geometry and keyboard height changes are bounded","invocations":1,"location":null,"numPassingAsserts":6,"retryReasons":[],"startAt":1793693207446,"status":"passed","title":"narrow viewport retains desktop geometry and keyboard height changes are bounded"},{"ancestorTitles":[],"duration":81,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"conflict disables pointer operations and ignores a late completion callback","invocations":1,"location":null,"numPassingAsserts":3,"retryReasons":[],"startAt":1793693207679,"status":"passed","title":"conflict disables pointer operations and ignores a late completion callback"}],"endTime":1793693207760,"message":"","name":"C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\src\\components\\DashboardGrid.test.tsx","startTime":1793693206607,"status":"passed","summary":""},{"assertionResults":[{"ancestorTitles":[],"duration":1,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"legacy preferences migrate to bounded geometry while preserving order, visibility and revision","invocations":1,"location":null,"numPassingAsserts":9,"retryReasons":[],"startAt":1793693207973,"status":"passed","title":"legacy preferences migrate to bounded geometry while preserving order, visibility and revision"},{"ancestorTitles":[],"duration":1,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"schema two preserves chosen hidden geometry without resetting the owner revision","invocations":1,"location":null,"numPassingAsserts":4,"retryReasons":[],"startAt":1793693207975,"status":"passed","title":"schema two preserves chosen hidden geometry without resetting the owner revision"},{"ancestorTitles":[],"duration":0,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"default catalogue preserves every section and supported widths","invocations":1,"location":null,"numPassingAsserts":4,"retryReasons":[],"startAt":1793693207976,"status":"passed","title":"default catalogue preserves every section and supported widths"},{"ancestorTitles":[],"duration":0,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"saved layouts retain known order, reject duplicates and append missing sections hidden","invocations":1,"location":null,"numPassingAsserts":5,"retryReasons":[],"startAt":1793693207976,"status":"passed","title":"saved layouts retain known order, reject duplicates and append missing sections hidden"},{"ancestorTitles":[],"duration":1,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"invalid input has safe defaults: null","invocations":1,"location":null,"numPassingAsserts":1,"retryReasons":[],"startAt":1793693207976,"status":"passed","title":"invalid input has safe defaults: null"},{"ancestorTitles":[],"duration":0,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"invalid input has safe defaults: {}","invocations":1,"location":null,"numPassingAsserts":1,"retryReasons":[],"startAt":1793693207977,"status":"passed","title":"invalid input has safe defaults: {}"},{"ancestorTitles":[],"duration":0,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"invalid input has safe defaults: {\"version\":3,\"revision\":8,\"widgets\":[]}","invocations":1,"location":null,"numPassingAsserts":1,"retryReasons":[],"startAt":1793693207977,"status":"passed","title":"invalid input has safe defaults: {\"version\":3,\"revision\":8,\"widgets\":[]}"},{"ancestorTitles":[],"duration":1,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"invalid input has safe defaults: {\"version\":1,\"revision\":-1,\"widgets\":[]}","invocations":1,"location":null,"numPassingAsserts":1,"retryReasons":[],"startAt":1793693207977,"status":"passed","title":"invalid input has safe defaults: {\"version\":1,\"revision\":-1,\"widgets\":[]}"}],"endTime":1793693207978,"message":"","name":"C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\src\\dashboardLayout.test.ts","startTime":1793693207767,"status":"passed","summary":""},{"assertionResults":[{"ancestorTitles":[],"duration":176,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"en mobile account menu localises the supplied build metadata (User)","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1793693208722,"status":"passed","title":"en mobile account menu localises the supplied build metadata (User)"},{"ancestorTitles":[],"duration":112,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"nb mobile account menu localises the supplied build metadata (Bruker)","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1793693208898,"status":"passed","title":"nb mobile account menu localises the supplied build metadata (Bruker)"},{"ancestorTitles":[],"duration":103,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"en mobile account menu localises the supplied build metadata (User)","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1793693209010,"status":"passed","title":"en mobile account menu localises the supplied build metadata (User)"},{"ancestorTitles":[],"duration":104,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"nb mobile account menu localises the supplied build metadata (Bruker)","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1793693209113,"status":"passed","title":"nb mobile account menu localises the supplied build metadata (Bruker)"}],"endTime":1793693209217,"message":"","name":"C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\src\\build-metadata-localisation.test.tsx","startTime":1793693207985,"status":"passed","summary":""},{"assertionResults":[{"ancestorTitles":[],"duration":317,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"en hybrid navigation has five labelled destinations and retains secondary tools","invocations":1,"location":null,"numPassingAsserts":9,"retryReasons":[],"startAt":1793693209899,"status":"passed","title":"en hybrid navigation has five labelled destinations and retains secondary tools"},{"ancestorTitles":[],"duration":253,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"nb hybrid navigation has five labelled destinations and retains secondary tools","invocations":1,"location":null,"numPassingAsserts":9,"retryReasons":[],"startAt":1793693210216,"status":"passed","title":"nb hybrid navigation has five labelled destinations and retains secondary tools"},{"ancestorTitles":[],"duration":162,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"/career/builder/42 selects its primary destination and keyboard route focus survives","invocations":1,"location":null,"numPassingAsserts":3,"retryReasons":[],"startAt":1793693210470,"status":"passed","title":"/career/builder/42 selects its primary destination and keyboard route focus survives"},{"ancestorTitles":[],"duration":168,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"/applications/42 selects its primary destination and keyboard route focus survives","invocations":1,"location":null,"numPassingAsserts":3,"retryReasons":[],"startAt":1793693210632,"status":"passed","title":"/applications/42 selects its primary destination and keyboard route focus survives"},{"ancestorTitles":[],"duration":138,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"/jobs/42 selects its primary destination and keyboard route focus survives","invocations":1,"location":null,"numPassingAsserts":3,"retryReasons":[],"startAt":1793693210800,"status":"passed","title":"/jobs/42 selects its primary destination and keyboard route focus survives"},{"ancestorTitles":[],"duration":166,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"desktop retains labelled collapse, persistence, and all secondary destinations","invocations":1,"location":null,"numPassingAsserts":3,"retryReasons":[],"startAt":1793693210938,"status":"passed","title":"desktop retains labelled collapse, persistence, and all secondary destinations"},{"ancestorTitles":[],"duration":139,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"choosing the current destination from More closes the drawer and focuses its heading","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1793693211104,"status":"passed","title":"choosing the current destination from More closes the drawer and focuses its heading"}],"endTime":1793693211243,"message":"","name":"C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\src\\a3-navigation.test.tsx","startTime":1793693209228,"status":"passed","summary":""}],"wasInterrupted":false} diff --git a/docs/ui-modernisation/evidence/dashboard/CHECKS.json b/docs/ui-modernisation/evidence/dashboard/CHECKS.json new file mode 100644 index 00000000..5bfd451b --- /dev/null +++ b/docs/ui-modernisation/evidence/dashboard/CHECKS.json @@ -0,0 +1,17 @@ +{ + "date": "2026-11-03", + "branch": "codex/a3-ui-03", + "base": "5e8a07d1", + "frontend": {"report": "FINAL_TESTS.json", "passed": 95, "failed": 0, "suites": 12}, + "backend": {"report": "backend/a3-dashboard-preferences.trx", "passed": 36, "failed": 0, "skipped": 0, "environment": "isolated temporary SQLite HTTP test host"}, + "commands": [ + {"command": "rtk proxy npm run typecheck", "exitCode": 0}, + {"command": "rtk proxy npm run lint", "exitCode": 0}, + {"command": "rtk proxy npm run build", "exitCode": 0}, + {"command": "rtk proxy node scripts/verify-a3-dashboard.mjs", "exitCode": 0}, + {"command": "rtk proxy git diff --check", "exitCode": 0}, + {"command": "rtk proxy graphify update .", "exitCode": 0, "qualification": "AST only, SQL extractor unavailable; generated untracked graph not acceptance evidence"} + ], + "review": {"result": "no actionable findings", "reviewerTests": 45, "overlapsFrontendTotal": true}, + "notRun": ["MariaDB", "real backend browser integration", "native devices and screen readers", "hosted CI", "provider or model services", "final integrated security", "production release qualification"] +} diff --git a/docs/ui-modernisation/evidence/dashboard/FINAL_TESTS.json b/docs/ui-modernisation/evidence/dashboard/FINAL_TESTS.json new file mode 100644 index 00000000..d0d06b61 --- /dev/null +++ b/docs/ui-modernisation/evidence/dashboard/FINAL_TESTS.json @@ -0,0 +1 @@ +{"numFailedTestSuites":0,"numFailedTests":0,"numPassedTestSuites":12,"numPassedTests":95,"numPendingTestSuites":0,"numPendingTests":0,"numRuntimeErrorTestSuites":0,"numTodoTests":0,"numTotalTestSuites":12,"numTotalTests":95,"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":1793694970913,"success":true,"testResults":[{"assertionResults":[{"ancestorTitles":[],"duration":88,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"deadline 2026-11-03 respects the local seven-day boundary and recorded precision","invocations":1,"location":null,"numPassingAsserts":3,"retryReasons":[],"startAt":1793694974772,"status":"passed","title":"deadline 2026-11-03 respects the local seven-day boundary and recorded precision"},{"ancestorTitles":[],"duration":12,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"deadline 2026-11-09 respects the local seven-day boundary and recorded precision","invocations":1,"location":null,"numPassingAsserts":3,"retryReasons":[],"startAt":1793694974860,"status":"passed","title":"deadline 2026-11-09 respects the local seven-day boundary and recorded precision"},{"ancestorTitles":[],"duration":8,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"deadline 2026-11-10 respects the local seven-day boundary and recorded precision","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1793694974872,"status":"passed","title":"deadline 2026-11-10 respects the local seven-day boundary and recorded precision"},{"ancestorTitles":[],"duration":8,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"deadline 2026-11-02 respects the local seven-day boundary and recorded precision","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1793694974880,"status":"passed","title":"deadline 2026-11-02 respects the local seven-day boundary and recorded precision"},{"ancestorTitles":[],"duration":8,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"deadline as soon as possible respects the local seven-day boundary and recorded precision","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1793694974888,"status":"passed","title":"deadline as soon as possible respects the local seven-day boundary and recorded precision"},{"ancestorTitles":[],"duration":6,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"deadline 2026-11-04T24:00:00Z respects the local seven-day boundary and recorded precision","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1793694974896,"status":"passed","title":"deadline 2026-11-04T24:00:00Z respects the local seven-day boundary and recorded precision"},{"ancestorTitles":[],"duration":7,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"deadline 2026-11-04T10:99:00Z respects the local seven-day boundary and recorded precision","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1793694974902,"status":"passed","title":"deadline 2026-11-04T10:99:00Z respects the local seven-day boundary and recorded precision"},{"ancestorTitles":[],"duration":7,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"invalid calendar dates do not silently roll into a real upcoming day","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1793694974909,"status":"passed","title":"invalid calendar dates do not silently roll into a real upcoming day"},{"ancestorTitles":[],"duration":16,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"recorded follow-up timestamps and date-only deadlines stay distinct and untranslated","invocations":1,"location":null,"numPassingAsserts":5,"retryReasons":[],"startAt":1793694974916,"status":"passed","title":"recorded follow-up timestamps and date-only deadlines stay distinct and untranslated"}],"endTime":1793694974933,"message":"","name":"C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\src\\dashboard-week.test.tsx","startTime":1793694971906,"status":"passed","summary":""},{"assertionResults":[{"ancestorTitles":[],"duration":947,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"visibility is a preview; cancel restores the saved layout without a write","invocations":1,"location":null,"numPassingAsserts":4,"retryReasons":[],"startAt":1793694975764,"status":"passed","title":"visibility is a preview; cancel restores the saved layout without a write"},{"ancestorTitles":[],"duration":1006,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"failed save keeps the draft and retry persists visibility, order and size","invocations":1,"location":null,"numPassingAsserts":5,"retryReasons":[],"startAt":1793694976712,"status":"passed","title":"failed save keeps the draft and retry persists visibility, order and size"},{"ancestorTitles":[],"duration":940,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"all hidden widgets leave an accessible recovery and reset remains cancellable","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1793694977718,"status":"passed","title":"all hidden widgets leave an accessible recovery and reset remains cancellable"},{"ancestorTitles":[],"duration":529,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"keyboard ordering keeps focus and announces the resulting position without refetching reports","invocations":1,"location":null,"numPassingAsserts":5,"retryReasons":[],"startAt":1793694978658,"status":"passed","title":"keyboard ordering keeps focus and announces the resulting position without refetching reports"},{"ancestorTitles":[],"duration":859,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"dirty route navigation uses the existing confirmation and can stay on the dashboard","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1793694979187,"status":"passed","title":"dirty route navigation uses the existing confirmation and can stay on the dashboard"},{"ancestorTitles":[],"duration":670,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"navigation waits for an in-flight save instead of promising to discard a possible commit","invocations":1,"location":null,"numPassingAsserts":4,"retryReasons":[],"startAt":1793694980047,"status":"passed","title":"navigation waits for an in-flight save instead of promising to discard a possible commit"}],"endTime":1793694980718,"message":"","name":"C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\src\\dashboard-customisation.test.tsx","startTime":1793694974946,"status":"passed","summary":""},{"assertionResults":[{"ancestorTitles":[],"duration":156,"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":1793694981402,"status":"passed","title":"A1 week glance shows recorded upcoming dates from attention data without inventing missing dates"},{"ancestorTitles":[],"duration":155,"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":1793694981558,"status":"passed","title":"[PRES-S01] exact monthly values, sample, custom statuses and separate salary group remain readable"},{"ancestorTitles":[],"duration":835,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"[PRES-S02] all widget visibility choices save and survive remount","invocations":1,"location":null,"numPassingAsserts":8,"retryReasons":[],"startAt":1793694981713,"status":"passed","title":"[PRES-S02] all widget visibility choices save and survive remount"},{"ancestorTitles":[],"duration":148,"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":1793694982548,"status":"passed","title":"[PRES-S01] a trends failure retains summary and retries without showing a false zero response rate"},{"ancestorTitles":[],"duration":53,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"[PRES-S01] a summary failure does not suppress independent trends","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1793694982696,"status":"passed","title":"[PRES-S01] a summary failure does not suppress independent trends"},{"ancestorTitles":[],"duration":224,"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":1793694982749,"status":"passed","title":"[PRES-J05 J09 S01] raw status and company destinations use supported exact query fields"},{"ancestorTitles":[],"duration":183,"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":1793694982973,"status":"passed","title":"[PRES-S01 S02] period changes only reload trends and retain current snapshot meaning"},{"ancestorTitles":[],"duration":78,"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":1793694983156,"status":"passed","title":"[PRES-S01] zero activity is an explicit window-empty state without a fabricated response percentage"},{"ancestorTitles":[],"duration":81,"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":1793694983234,"status":"passed","title":"[PRES-S01] activity chart uses one shared zero-based scale and a readable legend"},{"ancestorTitles":[],"duration":154,"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":1793694983315,"status":"passed","title":"[P7-02 PRES-S01 S02] setup guidance treats pending and failed summaries as unknown until their owner retries"}],"endTime":1793694983470,"message":"","name":"C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\src\\dashboard-presentation.test.tsx","startTime":1793694980728,"status":"passed","summary":""},{"assertionResults":[{"ancestorTitles":[],"duration":344,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"en hybrid navigation has five labelled destinations and retains secondary tools","invocations":1,"location":null,"numPassingAsserts":9,"retryReasons":[],"startAt":1793694984129,"status":"passed","title":"en hybrid navigation has five labelled destinations and retains secondary tools"},{"ancestorTitles":[],"duration":258,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"nb hybrid navigation has five labelled destinations and retains secondary tools","invocations":1,"location":null,"numPassingAsserts":9,"retryReasons":[],"startAt":1793694984473,"status":"passed","title":"nb hybrid navigation has five labelled destinations and retains secondary tools"},{"ancestorTitles":[],"duration":167,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"/career/builder/42 selects its primary destination and keyboard route focus survives","invocations":1,"location":null,"numPassingAsserts":3,"retryReasons":[],"startAt":1793694984732,"status":"passed","title":"/career/builder/42 selects its primary destination and keyboard route focus survives"},{"ancestorTitles":[],"duration":160,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"/applications/42 selects its primary destination and keyboard route focus survives","invocations":1,"location":null,"numPassingAsserts":3,"retryReasons":[],"startAt":1793694984899,"status":"passed","title":"/applications/42 selects its primary destination and keyboard route focus survives"},{"ancestorTitles":[],"duration":136,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"/jobs/42 selects its primary destination and keyboard route focus survives","invocations":1,"location":null,"numPassingAsserts":3,"retryReasons":[],"startAt":1793694985059,"status":"passed","title":"/jobs/42 selects its primary destination and keyboard route focus survives"},{"ancestorTitles":[],"duration":159,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"desktop retains labelled collapse, persistence, and all secondary destinations","invocations":1,"location":null,"numPassingAsserts":3,"retryReasons":[],"startAt":1793694985195,"status":"passed","title":"desktop retains labelled collapse, persistence, and all secondary destinations"},{"ancestorTitles":[],"duration":145,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"choosing the current destination from More closes the drawer and focuses its heading","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1793694985354,"status":"passed","title":"choosing the current destination from More closes the drawer and focuses its heading"}],"endTime":1793694985500,"message":"","name":"C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\src\\a3-navigation.test.tsx","startTime":1793694983477,"status":"passed","summary":""},{"assertionResults":[{"ancestorTitles":[],"duration":87,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"a dismissed optional checklist resumes without losing source-backed progress","invocations":1,"location":null,"numPassingAsserts":8,"retryReasons":[],"startAt":1793694986201,"status":"passed","title":"a dismissed optional checklist resumes without losing source-backed progress"},{"ancestorTitles":[],"duration":120,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"individual skips persist for their owner but do not count as completed data","invocations":1,"location":null,"numPassingAsserts":9,"retryReasons":[],"startAt":1793694986288,"status":"passed","title":"individual skips persist for their owner but do not count as completed data"},{"ancestorTitles":[],"duration":75,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"on-demand walkthrough changes guidance and focus without changing completion or connecting providers","invocations":1,"location":null,"numPassingAsserts":8,"retryReasons":[],"startAt":1793694986409,"status":"passed","title":"on-demand walkthrough changes guidance and focus without changing completion or connecting providers"},{"ancestorTitles":[],"duration":47,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"uses structured career data and connected inbox status for four source-backed onboarding steps","invocations":1,"location":null,"numPassingAsserts":15,"retryReasons":[],"startAt":1793694986484,"status":"passed","title":"uses structured career data and connected inbox status for four source-backed onboarding steps"},{"ancestorTitles":[],"duration":37,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"a stored /microsoft-graph/status connection counts without connecting or syncing","invocations":1,"location":null,"numPassingAsserts":7,"retryReasons":[],"startAt":1793694986532,"status":"passed","title":"a stored /microsoft-graph/status connection counts without connecting or syncing"},{"ancestorTitles":[],"duration":30,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"a stored /imap/status connection counts without connecting or syncing","invocations":1,"location":null,"numPassingAsserts":7,"retryReasons":[],"startAt":1793694986569,"status":"passed","title":"a stored /imap/status connection counts without connecting or syncing"},{"ancestorTitles":[],"duration":31,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"manual skills preserves the existing career-content criterion without claiming a CV import","invocations":1,"location":null,"numPassingAsserts":7,"retryReasons":[],"startAt":1793694986599,"status":"passed","title":"manual skills preserves the existing career-content criterion without claiming a CV import"},{"ancestorTitles":[],"duration":31,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"raw career text preserves the existing career-content criterion without claiming a CV import","invocations":1,"location":null,"numPassingAsserts":7,"retryReasons":[],"startAt":1793694986630,"status":"passed","title":"raw career text preserves the existing career-content criterion without claiming a CV import"},{"ancestorTitles":[],"duration":33,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"contact only preserves the existing career-content criterion without claiming a CV import","invocations":1,"location":null,"numPassingAsserts":7,"retryReasons":[],"startAt":1793694986661,"status":"passed","title":"contact only preserves the existing career-content criterion without claiming a CV import"},{"ancestorTitles":[],"duration":32,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"career names and account email alone do not count as account identity details","invocations":1,"location":null,"numPassingAsserts":6,"retryReasons":[],"startAt":1793694986694,"status":"passed","title":"career names and account email alone do not count as account identity details"},{"ancestorTitles":[],"duration":47,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"unknown job summary has indeterminate progress, then responds to known jobs without refetching sources","invocations":1,"location":null,"numPassingAsserts":12,"retryReasons":[],"startAt":1793694986726,"status":"passed","title":"unknown job summary has indeterminate progress, then responds to known jobs without refetching sources"},{"ancestorTitles":[],"duration":9,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"all four recorded steps hide the optional checklist after the sources settle","invocations":1,"location":null,"numPassingAsserts":6,"retryReasons":[],"startAt":1793694986773,"status":"passed","title":"all four recorded steps hide the optional checklist after the sources settle"},{"ancestorTitles":[],"duration":81,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"failed /auth/me status stays unknown and retries only the same source reads","invocations":1,"location":null,"numPassingAsserts":16,"retryReasons":[],"startAt":1793694986782,"status":"passed","title":"failed /auth/me status stays unknown and retries only the same source reads"},{"ancestorTitles":[],"duration":77,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"failed /career/profile status stays unknown and retries only the same source reads","invocations":1,"location":null,"numPassingAsserts":16,"retryReasons":[],"startAt":1793694986863,"status":"passed","title":"failed /career/profile status stays unknown and retries only the same source reads"},{"ancestorTitles":[],"duration":77,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"failed /gmail/status status stays unknown and retries only the same source reads","invocations":1,"location":null,"numPassingAsserts":16,"retryReasons":[],"startAt":1793694986940,"status":"passed","title":"failed /gmail/status status stays unknown and retries only the same source reads"},{"ancestorTitles":[],"duration":62,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"failed /microsoft-graph/status status stays unknown and retries only the same source reads","invocations":1,"location":null,"numPassingAsserts":16,"retryReasons":[],"startAt":1793694987017,"status":"passed","title":"failed /microsoft-graph/status status stays unknown and retries only the same source reads"},{"ancestorTitles":[],"duration":77,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"failed /imap/status status stays unknown and retries only the same source reads","invocations":1,"location":null,"numPassingAsserts":16,"retryReasons":[],"startAt":1793694987079,"status":"passed","title":"failed /imap/status status stays unknown and retries only the same source reads"},{"ancestorTitles":[],"duration":44,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"onboardingStepProfileAction keeps its source-owned destination","invocations":1,"location":null,"numPassingAsserts":5,"retryReasons":[],"startAt":1793694987156,"status":"passed","title":"onboardingStepProfileAction keeps its source-owned destination"},{"ancestorTitles":[],"duration":46,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"onboardingStepCvAction keeps its source-owned destination","invocations":1,"location":null,"numPassingAsserts":5,"retryReasons":[],"startAt":1793694987200,"status":"passed","title":"onboardingStepCvAction keeps its source-owned destination"},{"ancestorTitles":[],"duration":46,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"onboardingStepEmailAction keeps its source-owned destination","invocations":1,"location":null,"numPassingAsserts":5,"retryReasons":[],"startAt":1793694987246,"status":"passed","title":"onboardingStepEmailAction keeps its source-owned destination"},{"ancestorTitles":[],"duration":45,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"onboardingStepJobAction keeps its source-owned destination","invocations":1,"location":null,"numPassingAsserts":5,"retryReasons":[],"startAt":1793694987293,"status":"passed","title":"onboardingStepJobAction keeps its source-owned destination"},{"ancestorTitles":[],"duration":8,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"a legacy owner dismissal skips all reads on mount","invocations":1,"location":null,"numPassingAsserts":6,"retryReasons":[],"startAt":1793694987338,"status":"passed","title":"a legacy owner dismissal skips all reads on mount"},{"ancestorTitles":[],"duration":85,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"dismissal survives reload for its owner and stays separate from another owner's checklist and Dashboard flags","invocations":1,"location":null,"numPassingAsserts":12,"retryReasons":[],"startAt":1793694987346,"status":"passed","title":"dismissal survives reload for its owner and stays separate from another owner's checklist and Dashboard flags"},{"ancestorTitles":[],"duration":37,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"unavailable dismissal storage still permits setup and an in-memory dismissal","invocations":1,"location":null,"numPassingAsserts":6,"retryReasons":[],"startAt":1793694987431,"status":"passed","title":"unavailable dismissal storage still permits setup and an in-memory dismissal"},{"ancestorTitles":[],"duration":18,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"loading is announced without guessed progress and dismissal prevents late failure from reopening the checklist","invocations":1,"location":null,"numPassingAsserts":10,"retryReasons":[],"startAt":1793694987468,"status":"passed","title":"loading is announced without guessed progress and dismissal prevents late failure from reopening the checklist"},{"ancestorTitles":[],"duration":25,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"Bokmål guidance keeps semantic list and progress names without additional source reads","invocations":1,"location":null,"numPassingAsserts":11,"retryReasons":[],"startAt":1793694987486,"status":"passed","title":"Bokmål guidance keeps semantic list and progress names without additional source reads"}],"endTime":1793694987511,"message":"","name":"C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\src\\onboarding-checklist.test.tsx","startTime":1793694985507,"status":"passed","summary":""},{"assertionResults":[{"ancestorTitles":[],"duration":179,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"dashboard attention card opens follow-up workspace","invocations":1,"location":null,"numPassingAsserts":3,"retryReasons":[],"startAt":1793694988504,"status":"passed","title":"dashboard attention card opens follow-up workspace"},{"ancestorTitles":[],"duration":101,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"empty dashboard prioritizes onboarding and suppresses meaningless analytics","invocations":1,"location":null,"numPassingAsserts":8,"retryReasons":[],"startAt":1793694988683,"status":"passed","title":"empty dashboard prioritizes onboarding and suppresses meaningless analytics"},{"ancestorTitles":[],"duration":68,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"reminders open action routes tailored-cv gaps into the tailored cv workspace","invocations":1,"location":null,"numPassingAsserts":4,"retryReasons":[],"startAt":1793694988784,"status":"passed","title":"reminders open action routes tailored-cv gaps into the tailored cv workspace"},{"ancestorTitles":[],"duration":633,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"job table urgency signals and next actions route into the shared workspace flow","invocations":1,"location":null,"numPassingAsserts":4,"retryReasons":[],"startAt":1793694988852,"status":"passed","title":"job table urgency signals and next actions route into the shared workspace flow"}],"endTime":1793694989486,"message":"","name":"C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\src\\daily-control-loop.test.tsx","startTime":1793694987518,"status":"passed","summary":""},{"assertionResults":[{"ancestorTitles":[],"duration":113,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"library collision handling resolves resize overlaps but leaves deliberate gaps unchanged","invocations":1,"location":null,"numPassingAsserts":3,"retryReasons":[],"startAt":1793694990113,"status":"passed","title":"library collision handling resolves resize overlaps but leaves deliberate gaps unchanged"},{"ancestorTitles":[],"duration":89,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"mount and compaction do not save; completed resize keeps hidden geometry and uses the draft owner revision","invocations":1,"location":null,"numPassingAsserts":6,"retryReasons":[],"startAt":1793694990226,"status":"passed","title":"mount and compaction do not save; completed resize keeps hidden geometry and uses the draft owner revision"},{"ancestorTitles":[],"duration":265,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"narrow viewport retains desktop geometry and keyboard height changes are bounded","invocations":1,"location":null,"numPassingAsserts":6,"retryReasons":[],"startAt":1793694990315,"status":"passed","title":"narrow viewport retains desktop geometry and keyboard height changes are bounded"},{"ancestorTitles":[],"duration":88,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"conflict disables pointer operations and ignores a late completion callback","invocations":1,"location":null,"numPassingAsserts":3,"retryReasons":[],"startAt":1793694990580,"status":"passed","title":"conflict disables pointer operations and ignores a late completion callback"}],"endTime":1793694990668,"message":"","name":"C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\src\\components\\DashboardGrid.test.tsx","startTime":1793694989493,"status":"passed","summary":""},{"assertionResults":[{"ancestorTitles":[],"duration":13,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"migrates the legacy Norwegian code and persists Bokmål using nb-NO","invocations":1,"location":null,"numPassingAsserts":5,"retryReasons":[],"startAt":1793694991355,"status":"passed","title":"migrates the legacy Norwegian code and persists Bokmål using nb-NO"},{"ancestorTitles":[],"duration":6,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"hydrates an authenticated server preference without writing it back","invocations":1,"location":null,"numPassingAsserts":3,"retryReasons":[],"startAt":1793694991368,"status":"passed","title":"hydrates an authenticated server preference without writing it back"},{"ancestorTitles":[],"duration":280,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"a failed account language save keeps the local choice and offers a localized retry: Norsk","invocations":1,"location":null,"numPassingAsserts":11,"retryReasons":[],"startAt":1793694991374,"status":"passed","title":"a failed account language save keeps the local choice and offers a localized retry: Norsk"},{"ancestorTitles":[],"duration":246,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"a failed account language save keeps the local choice and offers a localized retry: English","invocations":1,"location":null,"numPassingAsserts":11,"retryReasons":[],"startAt":1793694991654,"status":"passed","title":"a failed account language save keeps the local choice and offers a localized retry: English"},{"ancestorTitles":[],"duration":10,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"a delayed language-save failure cannot offer a retry for a newer successful choice","invocations":1,"location":null,"numPassingAsserts":3,"retryReasons":[],"startAt":1793694991900,"status":"passed","title":"a delayed language-save failure cannot offer a retry for a newer successful choice"},{"ancestorTitles":[],"duration":33,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"a previously delivered language retry cannot reverse a newer choice","invocations":1,"location":null,"numPassingAsserts":3,"retryReasons":[],"startAt":1793694991910,"status":"passed","title":"a previously delivered language retry cannot reverse a newer choice"}],"endTime":1793694991944,"message":"","name":"C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\src\\i18n-provider.test.tsx","startTime":1793694990679,"status":"passed","summary":""},{"assertionResults":[{"ancestorTitles":[],"duration":161,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"en mobile account menu localises the supplied build metadata (User)","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1793694992599,"status":"passed","title":"en mobile account menu localises the supplied build metadata (User)"},{"ancestorTitles":[],"duration":118,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"nb mobile account menu localises the supplied build metadata (Bruker)","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1793694992760,"status":"passed","title":"nb mobile account menu localises the supplied build metadata (Bruker)"},{"ancestorTitles":[],"duration":105,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"en mobile account menu localises the supplied build metadata (User)","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1793694992878,"status":"passed","title":"en mobile account menu localises the supplied build metadata (User)"},{"ancestorTitles":[],"duration":108,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"nb mobile account menu localises the supplied build metadata (Bruker)","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1793694992983,"status":"passed","title":"nb mobile account menu localises the supplied build metadata (Bruker)"}],"endTime":1793694993092,"message":"","name":"C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\src\\build-metadata-localisation.test.tsx","startTime":1793694991953,"status":"passed","summary":""},{"assertionResults":[{"ancestorTitles":[],"duration":64,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"loads once, migrates only owner legacy flags, and cancels without writing","invocations":1,"location":null,"numPassingAsserts":7,"retryReasons":[],"startAt":1793694993301,"status":"passed","title":"loads once, migrates only owner legacy flags, and cancels without writing"},{"ancestorTitles":[],"duration":78,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"save waits for acknowledgement and retains draft on conflict until explicit reload","invocations":1,"location":null,"numPassingAsserts":11,"retryReasons":[],"startAt":1793694993365,"status":"passed","title":"save waits for acknowledgement and retains draft on conflict until explicit reload"},{"ancestorTitles":[],"duration":74,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"failed or future-version reads cannot enable editing or saves","invocations":1,"location":null,"numPassingAsserts":5,"retryReasons":[],"startAt":1793694993443,"status":"passed","title":"failed or future-version reads cannot enable editing or saves"},{"ancestorTitles":[],"duration":155,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"old save handlers and delayed reads cannot affect a new owner","invocations":1,"location":null,"numPassingAsserts":6,"retryReasons":[],"startAt":1793694993517,"status":"passed","title":"old save handlers and delayed reads cannot affect a new owner"},{"ancestorTitles":[],"duration":153,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"pending save acknowledgement is ignored after identity changes","invocations":1,"location":null,"numPassingAsserts":7,"retryReasons":[],"startAt":1793694993672,"status":"passed","title":"pending save acknowledgement is ignored after identity changes"},{"ancestorTitles":[],"duration":78,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"does not import anonymous legacy flags or replace saved server choices","invocations":1,"location":null,"numPassingAsserts":3,"retryReasons":[],"startAt":1793694993825,"status":"passed","title":"does not import anonymous legacy flags or replace saved server choices"}],"endTime":1793694993904,"message":"","name":"C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\src\\hooks\\useDashboardLayout.test.tsx","startTime":1793694993102,"status":"passed","summary":""},{"assertionResults":[{"ancestorTitles":[],"duration":8,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"English and Bokmål expose the same UI message keys","invocations":1,"location":null,"numPassingAsserts":1,"retryReasons":[],"startAt":1793694994094,"status":"passed","title":"English and Bokmål expose the same UI message keys"},{"ancestorTitles":[],"duration":5,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"Bokmål preserves every English substitution variable","invocations":1,"location":null,"numPassingAsserts":1,"retryReasons":[],"startAt":1793694994102,"status":"passed","title":"Bokmål preserves every English substitution variable"},{"ancestorTitles":[],"duration":2,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"supported UI catalogues contain no blank messages","invocations":1,"location":null,"numPassingAsserts":1,"retryReasons":[],"startAt":1793694994107,"status":"passed","title":"supported UI catalogues contain no blank messages"}],"endTime":1793694994109,"message":"","name":"C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\src\\translation-catalogue.test.ts","startTime":1793694993912,"status":"passed","summary":""},{"assertionResults":[{"ancestorTitles":[],"duration":2,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"legacy preferences migrate to bounded geometry while preserving order, visibility and revision","invocations":1,"location":null,"numPassingAsserts":9,"retryReasons":[],"startAt":1793694994298,"status":"passed","title":"legacy preferences migrate to bounded geometry while preserving order, visibility and revision"},{"ancestorTitles":[],"duration":0,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"schema two preserves chosen hidden geometry without resetting the owner revision","invocations":1,"location":null,"numPassingAsserts":4,"retryReasons":[],"startAt":1793694994300,"status":"passed","title":"schema two preserves chosen hidden geometry without resetting the owner revision"},{"ancestorTitles":[],"duration":0,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"default catalogue preserves every section and supported widths","invocations":1,"location":null,"numPassingAsserts":4,"retryReasons":[],"startAt":1793694994300,"status":"passed","title":"default catalogue preserves every section and supported widths"},{"ancestorTitles":[],"duration":1,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"new schema 1 preferences use task-first A1 defaults without omitting widgets","invocations":1,"location":null,"numPassingAsserts":5,"retryReasons":[],"startAt":1793694994300,"status":"passed","title":"new schema 1 preferences use task-first A1 defaults without omitting widgets"},{"ancestorTitles":[],"duration":0,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"new schema 2 preferences use task-first A1 defaults without omitting widgets","invocations":1,"location":null,"numPassingAsserts":5,"retryReasons":[],"startAt":1793694994301,"status":"passed","title":"new schema 2 preferences use task-first A1 defaults without omitting widgets"},{"ancestorTitles":[],"duration":0,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"saved layouts retain known order, reject duplicates and append missing sections hidden","invocations":1,"location":null,"numPassingAsserts":5,"retryReasons":[],"startAt":1793694994301,"status":"passed","title":"saved layouts retain known order, reject duplicates and append missing sections hidden"},{"ancestorTitles":[],"duration":0,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"invalid input has safe defaults: null","invocations":1,"location":null,"numPassingAsserts":1,"retryReasons":[],"startAt":1793694994302,"status":"passed","title":"invalid input has safe defaults: null"},{"ancestorTitles":[],"duration":0,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"invalid input has safe defaults: {}","invocations":1,"location":null,"numPassingAsserts":1,"retryReasons":[],"startAt":1793694994302,"status":"passed","title":"invalid input has safe defaults: {}"},{"ancestorTitles":[],"duration":0,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"invalid input has safe defaults: {\"version\":3,\"revision\":8,\"widgets\":[]}","invocations":1,"location":null,"numPassingAsserts":1,"retryReasons":[],"startAt":1793694994302,"status":"passed","title":"invalid input has safe defaults: {\"version\":3,\"revision\":8,\"widgets\":[]}"},{"ancestorTitles":[],"duration":0,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"invalid input has safe defaults: {\"version\":1,\"revision\":-1,\"widgets\":[]}","invocations":1,"location":null,"numPassingAsserts":1,"retryReasons":[],"startAt":1793694994303,"status":"passed","title":"invalid input has safe defaults: {\"version\":1,\"revision\":-1,\"widgets\":[]}"}],"endTime":1793694994303,"message":"","name":"C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\src\\dashboardLayout.test.ts","startTime":1793694994115,"status":"passed","summary":""}],"wasInterrupted":false} diff --git a/docs/ui-modernisation/evidence/dashboard/FRONTEND.log b/docs/ui-modernisation/evidence/dashboard/FRONTEND.log new file mode 100644 index 00000000..f6edcebb --- /dev/null +++ b/docs/ui-modernisation/evidence/dashboard/FRONTEND.log @@ -0,0 +1,11 @@ + +> job-tracker-ui@0.1.0 test +> jest --runInBand --runTestsByPath src/dashboardLayout.test.ts src/hooks/useDashboardLayout.test.tsx src/components/DashboardGrid.test.tsx src/dashboard-customisation.test.tsx src/dashboard-presentation.test.tsx src/onboarding-checklist.test.tsx src/daily-control-loop.test.tsx src/dashboard-week.test.tsx src/a3-navigation.test.tsx src/build-metadata-localisation.test.tsx src/translation-catalogue.test.ts src/i18n-provider.test.tsx --json --outputFile=../a3-dashboard-tests.json + + +Test Suites: 12 passed, 12 total +Tests: 95 passed, 95 total +Snapshots: 0 total +Time: 23.394 s +Ran all test suites within paths "src/dashboardLayout.test.ts", "src/hooks/useDashboardLayout.test.tsx", "src/components/DashboardGrid.test.tsx", "src/dashboard-customisation.test.tsx", "src/dashboard-presentation.test.tsx", "src/onboarding-checklist.test.tsx", "src/daily-control-loop.test.tsx", "src/dashboard-week.test.tsx", "src/a3-navigation.test.tsx", "src/build-metadata-localisation.test.tsx", "src/translation-catalogue.test.ts", "src/i18n-provider.test.tsx". +Test results written to: ..\a3-dashboard-tests.json diff --git a/docs/ui-modernisation/evidence/dashboard/RED_TESTS.json b/docs/ui-modernisation/evidence/dashboard/RED_TESTS.json new file mode 100644 index 00000000..b3347d49 --- /dev/null +++ b/docs/ui-modernisation/evidence/dashboard/RED_TESTS.json @@ -0,0 +1 @@ +{"numFailedTestSuites":3,"numFailedTests":6,"numPassedTestSuites":0,"numPassedTests":40,"numPendingTestSuites":0,"numPendingTests":0,"numRuntimeErrorTestSuites":0,"numTodoTests":0,"numTotalTestSuites":3,"numTotalTests":46,"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":1793693300936,"success":false,"testResults":[{"assertionResults":[{"ancestorTitles":[],"duration":52,"failing":false,"failureDetails":[{"name":"TestingLibraryElementError"}],"failureMessages":["TestingLibraryElementError: Unable to find an accessible element with the role \"button\" and name \"Continue setup\"\n\nHere are the accessible roles:\n\n status:\n\n Name \"Destination\":\n \u001b[36m\u001b[39m\n\n --------------------------------------------------\n\nIgnored nodes: comments, script, style\n\u001b[36m\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0m/\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[36m
\u001b[39m\n\u001b[36m\u001b[39m\n at Object.getElementError (C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\node_modules\\@testing-library\\dom\\dist\\config.js:37:19)\n at C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\node_modules\\@testing-library\\dom\\dist\\query-helpers.js:76:38\n at C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\node_modules\\@testing-library\\dom\\dist\\query-helpers.js:52:17\n at C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\node_modules\\@testing-library\\dom\\dist\\query-helpers.js:95:19\n at Object.getByRole (C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\src\\onboarding-checklist.test.tsx:63:26)\n at Promise.finally.completed (C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\node_modules\\jest-circus\\build\\jestAdapterInit.js:1561:28)\n at new Promise ()\n at callAsyncCircusFn (C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\node_modules\\jest-circus\\build\\jestAdapterInit.js:1501:10)\n at _callCircusTest (C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\node_modules\\jest-circus\\build\\jestAdapterInit.js:1011:40)\n at _runTest (C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\node_modules\\jest-circus\\build\\jestAdapterInit.js:951:3)\n at C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\node_modules\\jest-circus\\build\\jestAdapterInit.js:853:7\n at _runTestsForDescribeBlock (C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\node_modules\\jest-circus\\build\\jestAdapterInit.js:866:11)\n at run (C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\node_modules\\jest-circus\\build\\jestAdapterInit.js:765:3)\n at runAndTransformResultsToJestFormat (C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\node_modules\\jest-circus\\build\\jestAdapterInit.js:1993:21)\n at jestAdapter (C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\node_modules\\jest-circus\\build\\runner.js:111:19)\n at runTestInternal (C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\node_modules\\jest-runner\\build\\index.js:276:16)\n at runTest (C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\node_modules\\jest-runner\\build\\index.js:344:7)"],"fullName":"a dismissed optional checklist resumes without losing source-backed progress","invocations":1,"location":null,"numPassingAsserts":1,"retryReasons":[],"startAt":1793693304214,"status":"failed","title":"a dismissed optional checklist resumes without losing source-backed progress"},{"ancestorTitles":[],"duration":144,"failing":false,"failureDetails":[{"name":"TestingLibraryElementError"}],"failureMessages":["TestingLibraryElementError: Unable to find an accessible element with the role \"button\" and name `/^Skip for now:/`\n\nHere are the accessible roles:\n\n region:\n\n Name \"Get set up\":\n \u001b[36m\u001b[39m\n\n --------------------------------------------------\n banner:\n\n Name \"\":\n \u001b[36m\u001b[39m\n\n --------------------------------------------------\n heading:\n\n Name \"Get set up\":\n \u001b[36m\u001b[39m\n\n --------------------------------------------------\n paragraph:\n\n Name \"\":\n \u001b[36m\u001b[39m\n\n Name \"\":\n \u001b[36m\u001b[39m\n\n Name \"\":\n \u001b[36m\u001b[39m\n\n Name \"\":\n \u001b[36m\u001b[39m\n\n Name \"\":\n \u001b[36m\u001b[39m\n\n --------------------------------------------------\n button:\n\n Name \"Dismiss setup checklist\":\n \u001b[36m\u001b[39m\n\n Name \"Add job\":\n \u001b[36m\u001b[39m\n\n --------------------------------------------------\n progressbar:\n\n Name \"3 of 4 done\":\n \u001b[36m\u001b[39m\n\n --------------------------------------------------\n list:\n\n Name \"\":\n \u001b[36m\u001b[39m\n\n --------------------------------------------------\n listitem:\n\n Name \"\":\n \u001b[36m\u001b[39m\n\n Name \"\":\n \u001b[36m\u001b[39m\n\n Name \"\":\n \u001b[36m\u001b[39m\n\n Name \"\":\n \u001b[36m\u001b[39m\n\n --------------------------------------------------\n img:\n\n Name \"1 of 1 done\":\n \u001b[36m\u001b[39m\n\n Name \"1 of 1 done\":\n \u001b[36m\u001b[39m\n\n Name \"1 of 1 done\":\n \u001b[36m\u001b[39m\n\n Name \"0 of 1 done\":\n \u001b[36m\u001b[39m\n\n --------------------------------------------------\n status:\n\n Name \"Destination\":\n \u001b[36m\u001b[39m\n\n --------------------------------------------------\n\nIgnored nodes: comments, script, style\n\u001b[36m\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mGet set up\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n\n --------------------------------------------------\n banner:\n\n Name \"\":\n \u001b[36m\u001b[39m\n\n --------------------------------------------------\n heading:\n\n Name \"Get set up\":\n \u001b[36m\u001b[39m\n\n --------------------------------------------------\n paragraph:\n\n Name \"\":\n \u001b[36m\u001b[39m\n\n Name \"\":\n \u001b[36m\u001b[39m\n\n Name \"\":\n \u001b[36m\u001b[39m\n\n Name \"\":\n \u001b[36m\u001b[39m\n\n Name \"\":\n \u001b[36m\u001b[39m\n\n --------------------------------------------------\n button:\n\n Name \"Dismiss setup checklist\":\n \u001b[36m\u001b[39m\n\n Name \"Add job\":\n \u001b[36m\u001b[39m\n\n --------------------------------------------------\n progressbar:\n\n Name \"3 of 4 done\":\n \u001b[36m\u001b[39m\n\n --------------------------------------------------\n list:\n\n Name \"\":\n \u001b[36m\u001b[39m\n\n --------------------------------------------------\n listitem:\n\n Name \"\":\n \u001b[36m\u001b[39m\n\n Name \"\":\n \u001b[36m\u001b[39m\n\n Name \"\":\n \u001b[36m\u001b[39m\n\n Name \"\":\n \u001b[36m\u001b[39m\n\n --------------------------------------------------\n img:\n\n Name \"1 of 1 done\":\n \u001b[36m\u001b[39m\n\n Name \"1 of 1 done\":\n \u001b[36m\u001b[39m\n\n Name \"1 of 1 done\":\n \u001b[36m\u001b[39m\n\n Name \"0 of 1 done\":\n \u001b[36m\u001b[39m\n\n --------------------------------------------------\n status:\n\n Name \"Destination\":\n \u001b[36m\u001b[39m\n\n --------------------------------------------------\n\nIgnored nodes: comments, script, style\n\u001b[36m\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mGet set up\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n\n --------------------------------------------------\n\n Ignored nodes: comments, script, style\n \u001b[36m\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0m/\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m\u001b[39m\n\n 61 | const view = render();\n 62 | expect(mockedApi.get).not.toHaveBeenCalled();\n > 63 | fireEvent.click(screen.getByRole('button', { name: 'Continue setup' }));\n | ^\n 64 | expect(await screen.findByText('3 / 4')).toBeInTheDocument();\n 65 | expect(localStorage.getItem('onboardingDismissed:owner-a')).toBe('false');\n 66 | view.unmount(); render();\n\n at Object.getElementError (node_modules/@testing-library/dom/dist/config.js:37:19)\n at node_modules/@testing-library/dom/dist/query-helpers.js:76:38\n at node_modules/@testing-library/dom/dist/query-helpers.js:52:17\n at node_modules/@testing-library/dom/dist/query-helpers.js:95:19\n at Object.getByRole (src/onboarding-checklist.test.tsx:63:26)\n\n ● individual skips persist for their owner but do not count as completed data\n\n TestingLibraryElementError: Unable to find an accessible element with the role \"button\" and name `/^Skip for now:/`\n\n Here are the accessible roles:\n\n region:\n\n Name \"Get set up\":\n \u001b[36m\u001b[39m\n\n --------------------------------------------------\n banner:\n\n Name \"\":\n \u001b[36m\u001b[39m\n\n --------------------------------------------------\n heading:\n\n Name \"Get set up\":\n \u001b[36m\u001b[39m\n\n --------------------------------------------------\n paragraph:\n\n Name \"\":\n \u001b[36m\u001b[39m\n\n Name \"\":\n \u001b[36m\u001b[39m\n\n Name \"\":\n \u001b[36m\u001b[39m\n\n Name \"\":\n \u001b[36m\u001b[39m\n\n Name \"\":\n \u001b[36m\u001b[39m\n\n --------------------------------------------------\n button:\n\n Name \"Dismiss setup checklist\":\n \u001b[36m\u001b[39m\n\n Name \"Add job\":\n \u001b[36m\u001b[39m\n\n --------------------------------------------------\n progressbar:\n\n Name \"3 of 4 done\":\n \u001b[36m\u001b[39m\n\n --------------------------------------------------\n list:\n\n Name \"\":\n \u001b[36m\u001b[39m\n\n --------------------------------------------------\n listitem:\n\n Name \"\":\n \u001b[36m\u001b[39m\n\n Name \"\":\n \u001b[36m\u001b[39m\n\n Name \"\":\n \u001b[36m\u001b[39m\n\n Name \"\":\n \u001b[36m\u001b[39m\n\n --------------------------------------------------\n img:\n\n Name \"1 of 1 done\":\n \u001b[36m\u001b[39m\n\n Name \"1 of 1 done\":\n \u001b[36m\u001b[39m\n\n Name \"1 of 1 done\":\n \u001b[36m\u001b[39m\n\n Name \"0 of 1 done\":\n \u001b[36m\u001b[39m\n\n --------------------------------------------------\n status:\n\n Name \"Destination\":\n \u001b[36m\u001b[39m\n\n --------------------------------------------------\n\n Ignored nodes: comments, script, style\n \u001b[36m\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mGet set up\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[36m {\n 72 | const view = render(); await screen.findByText('3 / 4');\n > 73 | fireEvent.click(screen.getByRole('button', { name: /^Skip for now:/ }));\n | ^\n 74 | expect(screen.getByRole('progressbar', { name: '3 of 4 done' })).toHaveAttribute('aria-valuenow', '75');\n 75 | expect(localStorage.getItem('onboardingSkipped:owner-a')).toBe('[\"/jobs\"]');\n 76 | view.unmount(); render(); await screen.findByText('3 / 4');\n\n at Object.getElementError (node_modules/@testing-library/dom/dist/config.js:37:19)\n at node_modules/@testing-library/dom/dist/query-helpers.js:76:38\n at node_modules/@testing-library/dom/dist/query-helpers.js:52:17\n at node_modules/@testing-library/dom/dist/query-helpers.js:95:19\n at Object.getByRole (src/onboarding-checklist.test.tsx:73:26)\n\n ● on-demand walkthrough changes guidance and focus without changing completion or connecting providers\n\n TestingLibraryElementError: Unable to find an accessible element with the role \"button\" and name \"Show walkthrough\"\n\n Here are the accessible roles:\n\n region:\n\n Name \"Get set up\":\n \u001b[36m\u001b[39m\n\n --------------------------------------------------\n banner:\n\n Name \"\":\n \u001b[36m\u001b[39m\n\n --------------------------------------------------\n heading:\n\n Name \"Get set up\":\n \u001b[36m\u001b[39m\n\n --------------------------------------------------\n paragraph:\n\n Name \"\":\n \u001b[36m\u001b[39m\n\n Name \"\":\n \u001b[36m\u001b[39m\n\n Name \"\":\n \u001b[36m\u001b[39m\n\n Name \"\":\n \u001b[36m\u001b[39m\n\n Name \"\":\n \u001b[36m\u001b[39m\n\n --------------------------------------------------\n button:\n\n Name \"Dismiss setup checklist\":\n \u001b[36m\u001b[39m\n\n Name \"Add job\":\n \u001b[36m\u001b[39m\n\n --------------------------------------------------\n progressbar:\n\n Name \"3 of 4 done\":\n \u001b[36m\u001b[39m\n\n --------------------------------------------------\n list:\n\n Name \"\":\n \u001b[36m\u001b[39m\n\n --------------------------------------------------\n listitem:\n\n Name \"\":\n \u001b[36m\u001b[39m\n\n Name \"\":\n \u001b[36m\u001b[39m\n\n Name \"\":\n \u001b[36m\u001b[39m\n\n Name \"\":\n \u001b[36m\u001b[39m\n\n --------------------------------------------------\n img:\n\n Name \"1 of 1 done\":\n \u001b[36m\u001b[39m\n\n Name \"1 of 1 done\":\n \u001b[36m\u001b[39m\n\n Name \"1 of 1 done\":\n \u001b[36m\u001b[39m\n\n Name \"0 of 1 done\":\n \u001b[36m\u001b[39m\n\n --------------------------------------------------\n status:\n\n Name \"Destination\":\n \u001b[36m\u001b[39m\n\n --------------------------------------------------\n\n Ignored nodes: comments, script, style\n \u001b[36m\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mGet set up\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[36m {\n 83 | render(); await screen.findByText('3 / 4');\n > 84 | fireEvent.click(screen.getByRole('button', { name: 'Show walkthrough' }));\n | ^\n 85 | const guide = screen.getByRole('region', { name: translations.en.onboardingStepProfile });\n 86 | expect(within(guide).getByRole('heading')).toHaveFocus();\n 87 | fireEvent.click(within(guide).getByRole('button', { name: 'Next step' }));\n\n at Object.getElementError (node_modules/@testing-library/dom/dist/config.js:37:19)\n at node_modules/@testing-library/dom/dist/query-helpers.js:76:38\n at node_modules/@testing-library/dom/dist/query-helpers.js:52:17\n at node_modules/@testing-library/dom/dist/query-helpers.js:95:19\n at Object.getByRole (src/onboarding-checklist.test.tsx:84:26)\n","name":"C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\src\\onboarding-checklist.test.tsx","startTime":1793693301807,"status":"failed","summary":""},{"assertionResults":[{"ancestorTitles":[],"duration":4068,"failing":false,"failureDetails":[{"name":"TestingLibraryElementError"}],"failureMessages":["Error: Unable to find role=\"region\" and name \"Your week\"\n\nIgnored nodes: comments, script, style\n\u001b[36m\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0m/\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[0mSetup retained\u001b[0m\n \u001b[36m
\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mtrue\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m)\n at callAsyncCircusFn (C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\node_modules\\jest-circus\\build\\jestAdapterInit.js:1501:10)\n at _callCircusTest (C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\node_modules\\jest-circus\\build\\jestAdapterInit.js:1011:40)\n at _runTest (C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\node_modules\\jest-circus\\build\\jestAdapterInit.js:951:3)\n at C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\node_modules\\jest-circus\\build\\jestAdapterInit.js:853:7\n at _runTestsForDescribeBlock (C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\node_modules\\jest-circus\\build\\jestAdapterInit.js:866:11)\n at run (C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\node_modules\\jest-circus\\build\\jestAdapterInit.js:765:3)\n at runAndTransformResultsToJestFormat (C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\node_modules\\jest-circus\\build\\jestAdapterInit.js:1993:21)\n at jestAdapter (C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\node_modules\\jest-circus\\build\\runner.js:111:19)\n at runTestInternal (C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\node_modules\\jest-runner\\build\\index.js:276:16)\n at runTest (C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\node_modules\\jest-runner\\build\\index.js:344:7)"],"fullName":"A1 week glance shows recorded upcoming dates from attention data without inventing missing dates","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793693306280,"status":"failed","title":"A1 week glance shows recorded upcoming dates from attention data without inventing missing dates"},{"ancestorTitles":[],"duration":152,"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":1793693310348,"status":"passed","title":"[PRES-S01] exact monthly values, sample, custom statuses and separate salary group remain readable"},{"ancestorTitles":[],"duration":713,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"[PRES-S02] all widget visibility choices save and survive remount","invocations":1,"location":null,"numPassingAsserts":8,"retryReasons":[],"startAt":1793693310500,"status":"passed","title":"[PRES-S02] all widget visibility choices save and survive remount"},{"ancestorTitles":[],"duration":123,"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":1793693311213,"status":"passed","title":"[PRES-S01] a trends failure retains summary and retries without showing a false zero response rate"},{"ancestorTitles":[],"duration":59,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"[PRES-S01] a summary failure does not suppress independent trends","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1793693311336,"status":"passed","title":"[PRES-S01] a summary failure does not suppress independent trends"},{"ancestorTitles":[],"duration":203,"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":1793693311395,"status":"passed","title":"[PRES-J05 J09 S01] raw status and company destinations use supported exact query fields"},{"ancestorTitles":[],"duration":195,"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":1793693311598,"status":"passed","title":"[PRES-S01 S02] period changes only reload trends and retain current snapshot meaning"},{"ancestorTitles":[],"duration":93,"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":1793693311793,"status":"passed","title":"[PRES-S01] zero activity is an explicit window-empty state without a fabricated response percentage"},{"ancestorTitles":[],"duration":76,"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":1793693311886,"status":"passed","title":"[PRES-S01] activity chart uses one shared zero-based scale and a readable legend"},{"ancestorTitles":[],"duration":148,"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":1793693311962,"status":"passed","title":"[P7-02 PRES-S01 S02] setup guidance treats pending and failed summaries as unknown until their owner retries"}],"endTime":1793693312114,"message":" ● A1 week glance shows recorded upcoming dates from attention data without inventing missing dates\n\n Unable to find role=\"region\" and name \"Your week\"\n\n Ignored nodes: comments, script, style\n \u001b[36m\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0m/\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m
\u001b[39m\n \u001b[0mSetup retained\u001b[0m\n \u001b[36m
\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mtrue\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m Promise.resolve({ data: url.endsWith('/reminders') ? jobs : data(url) }) as any);\n 41 | open();\n > 42 | const week = await screen.findByRole('region', { name: 'Your week' });\n | ^\n 43 | expect(within(week).getByText('School vacancy')).toBeVisible();\n 44 | expect(within(week).queryByText('No recorded date')).not.toBeInTheDocument();\n 45 | expect(within(week).getByRole('link', { name: /School vacancy/ })).toHaveAttribute('href', '/jobs/91');\n\n at waitForWrapper (node_modules/@testing-library/dom/dist/wait-for.js:163:27)\n at node_modules/@testing-library/dom/dist/query-helpers.js:86:33\n at Object.findByRole (src/dashboard-presentation.test.tsx:42:29)\n","name":"C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\src\\dashboard-presentation.test.tsx","startTime":1793693305524,"status":"failed","summary":""},{"assertionResults":[{"ancestorTitles":[],"duration":2,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"legacy preferences migrate to bounded geometry while preserving order, visibility and revision","invocations":1,"location":null,"numPassingAsserts":9,"retryReasons":[],"startAt":1793693312342,"status":"passed","title":"legacy preferences migrate to bounded geometry while preserving order, visibility and revision"},{"ancestorTitles":[],"duration":0,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"schema two preserves chosen hidden geometry without resetting the owner revision","invocations":1,"location":null,"numPassingAsserts":4,"retryReasons":[],"startAt":1793693312344,"status":"passed","title":"schema two preserves chosen hidden geometry without resetting the owner revision"},{"ancestorTitles":[],"duration":1,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"default catalogue preserves every section and supported widths","invocations":1,"location":null,"numPassingAsserts":4,"retryReasons":[],"startAt":1793693312344,"status":"passed","title":"default catalogue preserves every section and supported widths"},{"ancestorTitles":[],"duration":2,"failing":false,"failureDetails":[{"matcherResult":{"message":"expect(received).toMatchObject(expected)\n\n- Expected - 1\n+ Received + 1\n\n Object {\n- \"id\": \"attention\",\n+ \"id\": \"cards\",\n \"visible\": true,\n \"w\": 12,\n \"x\": 0,\n \"y\": 0,\n }","pass":false}}],"failureMessages":["Error: expect(received).toMatchObject(expected)\n\n- Expected - 1\n+ Received + 1\n\n Object {\n- \"id\": \"attention\",\n+ \"id\": \"cards\",\n \"visible\": true,\n \"w\": 12,\n \"x\": 0,\n \"y\": 0,\n }\n at toMatchObject (C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\src\\dashboardLayout.test.ts:43:29)\n at Object. (C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\node_modules\\jest-each\\build\\index.js:80:140)\n at Promise.finally.completed (C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\node_modules\\jest-circus\\build\\jestAdapterInit.js:1561:28)\n at new Promise ()\n at callAsyncCircusFn (C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\node_modules\\jest-circus\\build\\jestAdapterInit.js:1501:10)\n at _callCircusTest (C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\node_modules\\jest-circus\\build\\jestAdapterInit.js:1011:40)\n at _runTest (C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\node_modules\\jest-circus\\build\\jestAdapterInit.js:951:3)\n at C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\node_modules\\jest-circus\\build\\jestAdapterInit.js:853:7\n at _runTestsForDescribeBlock (C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\node_modules\\jest-circus\\build\\jestAdapterInit.js:866:11)\n at run (C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\node_modules\\jest-circus\\build\\jestAdapterInit.js:765:3)\n at runAndTransformResultsToJestFormat (C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\node_modules\\jest-circus\\build\\jestAdapterInit.js:1993:21)\n at jestAdapter (C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\node_modules\\jest-circus\\build\\runner.js:111:19)\n at runTestInternal (C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\node_modules\\jest-runner\\build\\index.js:276:16)\n at runTest (C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\node_modules\\jest-runner\\build\\index.js:344:7)"],"fullName":"new schema 1 preferences use task-first A1 defaults without omitting widgets","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793693312345,"status":"failed","title":"new schema 1 preferences use task-first A1 defaults without omitting widgets"},{"ancestorTitles":[],"duration":0,"failing":false,"failureDetails":[{"matcherResult":{"message":"expect(received).toMatchObject(expected)\n\n- Expected - 1\n+ Received + 1\n\n Object {\n- \"id\": \"attention\",\n+ \"id\": \"cards\",\n \"visible\": true,\n \"w\": 12,\n \"x\": 0,\n \"y\": 0,\n }","pass":false}}],"failureMessages":["Error: expect(received).toMatchObject(expected)\n\n- Expected - 1\n+ Received + 1\n\n Object {\n- \"id\": \"attention\",\n+ \"id\": \"cards\",\n \"visible\": true,\n \"w\": 12,\n \"x\": 0,\n \"y\": 0,\n }\n at toMatchObject (C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\src\\dashboardLayout.test.ts:43:29)\n at Object. (C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\node_modules\\jest-each\\build\\index.js:80:140)\n at Promise.finally.completed (C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\node_modules\\jest-circus\\build\\jestAdapterInit.js:1561:28)\n at new Promise ()\n at callAsyncCircusFn (C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\node_modules\\jest-circus\\build\\jestAdapterInit.js:1501:10)\n at _callCircusTest (C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\node_modules\\jest-circus\\build\\jestAdapterInit.js:1011:40)\n at _runTest (C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\node_modules\\jest-circus\\build\\jestAdapterInit.js:951:3)\n at C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\node_modules\\jest-circus\\build\\jestAdapterInit.js:853:7\n at _runTestsForDescribeBlock (C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\node_modules\\jest-circus\\build\\jestAdapterInit.js:866:11)\n at run (C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\node_modules\\jest-circus\\build\\jestAdapterInit.js:765:3)\n at runAndTransformResultsToJestFormat (C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\node_modules\\jest-circus\\build\\jestAdapterInit.js:1993:21)\n at jestAdapter (C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\node_modules\\jest-circus\\build\\runner.js:111:19)\n at runTestInternal (C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\node_modules\\jest-runner\\build\\index.js:276:16)\n at runTest (C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\node_modules\\jest-runner\\build\\index.js:344:7)"],"fullName":"new schema 2 preferences use task-first A1 defaults without omitting widgets","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793693312353,"status":"failed","title":"new schema 2 preferences use task-first A1 defaults without omitting widgets"},{"ancestorTitles":[],"duration":0,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"saved layouts retain known order, reject duplicates and append missing sections hidden","invocations":1,"location":null,"numPassingAsserts":5,"retryReasons":[],"startAt":1793693312354,"status":"passed","title":"saved layouts retain known order, reject duplicates and append missing sections hidden"},{"ancestorTitles":[],"duration":0,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"invalid input has safe defaults: null","invocations":1,"location":null,"numPassingAsserts":1,"retryReasons":[],"startAt":1793693312354,"status":"passed","title":"invalid input has safe defaults: null"},{"ancestorTitles":[],"duration":0,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"invalid input has safe defaults: {}","invocations":1,"location":null,"numPassingAsserts":1,"retryReasons":[],"startAt":1793693312355,"status":"passed","title":"invalid input has safe defaults: {}"},{"ancestorTitles":[],"duration":0,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"invalid input has safe defaults: {\"version\":3,\"revision\":8,\"widgets\":[]}","invocations":1,"location":null,"numPassingAsserts":1,"retryReasons":[],"startAt":1793693312355,"status":"passed","title":"invalid input has safe defaults: {\"version\":3,\"revision\":8,\"widgets\":[]}"},{"ancestorTitles":[],"duration":0,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"invalid input has safe defaults: {\"version\":1,\"revision\":-1,\"widgets\":[]}","invocations":1,"location":null,"numPassingAsserts":1,"retryReasons":[],"startAt":1793693312355,"status":"passed","title":"invalid input has safe defaults: {\"version\":1,\"revision\":-1,\"widgets\":[]}"}],"endTime":1793693312359,"message":" ● new schema 1 preferences use task-first A1 defaults without omitting widgets\n\n expect(received).toMatchObject(expected)\n\n - Expected - 1\n + Received + 1\n\n Object {\n - \"id\": \"attention\",\n + \"id\": \"cards\",\n \"visible\": true,\n \"w\": 12,\n \"x\": 0,\n \"y\": 0,\n }\n\n 41 | test.each([1, 2])('new schema %s preferences use task-first A1 defaults without omitting widgets', version => {\n 42 | const layout = normalizeDashboardLayout({ version, revision: 0, widgets: [] });\n > 43 | expect(layout.widgets[0]).toMatchObject({ id: 'attention', visible: true, x: 0, y: 0, w: 12 });\n | ^\n 44 | expect(layout.widgets.findIndex(widget => widget.id === 'cards')).toBeGreaterThan(layout.widgets.findIndex(widget => widget.id === 'funnel'));\n 45 | expect(layout.widgets).toHaveLength(WIDGETS.length);\n 46 | const visible = layout.widgets.filter(widget => widget.visible);\n\n at toMatchObject (src/dashboardLayout.test.ts:43:29)\n\n ● new schema 2 preferences use task-first A1 defaults without omitting widgets\n\n expect(received).toMatchObject(expected)\n\n - Expected - 1\n + Received + 1\n\n Object {\n - \"id\": \"attention\",\n + \"id\": \"cards\",\n \"visible\": true,\n \"w\": 12,\n \"x\": 0,\n \"y\": 0,\n }\n\n 41 | test.each([1, 2])('new schema %s preferences use task-first A1 defaults without omitting widgets', version => {\n 42 | const layout = normalizeDashboardLayout({ version, revision: 0, widgets: [] });\n > 43 | expect(layout.widgets[0]).toMatchObject({ id: 'attention', visible: true, x: 0, y: 0, w: 12 });\n | ^\n 44 | expect(layout.widgets.findIndex(widget => widget.id === 'cards')).toBeGreaterThan(layout.widgets.findIndex(widget => widget.id === 'funnel'));\n 45 | expect(layout.widgets).toHaveLength(WIDGETS.length);\n 46 | const visible = layout.widgets.filter(widget => widget.visible);\n\n at toMatchObject (src/dashboardLayout.test.ts:43:29)\n","name":"C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\src\\dashboardLayout.test.ts","startTime":1793693312124,"status":"failed","summary":""}],"wasInterrupted":false} diff --git a/docs/ui-modernisation/evidence/dashboard/SOURCE_MANIFEST.json b/docs/ui-modernisation/evidence/dashboard/SOURCE_MANIFEST.json new file mode 100644 index 00000000..5f6b771f --- /dev/null +++ b/docs/ui-modernisation/evidence/dashboard/SOURCE_MANIFEST.json @@ -0,0 +1,156 @@ +{ + "recordedAtUtc": "2026-11-03T08:45:39.150316+00:00", + "base": "5e8a07d1", + "policy": "Text LF-normalised SHA-256; PNG bytes raw", + "sources": { + "JobTrackerApi.Tests/DashboardPreferencesTests.cs": "b004e3048b59de954915b1a051f34adcb934e87296a9ef8527e4107b8d5cc842", + "JobTrackerApi/Models/DashboardLayout.cs": "33d66ad0988c3d132f909108a98b8bbde067606b3acd591dc84947f97bab7cac", + "docs/pre-phase10-ux/preservation.json": "a4c2a5a0244ea89381008218de32daee1f615a250eb3808b60e0ee103c674d4d", + "job-tracker-ui/package-lock.json": "bf31e90d7af03e8af059db2aaf893925b9e8881a30a1688034ed88ef45d4fcab", + "job-tracker-ui/package.json": "3745394e406cefa40d30a87e840bccb359be147bac6ffba736e2ae90898a8626", + "job-tracker-ui/scripts/verify-a3-dashboard.mjs": "30abaf916ad7dd2d66b9068ea469bfc1b8ac2895ea1b9893c4fdb25ae2908a7a", + "job-tracker-ui/src/App.tsx": "608b4bed6c7e2881dfd65b7541ea211d193a636dd3f9cdf9a6d172acd12d137d", + "job-tracker-ui/src/build-metadata-localisation.test.tsx": "065c5550a18f7b3c7dca9bbb26c5564a53a53eaf389e7c9695596fcbb2c06f6e", + "job-tracker-ui/src/components/DashboardGrid.test.tsx": "423ba80f06cc92ace630a731a5536fc74256f921e2caa29d6b0dffd6d62513bc", + "job-tracker-ui/src/components/DashboardGrid.tsx": "1f9dfb5dc17b7804c00cca823e7045ec1804d3bcf162bb56590bcd3cf85358d8", + "job-tracker-ui/src/components/DashboardView.tsx": "21be089f43dfaec5ac5469c36b4610559421f958f52613b4deadbdaa12eadf4f", + "job-tracker-ui/src/components/DashboardWeek.tsx": "12ba41a7df41da9ea9e964c594ebb7f2c5364b4a7c523966912f31a048407340", + "job-tracker-ui/src/components/OnboardingChecklist.tsx": "78b3112465fb62c381c48e32253763f22b6f2034fbcd23c27faeaa0dddf380e6", + "job-tracker-ui/src/dashboard-customisation.test.tsx": "9fbdf02f6f4815b7d98e32c8bb295a72dafc552118567eebbd885cd1b1d5be94", + "job-tracker-ui/src/dashboard-presentation.test.tsx": "23743259e498bc525cb6e228d71ddb9a16e95868b1ddf93fdb19ec5a295ed91b", + "job-tracker-ui/src/dashboard-week.test.tsx": "a6d6fd1931057428695be595a4dc344e70ae4ac8dc95a0551e7dccb0201e4cdc", + "job-tracker-ui/src/dashboardLayout.test.ts": "4ec561778ee9cedd5fb578d18822124d38caa2357eef749eb32486a2922dddb3", + "job-tracker-ui/src/dashboardLayout.ts": "eead1fb296d50d914cd6ff6bea6c1411dd906ec4cbd96fd8ec378c2a1b9645f3", + "job-tracker-ui/src/hooks/useDashboardLayout.test.tsx": "4937c40959487ab39f98036d9c674fbb149bb26d2728fcf4a80096e7fdaf95a3", + "job-tracker-ui/src/hooks/useDashboardLayout.ts": "fa47ca4fb8aaacbc62641f5adca4120fa057e78290791cd62fad1844b2f46314", + "job-tracker-ui/src/i18n/translations.ts": "5e2b85153e329d2e6f30deaa572774248fa5d784e7b9f922e46590b41a235360", + "job-tracker-ui/src/layout/AppShell.tsx": "33dc40054ce390cc532e3bc51d6301795359f4105bac38b2dcdf2d17b3f4b917", + "job-tracker-ui/src/onboarding-checklist.test.tsx": "614d1be16911f87e12ac1ecd47da2aa3e914446f533dbafeb6cf48ea8873ac28" + }, + "evidence": { + "backend/a3-agenda-green.trx": "52d94de21ca4281761f40f554f0cec2db1641c6f4c7a06228e96ae5d4f006424", + "backend/a3-agenda-red.trx": "d3e24c227961a11d093627432b199edcaf3cc3916564b06c0e7350a67dff74a6", + "backend/a3-dashboard-preferences.trx": "ee82d831e6964085b6724bc963f9cc8302828e88a2c8998adbe6e72eab749e66", + "BACKEND.log": "82b2fb95acd3df170cba7ca1dd22b1a0d02f33da9a9dce53150a38d447c0fc91", + "BASELINE.json": "6d377505929caa1c20f42db8943cadf37b6c434ebdd5593282f943a45622787c", + "browser/BROWSER.json": "0512d68cf0a905fe47914be79130e4618cb094886d3a9274580a1ec0e1cd1bbf", + "browser/dashboard-en-dark-1440.png": "26f801f6f3d3026967cf7880718fbd899777b1ae3ca58458493375423921fdf1", + "browser/dashboard-en-dark-320.png": "107d019772aad51e6f167c4214ccbb34e67b1b870e51033a5bd1029579d25197", + "browser/dashboard-en-dark-390.png": "9bfceda63388d510198ef3c9756821579fa762d679e949c9684fd2ee9b080184", + "browser/dashboard-en-dark-768.png": "31cd55d7624b197379fe4aff64a216bb3642626ef9910e75ecfc0d71dfcc7634", + "browser/dashboard-en-light-1440.png": "e9fd3bd714a041a28b08174a58eaf24655a089847a369589ef1e30b3e8ed7a51", + "browser/dashboard-en-light-320.png": "70453cc4bec711728b485d8137278afe83075ae7356c276e32e09dbeacf65112", + "browser/dashboard-en-light-390.png": "8e241fca5fc25a985ce4c7301421203a1e11057eeb7c250615fc99557d8c1e6f", + "browser/dashboard-en-light-768.png": "e4e1c3e607c271cfb696e905f567cab40b3f48724dcc41ad3f85d92172b17aa8", + "browser/dashboard-nb-dark-1440.png": "8fcaf9ca2c381386b74f1c09510021c3fae504681e0902f5f9c0d67fdd2767e4", + "browser/dashboard-nb-dark-320.png": "391485db740a2b4fd2804976de2e783b799f3d07e04944b04ab301287b75b95d", + "browser/dashboard-nb-dark-390.png": "d674e5f1659176f1a6848fd2157ae924bd46e4848cb76a2e5be633b5cf58f452", + "browser/dashboard-nb-dark-768.png": "5441d3ae7f259acec39d43bd667eeb40fa43fbed9c6696b159466e11f7ae4dfd", + "browser/dashboard-nb-light-1440.png": "7efe3c549ffeda656b96f35e2f0a130123016072426d4123279104f0eb6bc5a3", + "browser/dashboard-nb-light-320.png": "f1c6e184b98ae78e254a9dc862814b542e98fe3da4211d1eaf842d622b7b33fc", + "browser/dashboard-nb-light-390.png": "ddc3aa03ec7246e5f6fdabfabe14006cb0ab43e972a2b396b2525111cd54c69c", + "browser/dashboard-nb-light-768.png": "7ae34f94766577fa157239743f06e74532dfb2f2c6b65824536797bb7e8936f3", + "browser/walkthrough-en-dark-1440.png": "f97eb34cf0591806945f6cd80e2019108ae3ed5bac1705183b759c183ea3af0d", + "browser/walkthrough-en-dark-320.png": "198c32c54248d707fb1be5c69a97c0ba9ecaaa6aa75a1f852952429a8a9b0d2a", + "browser/walkthrough-en-dark-390.png": "9b3c139868d5ddf9109ec90de11df11d92d45febf2f7adfdf0787e8bc242e18c", + "browser/walkthrough-en-dark-768.png": "177f5dbda30660cfa3df91e743c33f6fa711c2095893ebbff84bcf9ae0bd4435", + "browser/walkthrough-en-light-1440.png": "29f2ddbfc63a972dbd820dc98b4fc3d96ca4028775194cd114be3ea6ff65f345", + "browser/walkthrough-en-light-320.png": "f93ce4c54ca7d47115fd165894a973416e2be5bfd6eef139a89726ba347d8616", + "browser/walkthrough-en-light-390.png": "1ac2a2ab2cbbddde25650a774cfd2676a75fd622982bb0ed6e52c1209ae0cce1", + "browser/walkthrough-en-light-768.png": "87255e8aaaca5c86905bd2e0630b471fc08d06f09048840c6b1cee894809c222", + "browser/walkthrough-nb-dark-1440.png": "e63cb7c6a0a1e52a36a9b3ee6c72e0bed357d3fa08d0f941e389003aa20a3180", + "browser/walkthrough-nb-dark-320.png": "fc76e2a08c43d7f00a1ee0abea50ca293f17a21fea1c4dc649ab23f410ee808b", + "browser/walkthrough-nb-dark-390.png": "899de4dd2b459a6a531fab9273ef31f22270fd5c85bfb1bd01def2f69bbc6432", + "browser/walkthrough-nb-dark-768.png": "5450dbf4ec37d9ab87f46960aac64fbe22ef6025e936fe1e8554da77c51eb7cb", + "browser/walkthrough-nb-light-1440.png": "c8f0e8d865a625b777de51f6aacdfea74a5574af12c3ad3ee17815f6cd502fd7", + "browser/walkthrough-nb-light-320.png": "f99975a21c78784903fe4617c35dd652b50cd213a657d285eb2c51ea10097396", + "browser/walkthrough-nb-light-390.png": "0fc7b535dedc1377bde7752da29ce964748c38d62132b854f68c7625325a09e6", + "browser/walkthrough-nb-light-768.png": "f33c8360bf48f7eaf433290a197fb5e33389c64bb156b0786e908242c9ed4e30", + "CHECKS.json": "e4caec33a024c9e48aa21f8d321a86c300a9b63d03a3a1558a33fbd2c689e2cd", + "FINAL_TESTS.json": "6a4c174751d1d7576e598c3e74e2610746ed2ec626c742cd47e2ec76b6dfca03", + "FRONTEND.log": "cdc842bfcdd36b05e02b252a4e56f9030b3734b152f3b2ef5704891fd3d5e787", + "RED_TESTS.json": "30c09b48f503ae331ed6fcce2949d5d794bbde7cbd8d9470481240642c2a8e2a" + }, + "export": { + "404.html": "9967148f8c10546f2e6995f66ec20f8a42691c6f2a48ea9edd5ac5054332ed7a", + "__next.__PAGE__.txt": "51c3cd8bc84d9a73c34a8b03cec6a258a0597424a835b731f653e00f520d634f", + "__next._full.txt": "43eaf678423ad1afc243b9f65ee264868b38e86080c68acfb49ddb6a82aeed94", + "__next._tree.txt": "829be44f5ac7f37509dd83f0a1fa7f055e7250bd8ec2f81ab3ee0197727b1d04", + "_next/static/AOeRuze6s4O9UcClb3rJx/_buildManifest.js": "9476e0afd3f9da68a64e6ec58631191fd3eaca62c9f5a377e7517fe9a89e59ea", + "_next/static/AOeRuze6s4O9UcClb3rJx/_clientMiddlewareManifest.js": "5483e3295dc35dfe29ea932efc85341c916f10ff78ff5b0e965e003710893a4a", + "_next/static/AOeRuze6s4O9UcClb3rJx/_ssgManifest.js": "678f6ce2cb80b1fe72fc67e7412be6e2ab6ada083111b64f7c40d35e3cba5e00", + "_next/static/chunks/0-_aq6ipzcshj.js": "39f90c215bf52aef6274ed3dcc0f8af32e21a37fe8845b3bca0d6ea60da6a8c2", + "_next/static/chunks/07vdc-duql714.js": "5a4840d1ba6b65782459968940a2f4cf973c99f0f77890f4171c4f420f339353", + "_next/static/chunks/0bdt7ln111t48.js": "6184b1a61f22e2a640c17941634fdc75678ce93d142848d9f07f163e0d1fd201", + "_next/static/chunks/0bwvt3mysnclc.js": "7705667a1b75812b7833d9c9dff47fe023015e58e382c40f4507d9948c973af4", + "_next/static/chunks/0cz1d0mv5g_q7.js": "0973c1d64c88adc8e3c950410cb58b288f72118d5965b78049438deb8f2f9683", + "_next/static/chunks/0enhs8x_8oeah.js": "44c76f6178d407e1f147fdf70015fd05c21e58ed220fc922d23dfd1e0f8631bd", + "_next/static/chunks/0id-zhx_m2eg0.js": "3f1fd8f0e52a8b68a022f53fb9546eb1c06700950485b2932a36d93bec3629a9", + "_next/static/chunks/0jcvc3qm6-lyk.js": "7c27e1fe373b822adc68c63096a5bf38444ad605b9743f0920271652989c56f1", + "_next/static/chunks/0q20g9o8x-k8z.js": "3d4d9086985419aa8866b032158196c774acb8bf318f3c8245508c8daa9c5148", + "_next/static/chunks/0q_pwu9jxv4-d.js": "42a43b3d7245a413c7c29b4cbe87823037a6dbc637e2e282c92405470e0f575d", + "_next/static/chunks/0vlr_s464rzwd.js": "b404d85296c25066c63fd7abf499ebf22c0021bede6f1d0f459c6bb8fbedcea1", + "_next/static/chunks/11-48p8kygxgp.js": "57eb9059599b5a5a30598d0a334912d58af571c0ffe28448ca76ad77c8ea8df9", + "_next/static/chunks/14-lfugawba2w.js": "6a5780ca3c84d7f130f3c0a14431c85c7a1e799e2645b8c171161dab40a64360", + "_next/static/chunks/1435in_k_-dhc.js": "4757476512d5c4dd25f9264a18b304ed4824b23aff1c09e134fcbff21d9c36b6", + "_next/static/chunks/145oicobmjvm7.js": "d6bbbdeed1381652dc7412e5ce673a077579ecd5cee8934cb6b533c814098764", + "_next/static/chunks/14e7ovj0s3elm.js": "070567dd4d23fb9fa0b9ba132185a697af6578045d43b2b1c7bbb2a1594d935d", + "_next/static/chunks/154b5a6du9olf.js": "49e4df3a3e10119ddc34f84767883b9228ff6798a07fd759c2fb8e2be62fe9fb", + "_next/static/chunks/16lfece6cnlln.js": "716363ba044f30b0e70440756b4e5eafa2d25f2e1628e855e3bc4ab9eabee3d3", + "_next/static/chunks/1d0xrdyyzw7qc.js": "c11668ff1e152008e0d7b4454af12eb36c97df46a5860938e2eb5d8429b374a8", + "_next/static/chunks/1h7j7ef9e6i41.js": "cb6cd497566dc188b67d09b9c031ef9bbf36f2b85d0aac02621f56daa44faa34", + "_next/static/chunks/1ke8pzecwofrg.css": "32e9ab963ebadebcbe2c68dffb24cfbfc8cc24255ad91b48b7941fc777bf7439", + "_next/static/chunks/1m3dt58dmprbw.js": "3a80b00707e6a884a03fa636260511585f1760f37c24fd4861ead110e6b3e985", + "_next/static/chunks/1req80b9jj38r.js": "eee4d2e3789e1215195dba99ad0f09d34f8c83da848322b76054722233e9dae3", + "_next/static/chunks/1w3q8yrcjn2jk.js": "365200e241ee9290e93d7c2fdba169f2b0a8e774541e3cbf3f6f2781110fa644", + "_next/static/chunks/1xn023_28rgzj.js": "4f7f2108d030a48d039930736c92a55710975dddf419fe673f84d92899b1f575", + "_next/static/chunks/1xwc-b795syag.css": "f73c002233f931ca14d0ae58815c47ca9f2659f25b1547a6d1fdca12a2a00b3f", + "_next/static/chunks/1zi8i6dnxiac_.js": "8aea28bbe6a138e5d4b619429ca71570883a7aee974b86d5a4a746d90f704dbc", + "_next/static/chunks/22uh2tvaw80j2.js": "dafe6b4e23dc5c1d32f8b4a60c561238d2d5e7545f23ebc5b3f7304b48626dbf", + "_next/static/chunks/22wx8--1b9tnq.js": "3f7d8e762371262c6a740b4dac21725b424847319afac46055e97074a99ff5f5", + "_next/static/chunks/245ibf0f31ne8.js": "8b27ab5d720bd278b5d73a7e824bfcccf4cc893ced9fa6f1937bb2bc7a65f967", + "_next/static/chunks/280cq9ekejv1f.js": "01de8c1c3555cdd8d711763065ea9d8aec2968c3053f61254e43e26c999027bf", + "_next/static/chunks/2br3ad_-bc7k9.js": "80fa55f7a32db4d743fda36747c36994fde00a0f53f2eb25b2a6d07c6fb97cd7", + "_next/static/chunks/2gd9s_n-0nl1p.js": "31e2811be51a3c4d0ff2d1e3cea756e7fd8d57ef89141b5de69f638c1301d039", + "_next/static/chunks/2ghgf2s9b2iou.js": "23d8ff31e2128d0541c1887785fb75885323fea7c5d4f35826513b75c12c2ae3", + "_next/static/chunks/2j9f-6w2x4auf.js": "a23068fc68514dcaa1375de8802bf7f9bdfc561835e4fe47b9e6fe8f7372f85c", + "_next/static/chunks/2s2gjr6ro25dj.js": "7489e2ac285a05b563c2bcb4eaf9274bf930f2a7385f17d6d5e6850064b88b10", + "_next/static/chunks/2sjyagpqg_e_b.js": "17b0602a3b95b7776a76a48dc33803d45785586344ad28e480d21cc3acdf0318", + "_next/static/chunks/2u5qve004opku.js": "be388d9a6aa423082bafb4584c3facc65ddfed71bcfa5c1d06b097b408c5ff97", + "_next/static/chunks/2wae6urahmel1.js": "ae86575883781981cc373601a07009101d81eba801f40d4607e6f3f168777a5b", + "_next/static/chunks/2xlg4_1fj8fxh.js": "8ab4ba521db0167f3dbf684787ebf643cbc66fa7ffff7cdc65a8d8076a926155", + "_next/static/chunks/2y9p90vb01dn0.js": "d87e69ba315676b2c478b5c69d6716eff5b533cb346cf07e8e462e721a08e2b1", + "_next/static/chunks/2z42el0b2mp8_.js": "86ff990ef32c90c4839583e4f1d1bcbddb165dad3141b9fcd2ecec67f4ff24b8", + "_next/static/chunks/334vtqp3-g1j6.js": "c33d42da97f3af7388e29273297e9f42c67f54b7f5ec77a5d0637e2da06d7f7e", + "_next/static/chunks/3720519r84mh6.js": "54358db392a13914e3363046d594e824b1a3ef987913d95002bf48267dded4aa", + "_next/static/chunks/3bqn9b34na0b9.js": "f1b8ebcc1b22251c33c9f3a5d11c1f893cad2da2e8744a441e790f7347aa9e73", + "_next/static/chunks/3ckk_j5ril9ag.js": "87f631dae7b648d32171e9e05a3e000753ecb54f8d3b86a4401c0e4ab4d73553", + "_next/static/chunks/3f96r6vscobou.js": "d4b452ff3f1433f8fe1df705a8f592326f51b5a8846e6c2790d28df5a977aed3", + "_next/static/chunks/3gkylew_qn7cw.js": "7e6c74c81be0c41ab433e36d65b914f62c48f332922e27f7cc953476f4dd78cb", + "_next/static/chunks/3mowdlh-jq9ii.js": "2c6ccd1500d28ec0c7312cb08ee221f0e4184ba21465ee166a054aa9a6c7f3ed", + "_next/static/chunks/3os11ovm65tyl.js": "c5a908bd61f53f0d75927efb71e53aef8fdd9b2e4fef412a167990dbc702c166", + "_next/static/chunks/3q4v10rrqy40n.js": "d87ff7b7a0f07d0d92eab3876481eb686a12d9c43d8b92cbe406aefa227a11c1", + "_next/static/chunks/3q80eur194l-9.js": "7b52d29843c23a0ed74056eeb1704de14488f36e9d05f2f8271434c61051dce0", + "_next/static/chunks/3u61o8t7m_jza.js": "86231c12e0f8cacc8638bed0bc4b34f645fc35e60de342dc154d3cce0c9a5afa", + "_next/static/chunks/40y_aqqilokr2.js": "1f5a5260316134d6bffe949400752ee6b42addf908ae714ef4ae4ebde916dddd", + "_next/static/chunks/43b5x8qjjh3ax.js": "6ee7261cf9a63e0e6ea181bbb09125e09948588b6e10b5c02727c686a6062a3a", + "_next/static/chunks/442d7ylb3po0z.js": "d95cedfbdcd00c674fd718cbbf391ec01c0728018b0db2461a06df7074049132", + "_next/static/chunks/turbopack-25fzg9ay_1ss-.js": "c40f39e993a30c96d6247bb3a7b581538bb046efe8ef8ec07bea3ba32291750f", + "_not-found/__next._full.txt": "f4aebb067159d7ee927c964a4864a19495a4b91405ac39b70510a0c5bb6b8bbb", + "_not-found/__next._not-found/__PAGE__.txt": "803da6910a8f775def309668e8f924404853a0c473d74866b006fbe1f91905b3", + "_not-found/__next._tree.txt": "33cb524cf6cbd29a5e6a5063bce32695613363d733e01c69b7738703a532d66b", + "_not-found.html": "9967148f8c10546f2e6995f66ec20f8a42691c6f2a48ea9edd5ac5054332ed7a", + "_not-found.txt": "f4aebb067159d7ee927c964a4864a19495a4b91405ac39b70510a0c5bb6b8bbb", + "favicon.ico": "8d4923e2c487805ea9f852a685b2cf0fb10512ded8290d2a5c37d1cc8fd0702a", + "favicon.svg": "63ecf8f37f8bb146ecfae3917187c6b692d4e801641e8a314486a4007912f3a3", + "index.html": "bf511f325131b975ac8ccb33eb927584226460ad3508701a52a0bf3c05b5d9a7", + "index.txt": "43eaf678423ad1afc243b9f65ee264868b38e86080c68acfb49ddb6a82aeed94", + "logo192.png": "bc652031597fabd19b9f14e22a71f809490d218037521b3b64d013418c1390b4", + "logo512.png": "5129e9bc1ba812b711fd9a646d3a38a906d365a26ee685df183b8752458944d4", + "manifest.json": "2667ce63c2e0835b7007239816a5597639321d333da396a78638dfa9e8a7c935", + "mockups/dashboard.svg": "318be3adb1324c26eb30f05572a29528a3e38331c6a604652eef75b54f8c29dc", + "mockups/pipeline.svg": "db2ef41faabeb90e7ce85413e168581686c54535d970fc7d8deda3888a479de4", + "mockups/workspace.svg": "9bbab22349147c7b01d6e18c93c622a6f8aed6dedaf23715189ab54d60e1ddbd", + "robots.txt": "e5ab0d231eeb01b4a982d1c79a6729cac9797ad15a69247e4f28ba6afc149b4c" + } +} diff --git a/docs/ui-modernisation/evidence/dashboard/backend/a3-agenda-green.trx b/docs/ui-modernisation/evidence/dashboard/backend/a3-agenda-green.trx new file mode 100644 index 00000000..a8112fc6 --- /dev/null +++ b/docs/ui-modernisation/evidence/dashboard/backend/a3-agenda-green.trx @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + [xUnit.net 00:00:00.00] xUnit.net VSTest Adapter v2.8.2+699d445a1a (64-bit .NET 9.0.19) +[xUnit.net 00:00:00.21] Discovering: JobTrackerApi.Tests +[xUnit.net 00:00:00.37] Discovered: JobTrackerApi.Tests +[xUnit.net 00:00:00.37] Starting: JobTrackerApi.Tests +[xUnit.net 00:00:00.41] Finished: JobTrackerApi.Tests + + + + \ No newline at end of file diff --git a/docs/ui-modernisation/evidence/dashboard/backend/a3-agenda-red.trx b/docs/ui-modernisation/evidence/dashboard/backend/a3-agenda-red.trx new file mode 100644 index 00000000..11312fbb --- /dev/null +++ b/docs/ui-modernisation/evidence/dashboard/backend/a3-agenda-red.trx @@ -0,0 +1,53 @@ + + + + + + + + + + + Assert.NotNull() Failure: Value is null + at JobTrackerApi.Tests.DashboardPreferencesTests.Agenda_widget_uses_existing_schema_two_without_weakening_unknown_id_or_overlap_checks() in C:\Users\Connor\.codex\worktrees\direct-roadmap-integration\Job tracker\JobTrackerApi.Tests\DashboardPreferencesTests.cs:line 36 + at System.RuntimeMethodHandle.InvokeMethod(Object target, Void** arguments, Signature sig, Boolean isConstructor) + at System.Reflection.MethodBaseInvoker.InvokeWithNoArgs(Object obj, BindingFlags invokeAttr) + + + + + + + + + + + + + + + + + + + + + [xUnit.net 00:00:00.00] xUnit.net VSTest Adapter v2.8.2+699d445a1a (64-bit .NET 9.0.19) +[xUnit.net 00:00:00.50] Discovering: JobTrackerApi.Tests +[xUnit.net 00:00:00.64] Discovered: JobTrackerApi.Tests +[xUnit.net 00:00:00.64] Starting: JobTrackerApi.Tests +[xUnit.net 00:00:00.77] Assert.NotNull() Failure: Value is null +[xUnit.net 00:00:00.77] Stack Trace: +[xUnit.net 00:00:00.77] C:\Users\Connor\.codex\worktrees\direct-roadmap-integration\Job tracker\JobTrackerApi.Tests\DashboardPreferencesTests.cs(36,0): at JobTrackerApi.Tests.DashboardPreferencesTests.Agenda_widget_uses_existing_schema_two_without_weakening_unknown_id_or_overlap_checks() +[xUnit.net 00:00:00.77] at System.RuntimeMethodHandle.InvokeMethod(Object target, Void** arguments, Signature sig, Boolean isConstructor) +[xUnit.net 00:00:00.77] at System.Reflection.MethodBaseInvoker.InvokeWithNoArgs(Object obj, BindingFlags invokeAttr) +[xUnit.net 00:00:00.78] Finished: JobTrackerApi.Tests + + + + + [xUnit.net 00:00:00.77] JobTrackerApi.Tests.DashboardPreferencesTests.Agenda_widget_uses_existing_schema_two_without_weakening_unknown_id_or_overlap_checks [FAIL] + + + + \ No newline at end of file diff --git a/docs/ui-modernisation/evidence/dashboard/backend/a3-dashboard-preferences.trx b/docs/ui-modernisation/evidence/dashboard/backend/a3-dashboard-preferences.trx new file mode 100644 index 00000000..c5534873 --- /dev/null +++ b/docs/ui-modernisation/evidence/dashboard/backend/a3-dashboard-preferences.trx @@ -0,0 +1,244 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + [xUnit.net 00:00:00.00] xUnit.net VSTest Adapter v2.8.2+699d445a1a (64-bit .NET 9.0.19) +[xUnit.net 00:00:00.11] Discovering: JobTrackerApi.Tests +[xUnit.net 00:00:00.24] Discovered: JobTrackerApi.Tests +[xUnit.net 00:00:00.24] Starting: JobTrackerApi.Tests +[xUnit.net 00:00:36.75] Finished: JobTrackerApi.Tests + + + + \ No newline at end of file diff --git a/docs/ui-modernisation/evidence/dashboard/browser/BROWSER.json b/docs/ui-modernisation/evidence/dashboard/browser/BROWSER.json new file mode 100644 index 00000000..3fbb7f0e --- /dev/null +++ b/docs/ui-modernisation/evidence/dashboard/browser/BROWSER.json @@ -0,0 +1,512 @@ +{ + "screens": [ + { + "width": 320, + "language": "en", + "mode": "light", + "violations": [] + }, + { + "width": 320, + "language": "en", + "mode": "light", + "state": "walkthrough", + "violations": [] + }, + { + "width": 320, + "language": "en", + "mode": "dark", + "violations": [] + }, + { + "width": 320, + "language": "en", + "mode": "dark", + "state": "walkthrough", + "violations": [] + }, + { + "width": 320, + "language": "nb", + "mode": "light", + "violations": [] + }, + { + "width": 320, + "language": "nb", + "mode": "light", + "state": "walkthrough", + "violations": [] + }, + { + "width": 320, + "language": "nb", + "mode": "dark", + "violations": [] + }, + { + "width": 320, + "language": "nb", + "mode": "dark", + "state": "walkthrough", + "violations": [] + }, + { + "width": 390, + "language": "en", + "mode": "light", + "violations": [] + }, + { + "width": 390, + "language": "en", + "mode": "light", + "state": "walkthrough", + "violations": [] + }, + { + "width": 390, + "language": "en", + "mode": "dark", + "violations": [] + }, + { + "width": 390, + "language": "en", + "mode": "dark", + "state": "walkthrough", + "violations": [] + }, + { + "width": 390, + "language": "nb", + "mode": "light", + "violations": [] + }, + { + "width": 390, + "language": "nb", + "mode": "light", + "state": "walkthrough", + "violations": [] + }, + { + "width": 390, + "language": "nb", + "mode": "dark", + "violations": [] + }, + { + "width": 390, + "language": "nb", + "mode": "dark", + "state": "walkthrough", + "violations": [] + }, + { + "width": 768, + "language": "en", + "mode": "light", + "violations": [] + }, + { + "width": 768, + "language": "en", + "mode": "light", + "state": "walkthrough", + "violations": [] + }, + { + "width": 768, + "language": "en", + "mode": "dark", + "violations": [] + }, + { + "width": 768, + "language": "en", + "mode": "dark", + "state": "walkthrough", + "violations": [] + }, + { + "width": 768, + "language": "nb", + "mode": "light", + "violations": [] + }, + { + "width": 768, + "language": "nb", + "mode": "light", + "state": "walkthrough", + "violations": [] + }, + { + "width": 768, + "language": "nb", + "mode": "dark", + "violations": [] + }, + { + "width": 768, + "language": "nb", + "mode": "dark", + "state": "walkthrough", + "violations": [] + }, + { + "width": 1440, + "language": "en", + "mode": "light", + "violations": [] + }, + { + "width": 1440, + "language": "en", + "mode": "light", + "state": "walkthrough", + "violations": [] + }, + { + "width": 1440, + "language": "en", + "mode": "dark", + "violations": [] + }, + { + "width": 1440, + "language": "en", + "mode": "dark", + "state": "walkthrough", + "violations": [] + }, + { + "width": 1440, + "language": "nb", + "mode": "light", + "violations": [] + }, + { + "width": 1440, + "language": "nb", + "mode": "light", + "state": "walkthrough", + "violations": [] + }, + { + "width": 1440, + "language": "nb", + "mode": "dark", + "violations": [] + }, + { + "width": 1440, + "language": "nb", + "mode": "dark", + "state": "walkthrough", + "violations": [] + } + ], + "journeys": [ + { + "width": 320, + "language": "en", + "mode": "light", + "skipResumeDismissWalkthrough": "PASS", + "noGuidanceWrites": "PASS", + "explicitLayoutSaveReload": "PASS" + }, + { + "width": 320, + "language": "en", + "mode": "dark", + "skipResumeDismissWalkthrough": "PASS", + "noGuidanceWrites": "PASS", + "explicitLayoutSaveReload": "PASS" + }, + { + "width": 320, + "language": "nb", + "mode": "light", + "skipResumeDismissWalkthrough": "PASS", + "noGuidanceWrites": "PASS", + "explicitLayoutSaveReload": "PASS" + }, + { + "width": 320, + "language": "nb", + "mode": "dark", + "skipResumeDismissWalkthrough": "PASS", + "noGuidanceWrites": "PASS", + "explicitLayoutSaveReload": "PASS" + }, + { + "width": 390, + "language": "en", + "mode": "light", + "skipResumeDismissWalkthrough": "PASS", + "noGuidanceWrites": "PASS", + "explicitLayoutSaveReload": "PASS" + }, + { + "width": 390, + "language": "en", + "mode": "dark", + "skipResumeDismissWalkthrough": "PASS", + "noGuidanceWrites": "PASS", + "explicitLayoutSaveReload": "PASS" + }, + { + "width": 390, + "language": "nb", + "mode": "light", + "skipResumeDismissWalkthrough": "PASS", + "noGuidanceWrites": "PASS", + "explicitLayoutSaveReload": "PASS" + }, + { + "width": 390, + "language": "nb", + "mode": "dark", + "skipResumeDismissWalkthrough": "PASS", + "noGuidanceWrites": "PASS", + "explicitLayoutSaveReload": "PASS" + }, + { + "width": 768, + "language": "en", + "mode": "light", + "skipResumeDismissWalkthrough": "PASS", + "noGuidanceWrites": "PASS", + "explicitLayoutSaveReload": "PASS" + }, + { + "width": 768, + "language": "en", + "mode": "dark", + "skipResumeDismissWalkthrough": "PASS", + "noGuidanceWrites": "PASS", + "explicitLayoutSaveReload": "PASS" + }, + { + "width": 768, + "language": "nb", + "mode": "light", + "skipResumeDismissWalkthrough": "PASS", + "noGuidanceWrites": "PASS", + "explicitLayoutSaveReload": "PASS" + }, + { + "width": 768, + "language": "nb", + "mode": "dark", + "skipResumeDismissWalkthrough": "PASS", + "noGuidanceWrites": "PASS", + "explicitLayoutSaveReload": "PASS" + }, + { + "width": 1440, + "language": "en", + "mode": "light", + "skipResumeDismissWalkthrough": "PASS", + "noGuidanceWrites": "PASS", + "explicitLayoutSaveReload": "PASS" + }, + { + "width": 1440, + "language": "en", + "mode": "dark", + "skipResumeDismissWalkthrough": "PASS", + "noGuidanceWrites": "PASS", + "explicitLayoutSaveReload": "PASS" + }, + { + "width": 1440, + "language": "nb", + "mode": "light", + "skipResumeDismissWalkthrough": "PASS", + "noGuidanceWrites": "PASS", + "explicitLayoutSaveReload": "PASS" + }, + { + "width": 1440, + "language": "nb", + "mode": "dark", + "skipResumeDismissWalkthrough": "PASS", + "noGuidanceWrites": "PASS", + "explicitLayoutSaveReload": "PASS" + } + ], + "errors": [], + "unexpectedWrites": [], + "mockedWrites": [ + { + "width": 320, + "language": "en", + "mode": "light", + "revision": 1 + }, + { + "width": 320, + "language": "en", + "mode": "dark", + "revision": 1 + }, + { + "width": 320, + "language": "nb", + "mode": "light", + "revision": 1 + }, + { + "width": 320, + "language": "nb", + "mode": "dark", + "revision": 1 + }, + { + "width": 390, + "language": "en", + "mode": "light", + "revision": 1 + }, + { + "width": 390, + "language": "en", + "mode": "dark", + "revision": 1 + }, + { + "width": 390, + "language": "nb", + "mode": "light", + "revision": 1 + }, + { + "width": 390, + "language": "nb", + "mode": "dark", + "revision": 1 + }, + { + "width": 768, + "language": "en", + "mode": "light", + "revision": 1 + }, + { + "width": 768, + "language": "en", + "mode": "dark", + "revision": 1 + }, + { + "width": 768, + "language": "nb", + "mode": "light", + "revision": 1 + }, + { + "width": 768, + "language": "nb", + "mode": "dark", + "revision": 1 + }, + { + "width": 1440, + "language": "en", + "mode": "light", + "revision": 1 + }, + { + "width": 1440, + "language": "en", + "mode": "dark", + "revision": 1 + }, + { + "width": 1440, + "language": "nb", + "mode": "light", + "revision": 1 + }, + { + "width": 1440, + "language": "nb", + "mode": "dark", + "revision": 1 + } + ], + "blockedExternal": [ + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com", + "https://fonts.googleapis.com" + ], + "limits": [ + "synthetic API fixtures, not real backend end-to-end", + "no native device/screen reader/model/provider/security/release qualification" + ] +} \ No newline at end of file diff --git a/docs/ui-modernisation/evidence/dashboard/browser/dashboard-en-dark-1440.png b/docs/ui-modernisation/evidence/dashboard/browser/dashboard-en-dark-1440.png new file mode 100644 index 00000000..4c570b6d Binary files /dev/null and b/docs/ui-modernisation/evidence/dashboard/browser/dashboard-en-dark-1440.png differ diff --git a/docs/ui-modernisation/evidence/dashboard/browser/dashboard-en-dark-320.png b/docs/ui-modernisation/evidence/dashboard/browser/dashboard-en-dark-320.png new file mode 100644 index 00000000..9fd85a7b Binary files /dev/null and b/docs/ui-modernisation/evidence/dashboard/browser/dashboard-en-dark-320.png differ diff --git a/docs/ui-modernisation/evidence/dashboard/browser/dashboard-en-dark-390.png b/docs/ui-modernisation/evidence/dashboard/browser/dashboard-en-dark-390.png new file mode 100644 index 00000000..8af415aa Binary files /dev/null and b/docs/ui-modernisation/evidence/dashboard/browser/dashboard-en-dark-390.png differ diff --git a/docs/ui-modernisation/evidence/dashboard/browser/dashboard-en-dark-768.png b/docs/ui-modernisation/evidence/dashboard/browser/dashboard-en-dark-768.png new file mode 100644 index 00000000..470a9aca Binary files /dev/null and b/docs/ui-modernisation/evidence/dashboard/browser/dashboard-en-dark-768.png differ diff --git a/docs/ui-modernisation/evidence/dashboard/browser/dashboard-en-light-1440.png b/docs/ui-modernisation/evidence/dashboard/browser/dashboard-en-light-1440.png new file mode 100644 index 00000000..59b278fe Binary files /dev/null and b/docs/ui-modernisation/evidence/dashboard/browser/dashboard-en-light-1440.png differ diff --git a/docs/ui-modernisation/evidence/dashboard/browser/dashboard-en-light-320.png b/docs/ui-modernisation/evidence/dashboard/browser/dashboard-en-light-320.png new file mode 100644 index 00000000..30d9d717 Binary files /dev/null and b/docs/ui-modernisation/evidence/dashboard/browser/dashboard-en-light-320.png differ diff --git a/docs/ui-modernisation/evidence/dashboard/browser/dashboard-en-light-390.png b/docs/ui-modernisation/evidence/dashboard/browser/dashboard-en-light-390.png new file mode 100644 index 00000000..ce04443b Binary files /dev/null and b/docs/ui-modernisation/evidence/dashboard/browser/dashboard-en-light-390.png differ diff --git a/docs/ui-modernisation/evidence/dashboard/browser/dashboard-en-light-768.png b/docs/ui-modernisation/evidence/dashboard/browser/dashboard-en-light-768.png new file mode 100644 index 00000000..5984edab Binary files /dev/null and b/docs/ui-modernisation/evidence/dashboard/browser/dashboard-en-light-768.png differ diff --git a/docs/ui-modernisation/evidence/dashboard/browser/dashboard-nb-dark-1440.png b/docs/ui-modernisation/evidence/dashboard/browser/dashboard-nb-dark-1440.png new file mode 100644 index 00000000..138e862f Binary files /dev/null and b/docs/ui-modernisation/evidence/dashboard/browser/dashboard-nb-dark-1440.png differ diff --git a/docs/ui-modernisation/evidence/dashboard/browser/dashboard-nb-dark-320.png b/docs/ui-modernisation/evidence/dashboard/browser/dashboard-nb-dark-320.png new file mode 100644 index 00000000..74067879 Binary files /dev/null and b/docs/ui-modernisation/evidence/dashboard/browser/dashboard-nb-dark-320.png differ diff --git a/docs/ui-modernisation/evidence/dashboard/browser/dashboard-nb-dark-390.png b/docs/ui-modernisation/evidence/dashboard/browser/dashboard-nb-dark-390.png new file mode 100644 index 00000000..0012dc87 Binary files /dev/null and b/docs/ui-modernisation/evidence/dashboard/browser/dashboard-nb-dark-390.png differ diff --git a/docs/ui-modernisation/evidence/dashboard/browser/dashboard-nb-dark-768.png b/docs/ui-modernisation/evidence/dashboard/browser/dashboard-nb-dark-768.png new file mode 100644 index 00000000..27c4b8f1 Binary files /dev/null and b/docs/ui-modernisation/evidence/dashboard/browser/dashboard-nb-dark-768.png differ diff --git a/docs/ui-modernisation/evidence/dashboard/browser/dashboard-nb-light-1440.png b/docs/ui-modernisation/evidence/dashboard/browser/dashboard-nb-light-1440.png new file mode 100644 index 00000000..68237d02 Binary files /dev/null and b/docs/ui-modernisation/evidence/dashboard/browser/dashboard-nb-light-1440.png differ diff --git a/docs/ui-modernisation/evidence/dashboard/browser/dashboard-nb-light-320.png b/docs/ui-modernisation/evidence/dashboard/browser/dashboard-nb-light-320.png new file mode 100644 index 00000000..9f6571a8 Binary files /dev/null and b/docs/ui-modernisation/evidence/dashboard/browser/dashboard-nb-light-320.png differ diff --git a/docs/ui-modernisation/evidence/dashboard/browser/dashboard-nb-light-390.png b/docs/ui-modernisation/evidence/dashboard/browser/dashboard-nb-light-390.png new file mode 100644 index 00000000..d2e9c5c4 Binary files /dev/null and b/docs/ui-modernisation/evidence/dashboard/browser/dashboard-nb-light-390.png differ diff --git a/docs/ui-modernisation/evidence/dashboard/browser/dashboard-nb-light-768.png b/docs/ui-modernisation/evidence/dashboard/browser/dashboard-nb-light-768.png new file mode 100644 index 00000000..afc6f95a Binary files /dev/null and b/docs/ui-modernisation/evidence/dashboard/browser/dashboard-nb-light-768.png differ diff --git a/docs/ui-modernisation/evidence/dashboard/browser/walkthrough-en-dark-1440.png b/docs/ui-modernisation/evidence/dashboard/browser/walkthrough-en-dark-1440.png new file mode 100644 index 00000000..c022e957 Binary files /dev/null and b/docs/ui-modernisation/evidence/dashboard/browser/walkthrough-en-dark-1440.png differ diff --git a/docs/ui-modernisation/evidence/dashboard/browser/walkthrough-en-dark-320.png b/docs/ui-modernisation/evidence/dashboard/browser/walkthrough-en-dark-320.png new file mode 100644 index 00000000..7a045082 Binary files /dev/null and b/docs/ui-modernisation/evidence/dashboard/browser/walkthrough-en-dark-320.png differ diff --git a/docs/ui-modernisation/evidence/dashboard/browser/walkthrough-en-dark-390.png b/docs/ui-modernisation/evidence/dashboard/browser/walkthrough-en-dark-390.png new file mode 100644 index 00000000..324f42b8 Binary files /dev/null and b/docs/ui-modernisation/evidence/dashboard/browser/walkthrough-en-dark-390.png differ diff --git a/docs/ui-modernisation/evidence/dashboard/browser/walkthrough-en-dark-768.png b/docs/ui-modernisation/evidence/dashboard/browser/walkthrough-en-dark-768.png new file mode 100644 index 00000000..cce23fb4 Binary files /dev/null and b/docs/ui-modernisation/evidence/dashboard/browser/walkthrough-en-dark-768.png differ diff --git a/docs/ui-modernisation/evidence/dashboard/browser/walkthrough-en-light-1440.png b/docs/ui-modernisation/evidence/dashboard/browser/walkthrough-en-light-1440.png new file mode 100644 index 00000000..3cbcc112 Binary files /dev/null and b/docs/ui-modernisation/evidence/dashboard/browser/walkthrough-en-light-1440.png differ diff --git a/docs/ui-modernisation/evidence/dashboard/browser/walkthrough-en-light-320.png b/docs/ui-modernisation/evidence/dashboard/browser/walkthrough-en-light-320.png new file mode 100644 index 00000000..035b4876 Binary files /dev/null and b/docs/ui-modernisation/evidence/dashboard/browser/walkthrough-en-light-320.png differ diff --git a/docs/ui-modernisation/evidence/dashboard/browser/walkthrough-en-light-390.png b/docs/ui-modernisation/evidence/dashboard/browser/walkthrough-en-light-390.png new file mode 100644 index 00000000..45ced0b4 Binary files /dev/null and b/docs/ui-modernisation/evidence/dashboard/browser/walkthrough-en-light-390.png differ diff --git a/docs/ui-modernisation/evidence/dashboard/browser/walkthrough-en-light-768.png b/docs/ui-modernisation/evidence/dashboard/browser/walkthrough-en-light-768.png new file mode 100644 index 00000000..fd5e11c2 Binary files /dev/null and b/docs/ui-modernisation/evidence/dashboard/browser/walkthrough-en-light-768.png differ diff --git a/docs/ui-modernisation/evidence/dashboard/browser/walkthrough-nb-dark-1440.png b/docs/ui-modernisation/evidence/dashboard/browser/walkthrough-nb-dark-1440.png new file mode 100644 index 00000000..a8bc928c Binary files /dev/null and b/docs/ui-modernisation/evidence/dashboard/browser/walkthrough-nb-dark-1440.png differ diff --git a/docs/ui-modernisation/evidence/dashboard/browser/walkthrough-nb-dark-320.png b/docs/ui-modernisation/evidence/dashboard/browser/walkthrough-nb-dark-320.png new file mode 100644 index 00000000..73e7d5e1 Binary files /dev/null and b/docs/ui-modernisation/evidence/dashboard/browser/walkthrough-nb-dark-320.png differ diff --git a/docs/ui-modernisation/evidence/dashboard/browser/walkthrough-nb-dark-390.png b/docs/ui-modernisation/evidence/dashboard/browser/walkthrough-nb-dark-390.png new file mode 100644 index 00000000..16902ed1 Binary files /dev/null and b/docs/ui-modernisation/evidence/dashboard/browser/walkthrough-nb-dark-390.png differ diff --git a/docs/ui-modernisation/evidence/dashboard/browser/walkthrough-nb-dark-768.png b/docs/ui-modernisation/evidence/dashboard/browser/walkthrough-nb-dark-768.png new file mode 100644 index 00000000..875e7cc9 Binary files /dev/null and b/docs/ui-modernisation/evidence/dashboard/browser/walkthrough-nb-dark-768.png differ diff --git a/docs/ui-modernisation/evidence/dashboard/browser/walkthrough-nb-light-1440.png b/docs/ui-modernisation/evidence/dashboard/browser/walkthrough-nb-light-1440.png new file mode 100644 index 00000000..4f1354c7 Binary files /dev/null and b/docs/ui-modernisation/evidence/dashboard/browser/walkthrough-nb-light-1440.png differ diff --git a/docs/ui-modernisation/evidence/dashboard/browser/walkthrough-nb-light-320.png b/docs/ui-modernisation/evidence/dashboard/browser/walkthrough-nb-light-320.png new file mode 100644 index 00000000..3cb52689 Binary files /dev/null and b/docs/ui-modernisation/evidence/dashboard/browser/walkthrough-nb-light-320.png differ diff --git a/docs/ui-modernisation/evidence/dashboard/browser/walkthrough-nb-light-390.png b/docs/ui-modernisation/evidence/dashboard/browser/walkthrough-nb-light-390.png new file mode 100644 index 00000000..7725fe1c Binary files /dev/null and b/docs/ui-modernisation/evidence/dashboard/browser/walkthrough-nb-light-390.png differ diff --git a/docs/ui-modernisation/evidence/dashboard/browser/walkthrough-nb-light-768.png b/docs/ui-modernisation/evidence/dashboard/browser/walkthrough-nb-light-768.png new file mode 100644 index 00000000..2e50b067 Binary files /dev/null and b/docs/ui-modernisation/evidence/dashboard/browser/walkthrough-nb-light-768.png differ diff --git a/job-tracker-ui/scripts/verify-a3-dashboard.mjs b/job-tracker-ui/scripts/verify-a3-dashboard.mjs new file mode 100644 index 00000000..561398ec --- /dev/null +++ b/job-tracker-ui/scripts/verify-a3-dashboard.mjs @@ -0,0 +1,102 @@ +// Actual exported React UI with synthetic, in-memory API fixtures; no live backend. +import { chromium } from '@playwright/test'; +import AxeBuilder from '@axe-core/playwright'; +import assert from 'node:assert/strict'; +import fs from 'node:fs'; +import path from 'node:path'; +const out = path.resolve(import.meta.dirname, '../../docs/ui-modernisation/evidence/dashboard/browser'); +fs.mkdirSync(out, { recursive: true }); +const report = { screens: [], journeys: [], errors: [], unexpectedWrites: [], mockedWrites: [], blockedExternal: [] }; +const browser = await chromium.launch(); +try { + for (const width of [320, 390, 768, 1440]) for (const language of ['en', 'nb']) for (const mode of ['light', 'dark']) { + const context = await browser.newContext({ viewport: { width, height: 1000 }, reducedMotion: 'reduce' }); + const page = await context.newPage(); page.on('pageerror', error => report.errors.push(error.message)); + await page.addInitScript(({ language, mode }) => { localStorage.setItem('uiLanguage', language); localStorage.setItem('jobtracker.themeMode', mode); }, { language, mode }); + let layout = { version: 2, revision: 0, widgets: [] }; + const now = new Date(), due = new Date(now); due.setDate(due.getDate() + 2); + const deadline = `${due.getFullYear()}-${String(due.getMonth() + 1).padStart(2, '0')}-${String(due.getDate()).padStart(2, '0')}`; + const jobs = [ + { id: 31, version: 1, jobTitle: 'Community nurse', company: { id: 1, name: 'Fjord Care' }, status: 'Interview', workflowSignal: { actionKey: 'interview-prep', workspaceTab: 'interview-prep', needsAttention: true, needsInterviewPrep: true, reason: 'Review your recorded preparation' } }, + { id: 32, version: 1, jobTitle: 'Primary teacher', company: { id: 2, name: 'Linden School' }, status: 'Preparing', deadline, workflowSignal: { actionKey: 'package-work', workspaceTab: 'tailored-cv', needsAttention: true, hasPackageGap: true, reason: 'Review application documents' } }, + { id: 33, version: 1, jobTitle: 'Electrical engineer', company: { id: 3, name: 'Northline Energy' }, status: 'Applied', followUpAt: due.toISOString(), workflowSignal: { actionKey: 'follow-up', workspaceTab: 'follow-up', needsAttention: true, needsFollowUpAction: true, reason: 'Review a follow-up' } }, + ]; + await page.route('**/*', route => { + const req = route.request(), url = new URL(req.url()); + if (!['localhost', '127.0.0.1'].includes(url.hostname)) { report.blockedExternal.push(url.origin); return route.abort(); } + if (!url.pathname.startsWith('/api/')) return route.continue(); + if (req.method() === 'PUT' && url.pathname === '/api/auth/preferences/dashboard') { + const value = req.postDataJSON(); assert.equal(value.version, 2); assert.equal(value.revision, layout.revision); + assert.equal(value.widgets.length, 10); layout = { ...value, revision: layout.revision + 1 }; + report.mockedWrites.push({ width, language, mode, revision: layout.revision }); + return route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify(layout) }); + } + if (req.method() !== 'GET') { report.unexpectedWrites.push(url.pathname); return route.abort(); } + const fixtures = { + '/api/auth/config': { requireAuth: true }, + '/api/auth/me': { id: 'a3-dashboard-owner', sessionContext: 'a3-dashboard-session', firstName: 'Alex', displayName: 'Alex Sample', roles: [], uiLanguage: language === 'nb' ? 'nb-NO' : 'en' }, + '/api/auth/preferences/dashboard': layout, + '/api/jobapplications/stats': { total: 12, active: 10, deleted: 2, byStatus: { Preparing: 3, Applied: 5, Interview: 2 }, appliedLast30Days: 4, averageDaysSinceApplied: 5 }, + '/api/jobapplications/analytics-overview': { funnel: [{ label: 'Preparing', count: 3 }, { label: 'Applied', count: 5 }, { label: 'Interview', count: 2 }], responseRateBySource: [], topCompanies: [], timeInStage: [], salaryInsights: [], totalResponses: 2, totalActive: 10 }, + '/api/jobapplications/reminders': jobs, + '/api/jobapplications/analytics': [{ month: `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}`, applied: 4, responses: 1 }], + '/api/jobapplications/tags': [{ tag: 'Communication', count: 3 }], + '/api/jobapplications/tag-trends': { months: [], series: [] }, + '/api/notifications/unread-count': { count: 0 }, + '/api/career/profile': { revision: 1, cvText: '', profile: { version: '1', skills: ['Teaching', 'Care coordination'] } }, + '/api/gmail/status': { connected: false }, '/api/microsoft-graph/status': { connected: false }, '/api/imap/status': { connected: false }, + }; + return route.fulfill({ status: Object.hasOwn(fixtures, url.pathname) ? 200 : 404, contentType: 'application/json', body: JSON.stringify(fixtures[url.pathname] ?? {}) }); + }); + await page.goto('http://localhost:4338/dashboard'); + const heading = page.locator('main h1'); await heading.waitFor(); + assert.match(await heading.innerText(), language === 'en' ? /^Good (morning|afternoon|evening), Alex$/ : /^God (morgen|ettermiddag|kveld), Alex$/); + const weekName = language === 'en' ? 'Your week' : 'Uken din'; + const week = page.getByRole('region', { name: weekName, exact: true }); await week.waitFor(); + await page.getByText('3 / 4', { exact: true }).waitFor(); + assert.deepEqual(await page.locator('[data-dashboard-widget]').evaluateAll(nodes => nodes.slice(0, 4).map(node => node.getAttribute('data-dashboard-widget'))), ['attention', 'agenda', 'funnel', 'cards']); + assert.equal(await week.locator('time').first().getAttribute('datetime'), deadline); + assert.equal(await week.getByRole('link', { name: 'Community nurse' }).count(), 0); + assert.equal(await page.evaluate(() => document.documentElement.scrollWidth > innerWidth + 1), false); + if (width === 1440) await page.waitForFunction(() => { + 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; + }); + 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 }); + await page.getByRole('button', { name: language === 'en' ? /^Skip for now:/ : /^Hopp over foreløpig:/ }).click(); + await page.reload(); await page.getByRole('button', { name: language === 'en' ? /^Resume step:/ : /^Fortsett steg:/ }).waitFor(); + await page.getByRole('button', { name: language === 'en' ? /^Resume step:/ : /^Fortsett steg:/ }).click(); + await page.getByRole('button', { name: language === 'en' ? 'Dismiss setup checklist' : 'Skjul oppstartslisten', exact: true }).click(); + await page.reload(); + await page.getByRole('button', { name: language === 'en' ? 'Continue setup' : 'Fortsett oppsett', exact: true }).click(); + await page.getByText('3 / 4', { exact: true }).waitFor(); + await page.getByRole('button', { name: language === 'en' ? 'Show walkthrough' : 'Vis veiledning', exact: true }).click(); + assert.equal(await page.evaluate(() => document.activeElement.tagName), 'H3'); + await page.getByRole('button', { name: language === 'en' ? 'Next step' : 'Neste steg', exact: true }).click(); + assert.equal(await page.evaluate(() => document.activeElement.tagName), 'H3'); + const guideScan = await new AxeBuilder({ page }).withTags(['wcag2a', 'wcag2aa', 'wcag21aa', 'wcag22aa']).analyze(); + report.screens.push({ width, language, mode, state: 'walkthrough', violations: guideScan.violations }); assert.deepEqual(guideScan.violations, []); + await page.screenshot({ path: path.join(out, `walkthrough-${language}-${mode}-${width}.png`), fullPage: true }); + await page.getByRole('button', { name: language === 'en' ? 'Close walkthrough' : 'Lukk veiledning', exact: true }).click(); + assert.equal(await page.getByRole('button', { name: language === 'en' ? 'Show walkthrough' : 'Vis veiledning', exact: true }).evaluate(node => node === document.activeElement), true); + assert.equal(report.mockedWrites.filter(row => row.width === width && row.language === language && row.mode === mode).length, 0); + await page.getByRole('button', { name: language === 'en' ? 'Customize dashboard' : 'Tilpass dashboard', exact: true }).click(); + const editor = page.getByRole('region', { name: language === 'en' ? 'Editing dashboard' : 'Tilpasser dashboard', exact: true }); + await editor.locator('summary').click(); await editor.getByRole('checkbox', { name: weekName, exact: true }).uncheck(); + await editor.getByRole('button', { name: language === 'en' ? 'Save layout' : 'Lagre oppsett', exact: true }).click(); + await editor.waitFor({ state: 'hidden' }); assert.equal(layout.revision, 1); assert.equal(layout.widgets.find(widget => widget.id === 'agenda').visible, false); + await page.reload(); await page.getByRole('button', { name: language === 'en' ? 'Customize dashboard' : 'Tilpass dashboard', exact: true }).waitFor(); + assert.equal(await week.count(), 0); + report.journeys.push({ width, language, mode, skipResumeDismissWalkthrough: 'PASS', noGuidanceWrites: 'PASS', explicitLayoutSaveReload: 'PASS' }); + await context.close(); + } + assert.deepEqual(report.errors, []); assert.deepEqual(report.unexpectedWrites, []); + assert.ok(report.blockedExternal.every(origin => origin === 'https://fonts.googleapis.com')); + assert.equal(report.mockedWrites.length, 16); + console.log('PASS: 16 dashboard journeys,32 scoped axe sweeps,32 captures; controlled API persistence only.'); +} finally { + fs.writeFileSync(path.join(out, '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)); + await browser.close(); +} diff --git a/job-tracker-ui/src/App.tsx b/job-tracker-ui/src/App.tsx index b9444731..2baa4dd1 100644 --- a/job-tracker-ui/src/App.tsx +++ b/job-tracker-ui/src/App.tsx @@ -321,7 +321,12 @@ function Shell({ jobPageSize, setJobPageSize, jobColumns, setJobColumns, themeMo if (requireAuth === null || !authResolved) return {t("loading")}; if (requireAuth && !me) return ; - const pageTitle = titleFor(path, t); + const greetingName = me?.firstName?.trim() || me?.displayName?.trim(); + const hour = new Date().getHours(); + const pageTitle = path === "/dashboard" && greetingName + ? hour < 12 ? t("dashboardGoodMorning", { name: greetingName }) + : hour < 18 ? t("dashboardGoodAfternoon", { name: greetingName }) : t("dashboardGoodEvening", { name: greetingName }) + : titleFor(path, t); const pageSubtitle = subtitleFor(path, t); const breadcrumbs = breadcrumbsFor(path, t); const setAndPersistPageSize = (n: 15 | 20 | 25) => { setJobPageSize(n); window.localStorage.setItem("jobPageSize", String(n)); }; diff --git a/job-tracker-ui/src/components/DashboardGrid.test.tsx b/job-tracker-ui/src/components/DashboardGrid.test.tsx index 139b041e..ed375007 100644 --- a/job-tracker-ui/src/components/DashboardGrid.test.tsx +++ b/job-tracker-ui/src/components/DashboardGrid.test.tsx @@ -42,7 +42,7 @@ test('mount and compaction do not save; completed resize keeps hidden geometry a expect(change).toHaveBeenCalledTimes(1); const saved = change.mock.calls[0][0]; expect(saved.revision).toBe(8); - expect(saved.widgets[1]).toMatchObject({ w: 8, h: 12, size: 'standard' }); + expect(saved.widgets.find((widget: { id: string }) => widget.id === 'attention')).toMatchObject({ w: 8, h: 12, size: 'standard' }); expect(saved.widgets[2]).toEqual(layout.widgets[2]); }); @@ -58,7 +58,7 @@ test('narrow viewport retains desktop geometry and keyboard height changes are b fireEvent.change(screen.getByLabelText('Height for Today'), { target: { value: '25' } }); expect(change).not.toHaveBeenCalled(); fireEvent.change(screen.getByLabelText('Height for Today'), { target: { value: '12' } }); - expect(change.mock.calls[0][0].widgets[1].h).toBe(12); + expect(change.mock.calls[0][0].widgets.find((widget: { id: string }) => widget.id === 'attention').h).toBe(12); mockMeasuredWidth = 1200; view.rerender(

{widget.id} content

} />
); expect(change).toHaveBeenCalledTimes(1); diff --git a/job-tracker-ui/src/components/DashboardView.tsx b/job-tracker-ui/src/components/DashboardView.tsx index 9ed698db..68c6e814 100644 --- a/job-tracker-ui/src/components/DashboardView.tsx +++ b/job-tracker-ui/src/components/DashboardView.tsx @@ -20,6 +20,7 @@ import { WIDGETS, defaultDashboardLayout, WidgetId } from "../dashboardLayout"; import { useDashboardLayout } from "../hooks/useDashboardLayout"; import DashboardLayoutEditor, { DashboardNavigationGuard } from "./DashboardLayoutEditor"; import DashboardGrid from "./DashboardGrid"; +import DashboardWeek from "./DashboardWeek"; import { useOptionalToast } from "../toast"; import { useI18n } from "../i18n/I18nProvider"; import { statusLabel, statusTone } from "../pipeline"; @@ -177,7 +178,7 @@ export default function DashboardView({ mode = "dashboard" }: { mode?: "dashboar ; const detailsSx = { mt: 2, "& summary": { cursor: "pointer", color: "text.primary", fontWeight: 600, py: 1 }, "& th, & td": { px: 1, py: 0.75, overflowWrap: "anywhere" } }; const prefsButton = !layoutState.editing ? : null; - const pipelineReport = + const pipelineReport = {funnel.map(item => @@ -199,6 +200,7 @@ export default function DashboardView({ mode = "dashboard" }: { mode?: "dashboar ; const reports: Record = { + agenda: summaryReady && hasJobs ? : null, cards: summaryReady && hasJobs ? {[ { label: t("dashboardTrackedJobs"), value: stats!.active, caption: t("dashboardTrackedBody"), to: "/jobs" }, @@ -324,7 +326,6 @@ export default function DashboardView({ mode = "dashboard" }: { mode?: "dashboar return ( - {!reporting ? : null} {t(reporting ? "analyticsScope" : "dashboardSnapshotScope")} {reporting ? : prefsButton} @@ -371,6 +372,7 @@ export default function DashboardView({ mode = "dashboard" }: { mode?: "dashboar if (!content && !(ready && (hasJobs || layoutState.editing))) return null; return content ?? item.id === widget.id)!.titleKey)}>{t("dashboardNoSample")}; }} />} + {!reporting ? : null} ); } diff --git a/job-tracker-ui/src/components/DashboardWeek.tsx b/job-tracker-ui/src/components/DashboardWeek.tsx new file mode 100644 index 00000000..fe7030ab --- /dev/null +++ b/job-tracker-ui/src/components/DashboardWeek.tsx @@ -0,0 +1,46 @@ +import React from 'react'; +import { Box, Link, Typography } from '@mui/material'; +import { Link as RouterLink } from 'react-router-dom'; +import { useI18n } from '../i18n/I18nProvider'; +import { PageSection } from '../layout/PageLayout'; +import type { JobApplication } from '../types'; + +// Date-only deadlines remain local calendar dates; never infer a time from free text. +function storedDate(value: string | undefined): Date | null { + if (typeof value !== 'string') return null; + const parts = /^(\d{4})-(\d{2})-(\d{2})(?:$|T)/.exec(value); + if (!parts) return null; + const [year, month, day] = parts.slice(1).map(Number); + const calendar = new Date(0); calendar.setFullYear(year, month - 1, day); calendar.setHours(0, 0, 0, 0); + if (calendar.getFullYear() !== year || calendar.getMonth() !== month - 1 || calendar.getDate() !== day) return null; + if (value.length === 10) return calendar; + const time = /T(\d{2}):(\d{2})(?::(\d{2})(?:\.\d{1,7})?)?(?:Z|[+-]\d{2}:\d{2})?$/.exec(value); + if (!time || Number(time[1]) > 23 || Number(time[2]) > 59 || Number(time[3] ?? 0) > 59) return null; + const parsed = new Date(value); + return Number.isNaN(parsed.getTime()) ? null : parsed; +} + +export default function DashboardWeek({ jobs }: { jobs: JobApplication[] }) { + const { t, language } = useI18n(); + const start = new Date(); start.setHours(0, 0, 0, 0); + const end = new Date(start); end.setDate(end.getDate() + 7); + const events = jobs.flatMap(job => ([ + { kind: 'deadline', raw: job.deadline, label: t('dashboardWeekDeadline') }, + { kind: 'followUp', raw: job.followUpAt, label: t('dashboardWeekFollowUp') }, + ]).flatMap(event => { + const date = storedDate(event.raw); + return date && date >= start && date < end ? [{ ...event, raw: event.raw!, date, job }] : []; + })).sort((a, b) => a.date.getTime() - b.date.getTime()); + const format = new Intl.DateTimeFormat(language === 'nb' ? 'nb-NO' : 'en-US', { weekday: 'short', month: 'short', day: 'numeric' }); + return {t('reminders')}}> + {events.length ? + {events.slice(0, 3).map(event => + {format.format(event.date)} + {event.label} + {event.job.jobTitle} + {event.job.company?.name} + )} + : {t('dashboardWeekEmpty')}} + ; +} diff --git a/job-tracker-ui/src/components/OnboardingChecklist.tsx b/job-tracker-ui/src/components/OnboardingChecklist.tsx index f7abe785..c96abc2c 100644 --- a/job-tracker-ui/src/components/OnboardingChecklist.tsx +++ b/job-tracker-ui/src/components/OnboardingChecklist.tsx @@ -42,12 +42,54 @@ export default function OnboardingChecklist({ hasJobs }: { hasJobs: boolean | nu try { return window.localStorage.getItem(dismissalKey) === "true"; } catch { return false; } }); + const skippedKey = dismissalKey.replace('onboardingDismissed:', 'onboardingSkipped:'); + const [skipped, setSkipped] = useState(() => { + try { + const value: unknown = JSON.parse(localStorage.getItem(skippedKey) ?? '[]'); + return Array.isArray(value) ? ['/profile', '/career', '/settings/connected-accounts', '/jobs'].filter(path => value.includes(path)) : []; + } catch { return []; } + }); + const [guideStep, setGuideStep] = useState(null); + const guideTitle = useRef(null); + const guideEntry = useRef(null); + const resumeEntry = useRef(null); + const returnToEntry = useRef(false); + const stepActions = useRef>({}); + const pendingStepFocus = useRef(null); const [status, setStatus] = useState<{ profile: boolean; cv: boolean; email: boolean } | null>(null); const [loading, setLoading] = useState(true); const [loadError, setLoadError] = useState<{ cause: unknown } | null>(null); const [readAttempt, setReadAttempt] = useState(0); const dismissRef = useRef(null); + useEffect(() => { + if (guideStep !== null) guideTitle.current?.focus(); + else if (returnToEntry.current) { + returnToEntry.current = false; + (resumeEntry.current ?? guideEntry.current)?.focus(); + } + }, [guideStep, dismissed]); + + useEffect(() => { + if (!pendingStepFocus.current) return; + (stepActions.current[pendingStepFocus.current] ?? guideEntry.current)?.focus(); + pendingStepFocus.current = null; + }, [skipped]); + + const resume = (walkthrough: boolean) => { + try { localStorage.setItem(dismissalKey, 'false'); } catch { /* Session-only continuation remains available. */ } + if (!walkthrough) returnToEntry.current = true; + setDismissed(false); setGuideStep(walkthrough ? 0 : null); + }; + const skip = (path: string, value: boolean) => { + const next = skipped.filter(item => item !== path); + if (value) next.push(path); + try { localStorage.setItem(skippedKey, JSON.stringify(next)); } catch { /* Keep this session's choice. */ } + pendingStepFocus.current = path; + setSkipped(next); + }; + const closeGuide = () => { returnToEntry.current = true; setGuideStep(null); }; + useEffect(() => { if (dismissed) return; let active = true; @@ -87,8 +129,6 @@ export default function OnboardingChecklist({ hasJobs }: { hasJobs: boolean | nu return () => { active = false; }; }, [dismissed, readAttempt]); - if (dismissed) return null; - // These four recorded steps are guidance, not a completeness score or proof of // verified email, an imported CV, a rendered PDF, or a live mailbox grant. const steps = [ @@ -98,7 +138,17 @@ export default function OnboardingChecklist({ hasJobs }: { hasJobs: boolean | nu { done: hasJobs, label: t("onboardingStepJob"), path: "/jobs", actionLabel: t("onboardingStepJobAction") }, ]; const completed = steps.filter((step) => step.done === true).length; - if (status && hasJobs !== null && completed === steps.length) return null; + const allDone = Boolean(status && hasJobs !== null && completed === steps.length); + if (dismissed || (allDone && guideStep === null)) return + {t('onboardingResumeTitle')} + {t('onboardingResumeBody')} + + {!allDone ? : null} + + + ; + const guideHelp = [t('onboardingGuideProfile'), t('onboardingGuideCareer'), t('onboardingGuideEmail'), t('onboardingGuideJob')]; return ( { try { window.localStorage.setItem(dismissalKey, "true"); } catch { /* This dismissal still applies while the page remains open. */ } - setDismissed(true); + returnToEntry.current = true; + setGuideStep(null); setDismissed(true); }} > }> + {loading ? <> {t("loading")} @@ -145,11 +197,31 @@ export default function OnboardingChecklist({ hasJobs }: { hasJobs: boolean | nu {step.label} - {step.done === false ? : null} + {step.done === false ? + {skipped.includes(step.path) ? <> + {t('onboardingSkipped')} + + : <> + + + } + : null} ))} + {skipped.length ? {t('onboardingSkipHelp')} : null} : null} + {guideStep !== null ? + + {t('onboardingGuideCount', { current: guideStep + 1, total: steps.length })} + + + + + + + + : null} ); } diff --git a/job-tracker-ui/src/dashboard-customisation.test.tsx b/job-tracker-ui/src/dashboard-customisation.test.tsx index 75e94969..2fbe0dab 100644 --- a/job-tracker-ui/src/dashboard-customisation.test.tsx +++ b/job-tracker-ui/src/dashboard-customisation.test.tsx @@ -80,9 +80,13 @@ test('all hidden widgets leave an accessible recovery and reset remains cancella test('keyboard ordering keeps focus and announces the resulting position without refetching reports', async () => { open(); const editor = await edit(); const before = mocked.get.mock.calls.length; const preview = widgetOptions('Today'); - const move = within(preview).getByRole('button', { name: 'Move Today earlier' }); move.focus(); fireEvent.click(move); + const move = within(preview).getByRole('button', { name: 'Move Today later' }); act(() => move.focus()); fireEvent.click(move); + const earlier = within(preview).getByRole('button', { name: 'Move Today earlier' }); + await waitFor(() => expect(earlier).toHaveFocus()); + expect(screen.getByText('Today moved to position 2 of 10.')).toBeInTheDocument(); + fireEvent.click(earlier); await waitFor(() => expect(within(preview).getByRole('button', { name: 'Move Today later' })).toHaveFocus()); - expect(screen.getByText('Today moved to position 1 of 9.')).toBeInTheDocument(); + expect(screen.getByText('Today moved to position 1 of 10.')).toBeInTheDocument(); expect(mocked.get.mock.calls).toHaveLength(before); }); diff --git a/job-tracker-ui/src/dashboard-presentation.test.tsx b/job-tracker-ui/src/dashboard-presentation.test.tsx index 637fdb50..0a854d54 100644 --- a/job-tracker-ui/src/dashboard-presentation.test.tsx +++ b/job-tracker-ui/src/dashboard-presentation.test.tsx @@ -30,6 +30,21 @@ beforeEach(() => { mockedApi.put.mockImplementation((_url: string, body: any) => { layout = { ...body, revision: body.revision + 1 }; return Promise.resolve({ data: layout }) as any; }); }); +test('A1 week glance shows recorded upcoming dates from attention data without inventing missing dates', async () => { + const tomorrow = new Date(); tomorrow.setDate(tomorrow.getDate() + 1); + const deadline = `${tomorrow.getFullYear()}-${String(tomorrow.getMonth() + 1).padStart(2, '0')}-${String(tomorrow.getDate()).padStart(2, '0')}`; + const jobs = [ + { id: 91, jobTitle: 'School vacancy', company: { name: 'Linden School' }, deadline }, + { id: 92, jobTitle: 'No recorded date', company: { name: 'Care centre' }, deadline: 'as soon as possible' }, + ]; + mockedApi.get.mockImplementation((url: string) => Promise.resolve({ data: url.endsWith('/reminders') ? jobs : data(url) }) as any); + open(); + const week = await screen.findByRole('region', { name: 'Your week' }); + expect(within(week).getByText('School vacancy')).toBeVisible(); + expect(within(week).queryByText('No recorded date')).not.toBeInTheDocument(); + expect(within(week).getByRole('link', { name: /School vacancy/ })).toHaveAttribute('href', '/jobs/91'); +}); + test('[PRES-S01] exact monthly values, sample, custom statuses and separate salary group remain readable', async () => { open(); expect(await screen.findByText('Tracked jobs')).toBeVisible(); @@ -41,7 +56,7 @@ test('[PRES-S01] exact monthly values, sample, custom statuses and separate sala expect(within(table).getByRole('row', { name: 'September 2026 2 1' })).toBeVisible(); expect(screen.getByText('Faglig vurdering')).toBeInTheDocument(); expect(screen.getByText(/NOK.*2 jobs/)).toBeInTheDocument(); - expect(screen.queryByText(/upcoming|conversion funnel|active applications/i)).not.toBeInTheDocument(); + expect(screen.queryByText(/upcoming interviews|conversion funnel|active applications/i)).not.toBeInTheDocument(); }); test('[PRES-S02] all widget visibility choices save and survive remount', async () => { diff --git a/job-tracker-ui/src/dashboard-week.test.tsx b/job-tracker-ui/src/dashboard-week.test.tsx new file mode 100644 index 00000000..bb05df4e --- /dev/null +++ b/job-tracker-ui/src/dashboard-week.test.tsx @@ -0,0 +1,35 @@ +import React from 'react'; +import '@testing-library/jest-dom'; +import { render, screen } from '@testing-library/react'; +import { MemoryRouter } from 'react-router-dom'; +import DashboardWeek from './components/DashboardWeek'; +import { I18nProvider } from './i18n/I18nProvider'; +import type { JobApplication } from './types'; + +beforeEach(() => { localStorage.clear(); jest.useFakeTimers(); jest.setSystemTime(new Date(2026, 10, 3, 12)); }); +afterEach(() => { jest.useRealTimers(); localStorage.clear(); }); +function open(deadline?: string, followUpAt?: string) { + const jobs = [{ id: 3, jobTitle: 'Recorded role', company: { name: 'Example employer' }, deadline, followUpAt }] as JobApplication[]; + const original = JSON.stringify(jobs); + const view = render(); + expect(JSON.stringify(jobs)).toBe(original); + return view; +} +test.each([ + ['2026-11-03', true], ['2026-11-09', true], ['2026-11-10', false], ['2026-11-02', false], + ['as soon as possible', false], ['2026-11-04T24:00:00Z', false], ['2026-11-04T10:99:00Z', false], +])('deadline %s respects the local seven-day boundary and recorded precision', (value, visible) => { + const view = open(value); + expect(screen.queryByRole('link', { name: 'Recorded role' }) !== null).toBe(visible); + if (visible) expect(view.container.querySelector('time')).toHaveAttribute('datetime', value); +}); +test('invalid calendar dates do not silently roll into a real upcoming day', () => { + jest.setSystemTime(new Date(2026, 2, 1, 12)); open('2026-02-30'); + expect(screen.queryByRole('link', { name: 'Recorded role' })).not.toBeInTheDocument(); +}); +test('recorded follow-up timestamps and date-only deadlines stay distinct and untranslated', () => { + localStorage.setItem('uiLanguage', 'nb'); const view = open('2026-11-04', '2026-11-05T10:30:00Z'); + expect(screen.getByRole('heading', { name: 'Uken din' })).toBeInTheDocument(); + expect(screen.getByText('Søknadsfrist')).toBeInTheDocument(); expect(screen.getByText('Oppfølging')).toBeInTheDocument(); + expect([...view.container.querySelectorAll('time')].map(node => node.getAttribute('datetime'))).toEqual(['2026-11-04', '2026-11-05T10:30:00Z']); +}); diff --git a/job-tracker-ui/src/dashboardLayout.test.ts b/job-tracker-ui/src/dashboardLayout.test.ts index d1d33e90..3ecdd91a 100644 --- a/job-tracker-ui/src/dashboardLayout.test.ts +++ b/job-tracker-ui/src/dashboardLayout.test.ts @@ -31,13 +31,23 @@ test('schema two preserves chosen hidden geometry without resetting the owner re test('default catalogue preserves every section and supported widths', () => { const layout = defaultDashboardLayout(); - expect(layout.widgets.map(w => w.id)).toEqual(['cards', 'attention', 'funnel', 'activity', 'stageAge', 'sources', 'salary', 'companies', 'skills']); + expect(new Set(layout.widgets.map(w => w.id))).toEqual(new Set(WIDGETS.map(w => w.id))); expect(layout.widgets.every(w => w.visible)).toBe(true); expect(WIDGETS[0].sizes).toEqual(['full']); layout.widgets[0].visible = false; expect(defaultDashboardLayout().widgets[0].visible).toBe(true); }); +test.each([1, 2])('new schema %s preferences use task-first A1 defaults without omitting widgets', version => { + const layout = normalizeDashboardLayout({ version, revision: 0, widgets: [] }); + expect(layout.widgets[0]).toMatchObject({ id: 'attention', visible: true, x: 0, y: 0, w: 12 }); + expect(layout.widgets.findIndex(widget => widget.id === 'cards')).toBeGreaterThan(layout.widgets.findIndex(widget => widget.id === 'funnel')); + expect(layout.widgets).toHaveLength(WIDGETS.length); + const visible = layout.widgets.filter(widget => widget.visible); + expect(visible.every(widget => widget.x >= 0 && widget.x + widget.w <= 12 && widget.h <= 24 && widget.y + widget.h <= 256)).toBe(true); + expect(visible.some((a, index) => visible.slice(index + 1).some(b => a.x < b.x + b.w && a.x + a.w > b.x && a.y < b.y + b.h && a.y + a.h > b.y))).toBe(false); +}); + test('saved layouts retain known order, reject duplicates and append missing sections hidden', () => { const result = normalizeDashboardLayout({ version: 1, revision: 7, widgets: [ { id: 'skills', visible: false, size: 'full' }, @@ -49,7 +59,7 @@ test('saved layouts retain known order, reject duplicates and append missing sec expect(result.widgets[0]).toMatchObject({ id: 'skills', visible: false, size: 'full' }); expect(result.widgets[1]).toMatchObject({ id: 'cards', visible: false, size: 'full' }); expect(result.widgets.slice(2).every(w => !w.visible)).toBe(true); - expect(result.widgets).toHaveLength(9); + expect(result.widgets).toHaveLength(WIDGETS.length); }); test.each([null, {}, { version: 3, revision: 8, widgets: [] }, { version: 1, revision: -1, widgets: [] }])('invalid input has safe defaults: %j', input => { diff --git a/job-tracker-ui/src/dashboardLayout.ts b/job-tracker-ui/src/dashboardLayout.ts index 8522a6b9..20485401 100644 --- a/job-tracker-ui/src/dashboardLayout.ts +++ b/job-tracker-ui/src/dashboardLayout.ts @@ -3,6 +3,7 @@ export type WidgetSize = 'standard' | 'full'; export const WIDGETS = [ { id: 'cards', titleKey: 'dashboardSummaryCards', defaultSize: 'full', sizes: ['full'] }, { id: 'attention', titleKey: 'dashboardTodayTitle', defaultSize: 'standard', sizes: ['standard', 'full'] }, + { id: 'agenda', titleKey: 'dashboardYourWeek', defaultSize: 'standard', sizes: ['standard', 'full'] }, { id: 'funnel', titleKey: 'dashboardCurrentPipeline', defaultSize: 'standard', sizes: ['standard', 'full'] }, { id: 'activity', titleKey: 'dashboardApplicationActivity', defaultSize: 'full', sizes: ['standard', 'full'] }, { id: 'stageAge', titleKey: 'dashboardStageAge', defaultSize: 'standard', sizes: ['standard', 'full'] }, @@ -29,8 +30,12 @@ export function arrangeDashboardWidgets(widgets: DashboardWidget[]): DashboardWi } export function defaultDashboardLayout(): DashboardLayout { - return { version: 2, revision: 0, widgets: arrangeDashboardWidgets(WIDGETS.map(w => ({ id: w.id, visible: true, size: w.defaultSize, - x: 0, y: 0, w: w.defaultSize === 'full' ? 12 : 6, h: w.id === 'cards' ? 5 : 8 }))) }; + const order: WidgetId[] = ['attention', 'agenda', 'funnel', 'cards', 'activity', 'stageAge', 'sources', 'salary', 'companies', 'skills']; + return { version: 2, revision: 0, widgets: arrangeDashboardWidgets(order.map(id => { + const definition = WIDGETS.find(widget => widget.id === id)!; + const w = id === 'attention' ? 12 : id === 'agenda' ? 8 : id === 'funnel' ? 4 : definition.defaultSize === 'full' ? 12 : 6; + return { id, visible: true, size: w === 12 ? 'full' : 'standard', x: 0, y: 0, w, h: id === 'cards' ? 5 : id === 'attention' ? 9 : 8 }; + })) }; } export function normalizeDashboardLayout(input: unknown): DashboardLayout { @@ -54,8 +59,8 @@ export function normalizeDashboardLayout(input: unknown): DashboardLayout { size: w === 12 ? 'full' : 'standard', x: valid ? item.x : 0, y: valid ? item.y : 0, w, h: valid ? item.h : definition.id === 'cards' ? 5 : 8 }); } - for (const definition of WIDGETS) { - if (!seen.has(definition.id)) widgets.push({ ...defaultDashboardLayout().widgets.find(w => w.id === definition.id)!, visible: revision === 0 }); + for (const widget of defaultDashboardLayout().widgets) { + if (!seen.has(widget.id)) widgets.push({ ...widget, visible: revision === 0 }); } return { version: 2, revision, widgets: value.version === 1 ? arrangeDashboardWidgets(widgets) : widgets }; } diff --git a/job-tracker-ui/src/hooks/useDashboardLayout.test.tsx b/job-tracker-ui/src/hooks/useDashboardLayout.test.tsx index b953bf3e..295b57ca 100644 --- a/job-tracker-ui/src/hooks/useDashboardLayout.test.tsx +++ b/job-tracker-ui/src/hooks/useDashboardLayout.test.tsx @@ -19,8 +19,8 @@ test('loads once, migrates only owner legacy flags, and cancels without writing' localStorage.setItem('dashboardPrefs:a', JSON.stringify({ cards: false, activity: false })); const { result, rerender } = renderHook(() => useDashboardLayout(true)); await waitFor(() => expect(result.current.loading).toBe(false)); - expect(result.current.layout.widgets[0].visible).toBe(false); - expect(result.current.layout.widgets[1].visible).toBe(true); + expect(result.current.layout.widgets.find(widget => widget.id === 'cards')?.visible).toBe(false); + expect(result.current.layout.widgets.find(widget => widget.id === 'attention')?.visible).toBe(true); act(() => result.current.startEditing()); act(() => result.current.setDraft(d => d && ({ ...d, widgets: [...d.widgets].reverse() }))); rerender(); diff --git a/job-tracker-ui/src/i18n/translations.ts b/job-tracker-ui/src/i18n/translations.ts index 81f3374a..5d89c830 100644 --- a/job-tracker-ui/src/i18n/translations.ts +++ b/job-tracker-ui/src/i18n/translations.ts @@ -1960,6 +1960,25 @@ export const translations = { dashboardOverviewTitle: "Dashboard overview", dashboardHeroLabel: "Job search overview", onboardingTitle: "Get set up", + onboardingResumeTitle: "Make Jobbjakt yours", + onboardingResumeBody: "Optional setup. Pick up whenever you are ready.", + onboardingResume: "Continue setup", + onboardingWalkthrough: "Show walkthrough", + onboardingSkipAction: "Skip for now", + onboardingSkipStep: "Skip for now: {name}", + onboardingResumeStep: "Resume step: {name}", + onboardingResumeStepAction: "Resume", + onboardingSkipped: "Skipped for now", + onboardingSkipHelp: "Skipped steps stay optional and do not count as completed.", + onboardingGuideProfile: "Review your account details and security. Your account identity is separate from your Career Profile.", + onboardingGuideCareer: "Record experience you want to reuse. Import is optional; review changes before applying them to your Career Profile.", + onboardingGuideEmail: "Connect a mailbox only if you want to. Sign-in identity and mailbox access are separate permissions.", + onboardingGuideJob: "Add a job manually or discover an opportunity. Setup is never required before you start tracking.", + onboardingGuidePrevious: "Previous step", + onboardingGuideNext: "Next step", + onboardingGuideFinish: "Finish walkthrough", + onboardingGuideClose: "Close walkthrough", + onboardingGuideCount: "Walkthrough step {current} of {total}", onboardingBody: "Setup guidance based on your saved account details, Career information, mailbox connections and tracked jobs.", onboardingLoadFailed: "Could not load setup status.", onboardingJobStatusUnknown: "Tracked-job status is unavailable. Retry the dashboard summary to check your jobs.", @@ -1976,6 +1995,14 @@ export const translations = { onboardingStepEmailAction: "Connect email", dashboardResponseRate: "{rate}% response rate", dashboardTodayTitle: "Today", + dashboardYourWeek: "Your week", + dashboardGoodMorning: "Good morning, {name}", + dashboardGoodAfternoon: "Good afternoon, {name}", + dashboardGoodEvening: "Good evening, {name}", + dashboardWeekScope: "Upcoming deadlines and follow-ups recorded in your current attention list. More dates may exist outside this list.", + dashboardWeekDeadline: "Application deadline", + dashboardWeekFollowUp: "Follow-up", + dashboardWeekEmpty: "No recorded dates in these priorities for the next seven days.", dashboardTodayBody: "Your highest-priority application actions, ordered by urgency.", dashboardSummaryUnavailableTitle: "Unable to load dashboard summary", dashboardSummaryUnavailableBody: "The dashboard summary is unavailable right now.", @@ -5085,6 +5112,25 @@ export const translations = { dashboardOverviewTitle: "Dashboard-oversikt", dashboardHeroLabel: "Oversikt over jobbsøket", onboardingTitle: "Kom i gang", + onboardingResumeTitle: "Gjør Jobbjakt til ditt", + onboardingResumeBody: "Valgfritt oppsett. Fortsett når du er klar.", + onboardingResume: "Fortsett oppsett", + onboardingWalkthrough: "Vis veiledning", + onboardingSkipAction: "Hopp over foreløpig", + onboardingSkipStep: "Hopp over foreløpig: {name}", + onboardingResumeStep: "Fortsett steg: {name}", + onboardingResumeStepAction: "Fortsett", + onboardingSkipped: "Hoppet over foreløpig", + onboardingSkipHelp: "Steg du hopper over, er fortsatt valgfrie og telles ikke som fullført.", + onboardingGuideProfile: "Se gjennom kontodetaljer og sikkerhet. Kontoidentiteten er atskilt fra karriereprofilen din.", + onboardingGuideCareer: "Registrer erfaring du vil gjenbruke. Import er valgfritt; se gjennom endringer før du tar dem inn i karriereprofilen.", + onboardingGuideEmail: "Koble til en postboks bare hvis du ønsker det. Innloggingsidentitet og postbokstilgang er ulike tillatelser.", + onboardingGuideJob: "Legg til en jobb manuelt eller finn en stilling. Oppsett er aldri påkrevd før du begynner å følge opp jobber.", + onboardingGuidePrevious: "Forrige steg", + onboardingGuideNext: "Neste steg", + onboardingGuideFinish: "Fullfør veiledning", + onboardingGuideClose: "Lukk veiledning", + onboardingGuideCount: "Veiledning, steg {current} av {total}", onboardingBody: "Veiledning basert på lagrede kontoopplysninger, karriereinformasjon, tilkoblede postkasser og stillinger du følger.", onboardingLoadFailed: "Kunne ikke laste oppsettstatus.", onboardingJobStatusUnknown: "Status for stillinger er ikke tilgjengelig. Last oversikten på dashbordet på nytt for å sjekke stillingene dine.", @@ -5101,6 +5147,14 @@ export const translations = { onboardingStepEmailAction: "Koble til e-post", dashboardResponseRate: "{rate}% svarrate", dashboardTodayTitle: "I dag", + dashboardYourWeek: "Uken din", + dashboardGoodMorning: "God morgen, {name}", + dashboardGoodAfternoon: "God ettermiddag, {name}", + dashboardGoodEvening: "God kveld, {name}", + dashboardWeekScope: "Kommende søknadsfrister og oppfølginger i listen over det du bør følge opp. Flere datoer kan finnes utenfor listen.", + dashboardWeekDeadline: "Søknadsfrist", + dashboardWeekFollowUp: "Oppfølging", + dashboardWeekEmpty: "Ingen registrerte datoer blant disse prioriteringene de neste sju dagene.", dashboardTodayBody: "De viktigste handlingene for søknadene dine, sortert etter hvor mye de haster.", dashboardSummaryUnavailableTitle: "Kunne ikke laste dashboard-oppsummeringen", dashboardSummaryUnavailableBody: "Dashboard-oppsummeringen er ikke tilgjengelig akkurat nå.", diff --git a/job-tracker-ui/src/onboarding-checklist.test.tsx b/job-tracker-ui/src/onboarding-checklist.test.tsx index 788a1132..891ba111 100644 --- a/job-tracker-ui/src/onboarding-checklist.test.tsx +++ b/job-tracker-ui/src/onboarding-checklist.test.tsx @@ -56,6 +56,43 @@ afterEach(() => { window.localStorage.clear(); }); +test('a dismissed optional checklist resumes without losing source-backed progress', async () => { + localStorage.setItem('onboardingDismissed:owner-a', 'true'); + const view = render(); + expect(mockedApi.get).not.toHaveBeenCalled(); + fireEvent.click(screen.getByRole('button', { name: 'Continue setup' })); + expect(await screen.findByText('3 / 4')).toBeInTheDocument(); + expect(localStorage.getItem('onboardingDismissed:owner-a')).toBe('false'); + view.unmount(); render(); + expect(await screen.findByText('3 / 4')).toBeInTheDocument(); + expectNoWrites(); +}); + +test('individual skips persist for their owner but do not count as completed data', async () => { + const view = render(); await screen.findByText('3 / 4'); + fireEvent.click(screen.getByRole('button', { name: /^Skip for now:/ })); + expect(screen.getByRole('progressbar', { name: '3 of 4 done' })).toHaveAttribute('aria-valuenow', '75'); + expect(localStorage.getItem('onboardingSkipped:owner-a')).toBe('["/jobs"]'); + expect(screen.getByRole('button', { name: /^Resume step:/ })).toHaveFocus(); + view.unmount(); render(); await screen.findByText('3 / 4'); + fireEvent.click(screen.getByRole('button', { name: /^Resume step:/ })); + expect(screen.getByRole('button', { name: translations.en.onboardingStepJobAction })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: translations.en.onboardingStepJobAction })).toHaveFocus(); + expectNoWrites(); +}); + +test('on-demand walkthrough changes guidance and focus without changing completion or connecting providers', async () => { + render(); await screen.findByText('3 / 4'); + fireEvent.click(screen.getByRole('button', { name: 'Show walkthrough' })); + const guide = screen.getByRole('region', { name: translations.en.onboardingStepProfile }); + expect(within(guide).getByRole('heading')).toHaveFocus(); + fireEvent.click(within(guide).getByRole('button', { name: 'Next step' })); + expect(screen.getByRole('heading', { name: translations.en.onboardingStepCv })).toHaveFocus(); + expect(screen.getByText('3 / 4')).toBeInTheDocument(); + expect(mockedApi.get).toHaveBeenCalledTimes(5); + expectNoWrites(); +}); + test("uses structured career data and connected inbox status for four source-backed onboarding steps", async () => { render(); @@ -145,7 +182,7 @@ test.each(sources)("failed %s status stays unknown and retries only the same sou rejectFirst = false; const retry = within(alert).getByRole("button", { name: translations.en.retry }); - retry.focus(); + act(() => retry.focus()); fireEvent.click(retry); expect(await screen.findByText("3 / 4")).toBeInTheDocument(); expect(screen.getByRole("button", { name: translations.en.onboardingDismiss })).toHaveFocus();