From cd116ab8ca33253bbd919d3268f35001f0764f14 Mon Sep 17 00:00:00 2001 From: cesnimda Date: Wed, 4 Nov 2026 08:16:01 +0100 Subject: [PATCH] fix(i18n): use singular mail label counts in English and Bokmal --- .../visual-correction/MAIL_LABELS.md | 9 +++++++++ .../visual-correction/MAIL_LABEL_RED.json | 1 + .../visual-correction/MAIL_LABEL_TESTS.json | 1 + .../scripts/verify-a3-documents-inbox.mjs | 15 +++++++++++---- .../src/correspondence-inbox-page.test.tsx | 17 +++++++++++++++++ job-tracker-ui/src/i18n/translations.ts | 2 ++ .../src/views/CorrespondenceInboxPage.tsx | 2 +- 7 files changed, 42 insertions(+), 5 deletions(-) create mode 100644 docs/ui-modernisation/visual-correction/MAIL_LABELS.md create mode 100644 docs/ui-modernisation/visual-correction/MAIL_LABEL_RED.json create mode 100644 docs/ui-modernisation/visual-correction/MAIL_LABEL_TESTS.json diff --git a/docs/ui-modernisation/visual-correction/MAIL_LABELS.md b/docs/ui-modernisation/visual-correction/MAIL_LABELS.md new file mode 100644 index 00000000..b2696d27 --- /dev/null +++ b/docs/ui-modernisation/visual-correction/MAIL_LABELS.md @@ -0,0 +1,9 @@ +# Mail label-count localisation + +4 November 2026. Continue from published/independently verified1881fdb2, runtimea9208ab4. Actual EN/light320 and NB/dark1440 captures showed `1 labels` / `1 etiketter`. The only caller uses a plural catalogue key regardless of count. + +Add a paired singular `emailInboxLabelCount` entry and select it only for1. Preserve the plural entry, counts, zero-count hiding, provider label text, metadata, actions and message context. Both calls use literal keys; this adds no dynamic translation call beyond the separately reviewed historical188/new public7. Generated content and stored language/data remain unchanged. + +RED: two initial EN/NB cases failed;39 other inbox cases filtered. GREEN:44 cases/two suites PASSED (41 inbox including both new singular/plural/live-switch regressions, three catalogue cases). Locale switching updates1/2 labels without additional reads or mutations. This replaces the overlapping39 inbox total, not39+44. Types/full lint and scoped verifier lint passed. + +Pending: source commit/export build and focused36-configuration label display run, source/export/evidence checks and publication. The existing144-state mail workflow qualification is reused for unchanged behavior; label-only mode explicitly skips document/draft/send workflows, expects one state/configuration and zero writes, and captures the actual chip in its viewport. No new provider/mailbox/model, native-language/human/device, final security or release acceptance is implied. Rollback reverts this label/catalogue/test delta; no database or dependency change. diff --git a/docs/ui-modernisation/visual-correction/MAIL_LABEL_RED.json b/docs/ui-modernisation/visual-correction/MAIL_LABEL_RED.json new file mode 100644 index 00000000..35f5d916 --- /dev/null +++ b/docs/ui-modernisation/visual-correction/MAIL_LABEL_RED.json @@ -0,0 +1 @@ +{"numFailedTestSuites":1,"numFailedTests":2,"numPassedTestSuites":0,"numPassedTests":0,"numPendingTestSuites":0,"numPendingTests":39,"numRuntimeErrorTestSuites":0,"numTodoTests":0,"numTotalTestSuites":1,"numTotalTests":41,"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":1793776366232,"success":false,"testResults":[{"assertionResults":[{"ancestorTitles":["CorrespondenceInboxPage"],"duration":256,"failing":false,"failureDetails":[{"name":"TestingLibraryElementError"}],"failureMessages":["TestingLibraryElementError: Unable to find an element with the text: 1 label. This could be because the text is broken up by multiple elements. In this case, you can provide a function for your text matcher to make your matcher more flexible.\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[0mAcme Systems\u001b[0m\n \u001b[0m • \u001b[0m\n \u001b[0mBackend Engineer\u001b[0m\n \u001b[36m

\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mOne label\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mMaria <maria@acme.test>\u001b[0m\n \u001b[0m \u001b[0m\n \u001b[0m→ owner@gmail.test\u001b[0m\n \u001b[0m · \u001b[0m\n \u001b[0m2/13/2026, 12:30:00 PM\u001b[0m\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[0mInbound\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mGmail\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mThread thread-1\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0m1 labels\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mView message\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mOpen job\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mUnlink thread\u001b[0m\n \u001b[36m\u001b[39m\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 getByText (C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\src\\correspondence-inbox-page.test.tsx:130:38)\n at assertLabels (C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\src\\correspondence-inbox-page.test.tsx:133:5)"],"fullName":"CorrespondenceInboxPage en label counts keep singular/plural grammar through a live locale change","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793776369833,"status":"failed","title":"en label counts keep singular/plural grammar through a live locale change"},{"ancestorTitles":["CorrespondenceInboxPage"],"duration":122,"failing":false,"failureDetails":[{"name":"TestingLibraryElementError"}],"failureMessages":["TestingLibraryElementError: Unable to find an element with the text: 1 etikett. This could be because the text is broken up by multiple elements. In this case, you can provide a function for your text matcher to make your matcher more flexible.\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[0mAcme Systems\u001b[0m\n \u001b[0m • \u001b[0m\n \u001b[0mBackend Engineer\u001b[0m\n \u001b[36m

\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mOne label\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mMaria <maria@acme.test>\u001b[0m\n \u001b[0m \u001b[0m\n \u001b[0m→ owner@gmail.test\u001b[0m\n \u001b[0m · \u001b[0m\n \u001b[0m13.2.2026, 12:30:00\u001b[0m\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[0mInnkommende\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mGmail\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mTråd thread-1\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0m1 etiketter\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mVis melding\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mÅpne jobb\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mFjern trådtilknytning\u001b[0m\n \u001b[36m\u001b[39m\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 getByText (C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\src\\correspondence-inbox-page.test.tsx:130:38)\n at assertLabels (C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\src\\correspondence-inbox-page.test.tsx:133:5)"],"fullName":"CorrespondenceInboxPage nb label counts keep singular/plural grammar through a live locale change","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793776370109,"status":"failed","title":"nb label counts keep singular/plural grammar through a live locale change"},{"ancestorTitles":["CorrespondenceInboxPage"],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage en opened message retains one application action in named context","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793776370231,"status":"pending","title":"en opened message retains one application action in named context"},{"ancestorTitles":["CorrespondenceInboxPage"],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage nb opened message retains one application action in named context","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793776370231,"status":"pending","title":"nb opened message retains one application action in named context"},{"ancestorTitles":["CorrespondenceInboxPage"],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage en inbox filters and messages support request-free keyboard navigation","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793776370231,"status":"pending","title":"en inbox filters and messages support request-free keyboard navigation"},{"ancestorTitles":["CorrespondenceInboxPage"],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage nb inbox filters and messages support request-free keyboard navigation","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793776370231,"status":"pending","title":"nb inbox filters and messages support request-free keyboard navigation"},{"ancestorTitles":["CorrespondenceInboxPage"],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage [P7-11 inbox] resumed draft fallback metadata follows locale without mutating stored prose or refetching","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793776370231,"status":"pending","title":"[P7-11 inbox] resumed draft fallback metadata follows locale without mutating stored prose or refetching"},{"ancestorTitles":["CorrespondenceInboxPage"],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage renders correspondence inbox items and reloads with filters","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793776370232,"status":"pending","title":"renders correspondence inbox items and reloads with filters"},{"ancestorTitles":["CorrespondenceInboxPage"],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage keeps saved correspondence visible when provider status is unavailable","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793776370232,"status":"pending","title":"keeps saved correspondence visible when provider status is unavailable"},{"ancestorTitles":["CorrespondenceInboxPage"],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage opens provider-backed plain-text message detail in the hub","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793776370232,"status":"pending","title":"opens provider-backed plain-text message detail in the hub"},{"ancestorTitles":["CorrespondenceInboxPage"],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage navigates beyond the first correspondence page","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793776370232,"status":"pending","title":"navigates beyond the first correspondence page"},{"ancestorTitles":["CorrespondenceInboxPage"],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage unlinks a Gmail thread only after confirmation and returns it to review","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793776370232,"status":"pending","title":"unlinks a Gmail thread only after confirmation and returns it to review"},{"ancestorTitles":["CorrespondenceInboxPage"],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage falls back to the saved copy when provider detail is unavailable","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793776370232,"status":"pending","title":"falls back to the saved copy when provider detail is unavailable"},{"ancestorTitles":["CorrespondenceInboxPage"],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage keeps replies editable and requires final send confirmation","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793776370232,"status":"pending","title":"keeps replies editable and requires final send confirmation"},{"ancestorTitles":["CorrespondenceInboxPage"],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage saves an incomplete reply with its server delivery identity","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793776370232,"status":"pending","title":"saves an incomplete reply with its server delivery identity"},{"ancestorTitles":["CorrespondenceInboxPage"],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage resumes a saved draft and surfaces revision conflicts without overwriting it","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793776370232,"status":"pending","title":"resumes a saved draft and surfaces revision conflicts without overwriting it"},{"ancestorTitles":["CorrespondenceInboxPage"],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage persists a new delivery identity only after a definitive failed attempt","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793776370232,"status":"pending","title":"persists a new delivery identity only after a definitive failed attempt"},{"ancestorTitles":["CorrespondenceInboxPage"],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage starts a new-message draft only from an owned job and send-capable provider choice","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793776370232,"status":"pending","title":"starts a new-message draft only from an owned job and send-capable provider choice"},{"ancestorTitles":["CorrespondenceInboxPage"],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage searches the complete server-side job choice set while composing","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793776370232,"status":"pending","title":"searches the complete server-side job choice set while composing"},{"ancestorTitles":["CorrespondenceInboxPage"],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage does not offer retry when delivery is uncertain","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793776370232,"status":"pending","title":"does not offer retry when delivery is uncertain"},{"ancestorTitles":["CorrespondenceInboxPage"],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage uses one hub for linked messages and recruitment suggestions","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793776370232,"status":"pending","title":"uses one hub for linked messages and recruitment suggestions"},{"ancestorTitles":["CorrespondenceInboxPage"],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage localizes inbox controls while preserving correspondence content","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793776370232,"status":"pending","title":"localizes inbox controls while preserving correspondence content"},{"ancestorTitles":["CorrespondenceInboxPage"],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage legacy Gmail review route redirects into the consolidated hub filter","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793776370232,"status":"pending","title":"legacy Gmail review route redirects into the consolidated hub filter"},{"ancestorTitles":["CorrespondenceInboxPage"],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage failed inbox reads retain a current-language error and recover with one explicit GET-only Retry","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793776370232,"status":"pending","title":"failed inbox reads retain a current-language error and recover with one explicit GET-only Retry"},{"ancestorTitles":["CorrespondenceInboxPage"],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage a late old page cannot overwrite the latest query and page-one inbox snapshot","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793776370232,"status":"pending","title":"a late old page cannot overwrite the latest query and page-one inbox snapshot"},{"ancestorTitles":["CorrespondenceInboxPage"],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage message Retry replays the captured owned detail read without hiding it and localizes saved-copy fallback","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793776370232,"status":"pending","title":"message Retry replays the captured owned detail read without hiding it and localizes saved-copy fallback"},{"ancestorTitles":["CorrespondenceInboxPage"],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage hidden or inactive details never resume a saved-copy fallback and hub return preserves inbox context","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793776370232,"status":"pending","title":"hidden or inactive details never resume a saved-copy fallback and hub return preserves inbox context"},{"ancestorTitles":["CorrespondenceInboxPage"],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage provider status Retry is isolated from saved messages and drafts and live locale changes cause no reads","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793776370232,"status":"pending","title":"provider status Retry is isolated from saved messages and drafts and live locale changes cause no reads"},{"ancestorTitles":["CorrespondenceInboxPage"],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage confirmed unlink owns pending actions and a failed successful-write refresh recovers with GET only","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793776370232,"status":"pending","title":"confirmed unlink owns pending actions and a failed successful-write refresh recovers with GET only"},{"ancestorTitles":["CorrespondenceInboxPage"],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage literal all search preserves source and hash and hides another query snapshot while retaining same-query refresh data","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793776370232,"status":"pending","title":"literal all search preserves source and hash and hides another query snapshot while retaining same-query refresh data"},{"ancestorTitles":["CorrespondenceInboxPage"],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage typing an inbox search keeps one natural Back step to the prior screen and preserves filter context","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793776370232,"status":"pending","title":"typing an inbox search keeps one natural Back step to the prior screen and preserves filter context"},{"ancestorTitles":["CorrespondenceInboxPage"],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage held draft Save captures one write and locks edits, Send, Discard and every draft replacement entry","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793776370232,"status":"pending","title":"held draft Save captures one write and locks edits, Send, Discard and every draft replacement entry"},{"ancestorTitles":["CorrespondenceInboxPage"],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage double Review creates one confirmation, Cancel sends nothing and explicit double Send owns one captured attempt","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793776370232,"status":"pending","title":"double Review creates one confirmation, Cancel sends nothing and explicit double Send owns one captured attempt"},{"ancestorTitles":["CorrespondenceInboxPage"],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage recipient, subject and body edits cannot erase any recorded delivery state or rotate its attempt identity","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793776370232,"status":"pending","title":"recipient, subject and body edits cannot erase any recorded delivery state or rotate its attempt identity"},{"ancestorTitles":["CorrespondenceInboxPage"],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage generic 409 cannot prepare a new attempt while an authoritative failed result requires one explicit server rotation","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793776370232,"status":"pending","title":"generic 409 cannot prepare a new attempt while an authoritative failed result requires one explicit server rotation"},{"ancestorTitles":["CorrespondenceInboxPage"],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage recipient-only and subject-only unsaved changes require replacement and discard confirmation whose Cancel retains the draft","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793776370232,"status":"pending","title":"recipient-only and subject-only unsaved changes require replacement and discard confirmation whose Cancel retains the draft"},{"ancestorTitles":["CorrespondenceInboxPage"],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage saved-draft initial read and post-save refresh failures recover with GET-only Retry without replaying the successful Save","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793776370232,"status":"pending","title":"saved-draft initial read and post-save refresh failures recover with GET-only Retry without replaying the successful Save"},{"ancestorTitles":["CorrespondenceInboxPage"],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage a late Save after inbox unmount cannot refresh drafts or attach its result to another page","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793776370232,"status":"pending","title":"a late Save after inbox unmount cannot refresh drafts or attach its result to another page"},{"ancestorTitles":["CorrespondenceInboxPage"],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage a held managed-draft 409 retains recipient, subject, body and revision with current-language original Save recovery","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793776370232,"status":"pending","title":"a held managed-draft 409 retains recipient, subject, body and revision with current-language original Save recovery"},{"ancestorTitles":["CorrespondenceInboxPage"],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage server attempt rotation preserves three unsaved fields and their dirty state while the next send uses the new identity","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793776370232,"status":"pending","title":"server attempt rotation preserves three unsaved fields and their dirty state while the next send uses the new identity"},{"ancestorTitles":["CorrespondenceInboxPage"],"duration":null,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage a committed Save starts a fresh draft-list read despite an older pending GET and rejects the late old snapshot","invocations":1,"location":null,"numPassingAsserts":0,"retryReasons":[],"startAt":1793776370232,"status":"pending","title":"a committed Save starts a fresh draft-list read despite an older pending GET and rejects the late old snapshot"}],"endTime":1793776370246,"message":" ● CorrespondenceInboxPage › en label counts keep singular/plural grammar through a live locale change\n\n TestingLibraryElementError: Unable to find an element with the text: 1 label. This could be because the text is broken up by multiple elements. In this case, you can provide a function for your text matcher to make your matcher more flexible.\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[0mAcme Systems\u001b[0m\n \u001b[0m • \u001b[0m\n \u001b[0mBackend Engineer\u001b[0m\n \u001b[36m

\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mOne label\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mMaria <maria@acme.test>\u001b[0m\n \u001b[0m \u001b[0m\n \u001b[0m→ owner@gmail.test\u001b[0m\n \u001b[0m · \u001b[0m\n \u001b[0m2/13/2026, 12:30:00 PM\u001b[0m\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[0mInbound\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mGmail\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mThread thread-1\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0m1 labels\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mView message\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mOpen job\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mUnlink thread\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n\n 128 | const reads = mockedApi.get.mock.calls.length;\n 129 | const assertLabels = (locale: UiLanguage) => {\n > 130 | expect(messageRow('One label').getByText(locale === 'en' ? '1 label' : '1 etikett', { exact: true })).toBeVisible();\n | ^\n 131 | expect(messageRow('Two labels').getByText(locale === 'en' ? '2 labels' : '2 etiketter', { exact: true })).toBeVisible();\n 132 | };\n 133 | assertLabels(language);\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 getByText (src/correspondence-inbox-page.test.tsx:130:38)\n at assertLabels (src/correspondence-inbox-page.test.tsx:133:5)\n\n ● CorrespondenceInboxPage › nb label counts keep singular/plural grammar through a live locale change\n\n TestingLibraryElementError: Unable to find an element with the text: 1 etikett. This could be because the text is broken up by multiple elements. In this case, you can provide a function for your text matcher to make your matcher more flexible.\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[0mAcme Systems\u001b[0m\n \u001b[0m • \u001b[0m\n \u001b[0mBackend Engineer\u001b[0m\n \u001b[36m

\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mOne label\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mMaria <maria@acme.test>\u001b[0m\n \u001b[0m \u001b[0m\n \u001b[0m→ owner@gmail.test\u001b[0m\n \u001b[0m · \u001b[0m\n \u001b[0m13.2.2026, 12:30:00\u001b[0m\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[0mInnkommende\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mGmail\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mTråd thread-1\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0m1 etiketter\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mVis melding\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mÅpne jobb\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[0mFjern trådtilknytning\u001b[0m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n \u001b[36m\u001b[39m\n\n 128 | const reads = mockedApi.get.mock.calls.length;\n 129 | const assertLabels = (locale: UiLanguage) => {\n > 130 | expect(messageRow('One label').getByText(locale === 'en' ? '1 label' : '1 etikett', { exact: true })).toBeVisible();\n | ^\n 131 | expect(messageRow('Two labels').getByText(locale === 'en' ? '2 labels' : '2 etiketter', { exact: true })).toBeVisible();\n 132 | };\n 133 | assertLabels(language);\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 getByText (src/correspondence-inbox-page.test.tsx:130:38)\n at assertLabels (src/correspondence-inbox-page.test.tsx:133:5)\n","name":"C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\src\\correspondence-inbox-page.test.tsx","startTime":1793776367163,"status":"failed","summary":""}],"wasInterrupted":false} diff --git a/docs/ui-modernisation/visual-correction/MAIL_LABEL_TESTS.json b/docs/ui-modernisation/visual-correction/MAIL_LABEL_TESTS.json new file mode 100644 index 00000000..36a84f22 --- /dev/null +++ b/docs/ui-modernisation/visual-correction/MAIL_LABEL_TESTS.json @@ -0,0 +1 @@ +{"numFailedTestSuites":0,"numFailedTests":0,"numPassedTestSuites":2,"numPassedTests":44,"numPendingTestSuites":0,"numPendingTests":0,"numRuntimeErrorTestSuites":0,"numTodoTests":0,"numTotalTestSuites":2,"numTotalTests":44,"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":1793776387223,"success":true,"testResults":[{"assertionResults":[{"ancestorTitles":["CorrespondenceInboxPage"],"duration":339,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage en label counts keep singular/plural grammar through a live locale change","invocations":1,"location":null,"numPassingAsserts":8,"retryReasons":[],"startAt":1793776391534,"status":"passed","title":"en label counts keep singular/plural grammar through a live locale change"},{"ancestorTitles":["CorrespondenceInboxPage"],"duration":221,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage nb label counts keep singular/plural grammar through a live locale change","invocations":1,"location":null,"numPassingAsserts":8,"retryReasons":[],"startAt":1793776391874,"status":"passed","title":"nb label counts keep singular/plural grammar through a live locale change"},{"ancestorTitles":["CorrespondenceInboxPage"],"duration":279,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage en opened message retains one application action in named context","invocations":1,"location":null,"numPassingAsserts":9,"retryReasons":[],"startAt":1793776392095,"status":"passed","title":"en opened message retains one application action in named context"},{"ancestorTitles":["CorrespondenceInboxPage"],"duration":257,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage nb opened message retains one application action in named context","invocations":1,"location":null,"numPassingAsserts":9,"retryReasons":[],"startAt":1793776392374,"status":"passed","title":"nb opened message retains one application action in named context"},{"ancestorTitles":["CorrespondenceInboxPage"],"duration":186,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage en inbox filters and messages support request-free keyboard navigation","invocations":1,"location":null,"numPassingAsserts":7,"retryReasons":[],"startAt":1793776392631,"status":"passed","title":"en inbox filters and messages support request-free keyboard navigation"},{"ancestorTitles":["CorrespondenceInboxPage"],"duration":173,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage nb inbox filters and messages support request-free keyboard navigation","invocations":1,"location":null,"numPassingAsserts":7,"retryReasons":[],"startAt":1793776392817,"status":"passed","title":"nb inbox filters and messages support request-free keyboard navigation"},{"ancestorTitles":["CorrespondenceInboxPage"],"duration":417,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage [P7-11 inbox] resumed draft fallback metadata follows locale without mutating stored prose or refetching","invocations":1,"location":null,"numPassingAsserts":7,"retryReasons":[],"startAt":1793776392990,"status":"passed","title":"[P7-11 inbox] resumed draft fallback metadata follows locale without mutating stored prose or refetching"},{"ancestorTitles":["CorrespondenceInboxPage"],"duration":355,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage renders correspondence inbox items and reloads with filters","invocations":1,"location":null,"numPassingAsserts":11,"retryReasons":[],"startAt":1793776393407,"status":"passed","title":"renders correspondence inbox items and reloads with filters"},{"ancestorTitles":["CorrespondenceInboxPage"],"duration":155,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage keeps saved correspondence visible when provider status is unavailable","invocations":1,"location":null,"numPassingAsserts":3,"retryReasons":[],"startAt":1793776393763,"status":"passed","title":"keeps saved correspondence visible when provider status is unavailable"},{"ancestorTitles":["CorrespondenceInboxPage"],"duration":266,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage opens provider-backed plain-text message detail in the hub","invocations":1,"location":null,"numPassingAsserts":4,"retryReasons":[],"startAt":1793776393918,"status":"passed","title":"opens provider-backed plain-text message detail in the hub"},{"ancestorTitles":["CorrespondenceInboxPage"],"duration":241,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage navigates beyond the first correspondence page","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1793776394185,"status":"passed","title":"navigates beyond the first correspondence page"},{"ancestorTitles":["CorrespondenceInboxPage"],"duration":295,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage unlinks a Gmail thread only after confirmation and returns it to review","invocations":1,"location":null,"numPassingAsserts":5,"retryReasons":[],"startAt":1793776394426,"status":"passed","title":"unlinks a Gmail thread only after confirmation and returns it to review"},{"ancestorTitles":["CorrespondenceInboxPage"],"duration":249,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage falls back to the saved copy when provider detail is unavailable","invocations":1,"location":null,"numPassingAsserts":3,"retryReasons":[],"startAt":1793776394721,"status":"passed","title":"falls back to the saved copy when provider detail is unavailable"},{"ancestorTitles":["CorrespondenceInboxPage"],"duration":1353,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage keeps replies editable and requires final send confirmation","invocations":1,"location":null,"numPassingAsserts":14,"retryReasons":[],"startAt":1793776394970,"status":"passed","title":"keeps replies editable and requires final send confirmation"},{"ancestorTitles":["CorrespondenceInboxPage"],"duration":580,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage saves an incomplete reply with its server delivery identity","invocations":1,"location":null,"numPassingAsserts":3,"retryReasons":[],"startAt":1793776396323,"status":"passed","title":"saves an incomplete reply with its server delivery identity"},{"ancestorTitles":["CorrespondenceInboxPage"],"duration":521,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage resumes a saved draft and surfaces revision conflicts without overwriting it","invocations":1,"location":null,"numPassingAsserts":4,"retryReasons":[],"startAt":1793776396903,"status":"passed","title":"resumes a saved draft and surfaces revision conflicts without overwriting it"},{"ancestorTitles":["CorrespondenceInboxPage"],"duration":816,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage persists a new delivery identity only after a definitive failed attempt","invocations":1,"location":null,"numPassingAsserts":3,"retryReasons":[],"startAt":1793776397424,"status":"passed","title":"persists a new delivery identity only after a definitive failed attempt"},{"ancestorTitles":["CorrespondenceInboxPage"],"duration":873,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage starts a new-message draft only from an owned job and send-capable provider choice","invocations":1,"location":null,"numPassingAsserts":7,"retryReasons":[],"startAt":1793776398240,"status":"passed","title":"starts a new-message draft only from an owned job and send-capable provider choice"},{"ancestorTitles":["CorrespondenceInboxPage"],"duration":823,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage searches the complete server-side job choice set while composing","invocations":1,"location":null,"numPassingAsserts":3,"retryReasons":[],"startAt":1793776399113,"status":"passed","title":"searches the complete server-side job choice set while composing"},{"ancestorTitles":["CorrespondenceInboxPage"],"duration":914,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage does not offer retry when delivery is uncertain","invocations":1,"location":null,"numPassingAsserts":5,"retryReasons":[],"startAt":1793776399936,"status":"passed","title":"does not offer retry when delivery is uncertain"},{"ancestorTitles":["CorrespondenceInboxPage"],"duration":283,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage uses one hub for linked messages and recruitment suggestions","invocations":1,"location":null,"numPassingAsserts":3,"retryReasons":[],"startAt":1793776400850,"status":"passed","title":"uses one hub for linked messages and recruitment suggestions"},{"ancestorTitles":["CorrespondenceInboxPage"],"duration":125,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage localizes inbox controls while preserving correspondence content","invocations":1,"location":null,"numPassingAsserts":5,"retryReasons":[],"startAt":1793776401133,"status":"passed","title":"localizes inbox controls while preserving correspondence content"},{"ancestorTitles":["CorrespondenceInboxPage"],"duration":173,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage legacy Gmail review route redirects into the consolidated hub filter","invocations":1,"location":null,"numPassingAsserts":2,"retryReasons":[],"startAt":1793776401258,"status":"passed","title":"legacy Gmail review route redirects into the consolidated hub filter"},{"ancestorTitles":["CorrespondenceInboxPage"],"duration":251,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage failed inbox reads retain a current-language error and recover with one explicit GET-only Retry","invocations":1,"location":null,"numPassingAsserts":13,"retryReasons":[],"startAt":1793776401431,"status":"passed","title":"failed inbox reads retain a current-language error and recover with one explicit GET-only Retry"},{"ancestorTitles":["CorrespondenceInboxPage"],"duration":323,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage a late old page cannot overwrite the latest query and page-one inbox snapshot","invocations":1,"location":null,"numPassingAsserts":7,"retryReasons":[],"startAt":1793776401682,"status":"passed","title":"a late old page cannot overwrite the latest query and page-one inbox snapshot"},{"ancestorTitles":["CorrespondenceInboxPage"],"duration":447,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage message Retry replays the captured owned detail read without hiding it and localizes saved-copy fallback","invocations":1,"location":null,"numPassingAsserts":9,"retryReasons":[],"startAt":1793776402005,"status":"passed","title":"message Retry replays the captured owned detail read without hiding it and localizes saved-copy fallback"},{"ancestorTitles":["CorrespondenceInboxPage"],"duration":1021,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage hidden or inactive details never resume a saved-copy fallback and hub return preserves inbox context","invocations":1,"location":null,"numPassingAsserts":8,"retryReasons":[],"startAt":1793776402452,"status":"passed","title":"hidden or inactive details never resume a saved-copy fallback and hub return preserves inbox context"},{"ancestorTitles":["CorrespondenceInboxPage"],"duration":301,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage provider status Retry is isolated from saved messages and drafts and live locale changes cause no reads","invocations":1,"location":null,"numPassingAsserts":11,"retryReasons":[],"startAt":1793776403473,"status":"passed","title":"provider status Retry is isolated from saved messages and drafts and live locale changes cause no reads"},{"ancestorTitles":["CorrespondenceInboxPage"],"duration":560,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage confirmed unlink owns pending actions and a failed successful-write refresh recovers with GET only","invocations":1,"location":null,"numPassingAsserts":13,"retryReasons":[],"startAt":1793776403775,"status":"passed","title":"confirmed unlink owns pending actions and a failed successful-write refresh recovers with GET only"},{"ancestorTitles":["CorrespondenceInboxPage"],"duration":810,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage literal all search preserves source and hash and hides another query snapshot while retaining same-query refresh data","invocations":1,"location":null,"numPassingAsserts":29,"retryReasons":[],"startAt":1793776404335,"status":"passed","title":"literal all search preserves source and hash and hides another query snapshot while retaining same-query refresh data"},{"ancestorTitles":["CorrespondenceInboxPage"],"duration":292,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage typing an inbox search keeps one natural Back step to the prior screen and preserves filter context","invocations":1,"location":null,"numPassingAsserts":14,"retryReasons":[],"startAt":1793776405145,"status":"passed","title":"typing an inbox search keeps one natural Back step to the prior screen and preserves filter context"},{"ancestorTitles":["CorrespondenceInboxPage"],"duration":704,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage held draft Save captures one write and locks edits, Send, Discard and every draft replacement entry","invocations":1,"location":null,"numPassingAsserts":15,"retryReasons":[],"startAt":1793776405437,"status":"passed","title":"held draft Save captures one write and locks edits, Send, Discard and every draft replacement entry"},{"ancestorTitles":["CorrespondenceInboxPage"],"duration":1196,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage double Review creates one confirmation, Cancel sends nothing and explicit double Send owns one captured attempt","invocations":1,"location":null,"numPassingAsserts":14,"retryReasons":[],"startAt":1793776406141,"status":"passed","title":"double Review creates one confirmation, Cancel sends nothing and explicit double Send owns one captured attempt"},{"ancestorTitles":["CorrespondenceInboxPage"],"duration":4664,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage recipient, subject and body edits cannot erase any recorded delivery state or rotate its attempt identity","invocations":1,"location":null,"numPassingAsserts":70,"retryReasons":[],"startAt":1793776407337,"status":"passed","title":"recipient, subject and body edits cannot erase any recorded delivery state or rotate its attempt identity"},{"ancestorTitles":["CorrespondenceInboxPage"],"duration":1599,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage generic 409 cannot prepare a new attempt while an authoritative failed result requires one explicit server rotation","invocations":1,"location":null,"numPassingAsserts":14,"retryReasons":[],"startAt":1793776412001,"status":"passed","title":"generic 409 cannot prepare a new attempt while an authoritative failed result requires one explicit server rotation"},{"ancestorTitles":["CorrespondenceInboxPage"],"duration":2129,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage recipient-only and subject-only unsaved changes require replacement and discard confirmation whose Cancel retains the draft","invocations":1,"location":null,"numPassingAsserts":22,"retryReasons":[],"startAt":1793776413600,"status":"passed","title":"recipient-only and subject-only unsaved changes require replacement and discard confirmation whose Cancel retains the draft"},{"ancestorTitles":["CorrespondenceInboxPage"],"duration":757,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage saved-draft initial read and post-save refresh failures recover with GET-only Retry without replaying the successful Save","invocations":1,"location":null,"numPassingAsserts":13,"retryReasons":[],"startAt":1793776415729,"status":"passed","title":"saved-draft initial read and post-save refresh failures recover with GET-only Retry without replaying the successful Save"},{"ancestorTitles":["CorrespondenceInboxPage"],"duration":478,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage a late Save after inbox unmount cannot refresh drafts or attach its result to another page","invocations":1,"location":null,"numPassingAsserts":8,"retryReasons":[],"startAt":1793776416486,"status":"passed","title":"a late Save after inbox unmount cannot refresh drafts or attach its result to another page"},{"ancestorTitles":["CorrespondenceInboxPage"],"duration":662,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage a held managed-draft 409 retains recipient, subject, body and revision with current-language original Save recovery","invocations":1,"location":null,"numPassingAsserts":11,"retryReasons":[],"startAt":1793776416965,"status":"passed","title":"a held managed-draft 409 retains recipient, subject, body and revision with current-language original Save recovery"},{"ancestorTitles":["CorrespondenceInboxPage"],"duration":1510,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage server attempt rotation preserves three unsaved fields and their dirty state while the next send uses the new identity","invocations":1,"location":null,"numPassingAsserts":15,"retryReasons":[],"startAt":1793776417627,"status":"passed","title":"server attempt rotation preserves three unsaved fields and their dirty state while the next send uses the new identity"},{"ancestorTitles":["CorrespondenceInboxPage"],"duration":629,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"CorrespondenceInboxPage a committed Save starts a fresh draft-list read despite an older pending GET and rejects the late old snapshot","invocations":1,"location":null,"numPassingAsserts":11,"retryReasons":[],"startAt":1793776419137,"status":"passed","title":"a committed Save starts a fresh draft-list read despite an older pending GET and rejects the late old snapshot"}],"endTime":1793776419768,"message":"","name":"C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\src\\correspondence-inbox-page.test.tsx","startTime":1793776388178,"status":"passed","summary":""},{"assertionResults":[{"ancestorTitles":[],"duration":9,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"English and Bokmål expose the same UI message keys","invocations":1,"location":null,"numPassingAsserts":1,"retryReasons":[],"startAt":1793776419989,"status":"passed","title":"English and Bokmål expose the same UI message keys"},{"ancestorTitles":[],"duration":4,"failing":false,"failureDetails":[],"failureMessages":[],"fullName":"Bokmål preserves every English substitution variable","invocations":1,"location":null,"numPassingAsserts":1,"retryReasons":[],"startAt":1793776419999,"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":1793776420003,"status":"passed","title":"supported UI catalogues contain no blank messages"}],"endTime":1793776420006,"message":"","name":"C:\\Users\\Connor\\.codex\\worktrees\\direct-roadmap-integration\\Job tracker\\job-tracker-ui\\src\\translation-catalogue.test.ts","startTime":1793776419781,"status":"passed","summary":""}],"wasInterrupted":false} diff --git a/job-tracker-ui/scripts/verify-a3-documents-inbox.mjs b/job-tracker-ui/scripts/verify-a3-documents-inbox.mjs index 9832201f..bb0e3e51 100644 --- a/job-tracker-ui/scripts/verify-a3-documents-inbox.mjs +++ b/job-tracker-ui/scripts/verify-a3-documents-inbox.mjs @@ -8,10 +8,11 @@ import assert from 'node:assert/strict'; const compiled = ts.transpileModule(fs.readFileSync(new URL('../src/i18n/translations.ts', import.meta.url), 'utf8'), { compilerOptions: { module: ts.ModuleKind.ESNext } }).outputText; const { translations } = await import(`data:text/javascript;base64,${Buffer.from(compiled).toString('base64')}`); const visualMatrix = process.env.A3_VISUAL_MATRIX === '1'; -const mailOnly = process.env.A3_MAIL_FLAT === '1'; +const labelOnly = process.env.A3_LABEL_COUNT === '1'; +const mailOnly = process.env.A3_MAIL_FLAT === '1' || labelOnly; const widths = process.env.A3_PILOT ? [320] : visualMatrix ? [320, 360, 390, 430, 768, 1024, 1280, 1440, 1920] : [320, 390, 768, 1440]; const expectedJourneys = widths.length * (process.env.A3_PILOT ? 1 : 4); -const out = path.resolve(import.meta.dirname, mailOnly ? '../../docs/ui-modernisation/visual-correction/evidence/mail-flat' : visualMatrix ? '../../docs/ui-modernisation/visual-correction/evidence/documents-inbox' : '../../docs/ui-modernisation/evidence/documents-inbox/browser'); fs.mkdirSync(out, { recursive: true }); +const out = path.resolve(import.meta.dirname, labelOnly ? '../../docs/ui-modernisation/visual-correction/evidence/mail-labels' : mailOnly ? '../../docs/ui-modernisation/visual-correction/evidence/mail-flat' : visualMatrix ? '../../docs/ui-modernisation/visual-correction/evidence/documents-inbox' : '../../docs/ui-modernisation/evidence/documents-inbox/browser'); fs.mkdirSync(out, { recursive: true }); const report = { screens: [], journeys: [], errors: [], unexpectedWrites: [], mockedWrites: [], unavailableReads: [], blockedExternal: [] }; const browser = await chromium.launch(); try { @@ -66,7 +67,13 @@ try { const download = page.waitForEvent('download'); await docs.getByRole('button', { name: t('documentsDownloadAttachment'), exact: true }).click(); const file = await download; assert.equal(file.suggestedFilename(), document.name); assert.equal(fs.readFileSync(await file.path(), 'utf8'), 'Controlled certificate fixture'); assert.equal(await docs.getByRole('link', { name: t('documentsOpenSource'), exact: true }).getAttribute('href'), document.sourceHref); assert.equal(await docs.locator('time').getAttribute('datetime'), document.recordedAt); await scan('document-filtered', docs.getByText(document.name, { exact: true })); } - await page.goto('http://localhost:4338/correspondence'); const messages = page.getByRole('region', { name: t('emailInboxLinkedMessages'), exact: true }); await messages.getByText(mail.subject, { exact: true }).waitFor(); await scan('inbox'); + await page.goto('http://localhost:4338/correspondence'); const messages = page.getByRole('region', { name: t('emailInboxLinkedMessages'), exact: true }); await messages.getByText(mail.subject, { exact: true }).waitFor(); + if (labelOnly) { + const label = messages.getByText(t('emailInboxLabelCount').replace('{count}', '1'), { exact: true }); await expect(label).toBeVisible(); + await scan('label-count', label); + report.journeys.push({ width, language, mode, singularRecordedLabelCount: 'PASS', documentFilterFocusDownloadSource: 'NOT RUN', inboxDraftSendWorkflow: 'NOT RUN' }); await context.close(); continue; + } + await scan('inbox'); const initialMailReads = mailReads; await page.getByRole('button', { name: t('emailInboxJumpMessages'), exact: true }).press('Enter'); await expect(messages).toBeFocused(); await messages.getByRole('button', { name: t('emailInboxReturnFilters'), exact: true }).click(); await expect(page.getByRole('textbox', { name: t('correspondenceSearch'), exact: true })).toBeFocused(); assert.equal(mailReads, initialMailReads); await messages.getByRole('button', { name: t('emailInboxViewMessage'), exact: true }).click(); const messageBody = messages.getByText('Please choose an interview time. ', { exact: true }); await messageBody.waitFor(); const article = messages.getByRole('article', { name: mail.subject, exact: true }); @@ -84,6 +91,6 @@ try { assert.equal(sends, 1); await expect(compose.getByRole('textbox', { name: t('emailInboxMessage'), exact: true })).toHaveValue(draft.bodyText); await scan('uncertain-send', compose.getByRole('textbox', { name: t('emailInboxMessage'), exact: true })); report.journeys.push({ width, language, mode, documentFilterFocusDownloadSource: mailOnly ? 'NOT RUN' : 'PASS', inboxFocusPlainTextReplySaveExplicitConfirmUncertainNoRetry: 'PASS' }); await context.close(); } - assert.equal(report.journeys.length, expectedJourneys); assert.equal(report.screens.length, report.journeys.length * (mailOnly ? 4 : 6)); assert.equal(report.mockedWrites.length, report.journeys.length * 2); assert.deepEqual(report.errors, []); assert.deepEqual(report.unexpectedWrites, []); assert.ok(report.blockedExternal.every(origin => origin === 'https://fonts.googleapis.com')); + assert.equal(report.journeys.length, expectedJourneys); assert.equal(report.screens.length, report.journeys.length * (labelOnly ? 1 : mailOnly ? 4 : 6)); assert.equal(report.mockedWrites.length, labelOnly ? 0 : report.journeys.length * 2); assert.deepEqual(report.errors, []); assert.deepEqual(report.unexpectedWrites, []); assert.ok(report.blockedExternal.every(origin => origin === 'https://fonts.googleapis.com')); console.log(`PASS:${report.journeys.length} document/mail journeys,${report.screens.length} axe scans/captures,${report.mockedWrites.length} fixture-only save/send requests.`); } finally { fs.writeFileSync(path.join(out, process.env.A3_PILOT ? 'PILOT.json' : 'BROWSER.json'), JSON.stringify({ ...report, limits: ['Synthetic metadata/binary/mail/provider fixtures; not live storage, mailbox, OAuth or delivery.', 'Browser covers draft save, explicit confirmation and controlled uncertain outcome only; other recovery/conflict paths remain targeted tests.', 'No real sending, native device, final integrated security or release acceptance.'] }, null, 2)); await browser.close(); } diff --git a/job-tracker-ui/src/correspondence-inbox-page.test.tsx b/job-tracker-ui/src/correspondence-inbox-page.test.tsx index 6261737e..94d0c311 100644 --- a/job-tracker-ui/src/correspondence-inbox-page.test.tsx +++ b/job-tracker-ui/src/correspondence-inbox-page.test.tsx @@ -118,6 +118,23 @@ function renderPage(initialPath = '/') { } describe('CorrespondenceInboxPage', () => { + test.each(['en', 'nb'] as const)('%s label counts keep singular/plural grammar through a live locale change', async language => { + const original = mockedApi.get.getMockImplementation(); + mockedApi.get.mockImplementation((url: string, config?: any) => url === '/correspondence/page' + ? Promise.resolve({ data: pageData([messageItem({ subject: 'One label', labelCount: 1 }), messageItem({ id: 2, subject: 'Two labels', labelCount: 2 })]) } as any) + : original!(url, config)); + localStorage.setItem('uiLanguage', language); renderPage(); + await screen.findByRole('article', { name: 'One label' }); + const reads = mockedApi.get.mock.calls.length; + const assertLabels = (locale: UiLanguage) => { + expect(messageRow('One label').getByText(locale === 'en' ? '1 label' : '1 etikett', { exact: true })).toBeVisible(); + expect(messageRow('Two labels').getByText(locale === 'en' ? '2 labels' : '2 etiketter', { exact: true })).toBeVisible(); + }; + assertLabels(language); + const other = language === 'en' ? 'nb' : 'en'; await changeLanguage(other); assertLabels(other); + expect(mockedApi.get).toHaveBeenCalledTimes(reads); + expect(mockedApi.post).not.toHaveBeenCalled(); expect(mockedApi.put).not.toHaveBeenCalled(); expect(mockedApi.delete).not.toHaveBeenCalled(); + }); test.each(['en', 'nb'] as const)('%s opened message retains one application action in named context', async language => { localStorage.setItem('uiLanguage', language); renderPage(); await screen.findByText('Interview invite', { exact: true }); diff --git a/job-tracker-ui/src/i18n/translations.ts b/job-tracker-ui/src/i18n/translations.ts index 5da15bc0..3fbd3fc0 100644 --- a/job-tracker-ui/src/i18n/translations.ts +++ b/job-tracker-ui/src/i18n/translations.ts @@ -453,6 +453,7 @@ export const translations = { emailInboxUnknownRole: "Unknown role", emailInboxThreadId: "Thread {id}", emailInboxManualInternal: "Manual/internal", + emailInboxLabelCount: "{count} label", emailInboxLabels: "{count} labels", emailInboxAttachments: "{count} attachments", emailInboxHideMessage: "Hide message", @@ -3669,6 +3670,7 @@ export const translations = { emailInboxUnknownRole: "Ukjent rolle", emailInboxThreadId: "Tråd {id}", emailInboxManualInternal: "Manuell/intern", + emailInboxLabelCount: "{count} etikett", emailInboxLabels: "{count} etiketter", emailInboxAttachments: "{count} vedlegg", emailInboxHideMessage: "Skjul melding", diff --git a/job-tracker-ui/src/views/CorrespondenceInboxPage.tsx b/job-tracker-ui/src/views/CorrespondenceInboxPage.tsx index 07f18687..433d11d9 100644 --- a/job-tracker-ui/src/views/CorrespondenceInboxPage.tsx +++ b/job-tracker-ui/src/views/CorrespondenceInboxPage.tsx @@ -842,7 +842,7 @@ export default function CorrespondenceInboxPage() { {item.direction ? : null} {item.provider ? : null} {item.externalThreadId ? : } - {item.labelCount > 0 ? : null} + {item.labelCount > 0 ? : null} {item.attachmentCount > 0 ? : null} {selectedMessageId !== item.id ? : null}