fix(i18n): localize generated labels without changing saved content

This commit is contained in:
cesnimda
2026-10-20 20:27:42 +02:00
parent d8c0187305
commit ab89b6984c
17 changed files with 1106 additions and 31 deletions
+2
View File
@@ -2,6 +2,8 @@
## Continuation — 20 October 2026
P7-11 shared **d8c01873053e59c896e60911b8e5c14b00693c7f** normally pushed/directly verified18:26:54UTC, retaining all remaining owner WIP. [Generated-label localisation](p7-11/LOCALISATION.md) final boundary:76/4 direct plus7/2 compatibility consumer vectors; native9/9 PASS18:15:24–18:17:05UTC,50scans0violations/355incomplete observations, three actual images inspected. Final hygiene/publication follows; no provider/model/save contract changes.
P7-10 System **d824f59d2b3e9b680bee5952802aa3c0453aee03** normally pushed/directly verified clean13:40:25UTC. CI-only main repair merge **6db62095c6dc6f7e8275447827c62b09f9b2d2e3** pushed/directly verified clean13:40:41UTC. P7-10 is implemented CONDITIONAL GO and published. P7-11 [shared integration](p7-11/SHARED.md) has80/11 affected/consumer frontend, current7version/10command vectors and native5modal+1Strategy PASS; final hygiene/publication follows.59paired label additions are the shared foundation for subsequent responsible-owner commits. Whole candidate frontend898/101 and backend1057 pass; final ARIA continuations receive focused/native proof and fresh P7-12 regression.
Independent fixture/focus repair **bba52af4b8724f1a5c74013ce1476a20ca3972c2** normally pushed/directly verified on development18:05:08UTC, with all P7-11 WIP retained. Main's equivalent **3e1057d** plus Qwen adapter cherry-pick **f7dc22e5e7efe1fd1d64f6ace2c04cf2f95bf558** are pushed/directly verified18:05UTC. Release833 tests; no verified deployment/model activation or production GO claim. Run832's16failures are explained in its release report; one flaky Rules case remains debt.
+2
View File
@@ -1,5 +1,7 @@
# Phase 7 — Initial preservation matrix
P7-11 [localisation](p7-11/LOCALISATION.md) adds bounded PRES-C08/C10/W04/M04/E09 evidence: original template selection objects/IDs/admission and English/custom metadata retained; known Bokmål generated labels and history enums translate without modifying user text. Resumed-draft fallback derives current language without rereads or dirty-field loss. Native9cases/50scans0violations retain355qualified incomplete observations. All57 groups remain56 PARTIAL/C11 DEFERRED and33 routes; no provider/model/revision/renderer or production acceptance claim.
P7-11 [shared integration](p7-11/SHARED.md) adds bounded PRES-E07/E08/W04/W09 evidence: valid badge/command names, retained item-specific checklist/interview actions, contextual form headings and Strategy GET-only error recovery with exact intentional-empty contract. Existing AI/Pro and operations semantics remain. All57 groups stay56 PARTIAL/C11 DEFERRED and33 routes; accessibility incompletes/manual/provider/output/production gates remain qualified. No Phase8 work.
P7-10 [System](p7-10/SYSTEM.md) adds bounded PRES-A03/E09 proof: four independent diagnostic reads, explicit failed-read availability, denied-refresh disposal, GET-only retry, unchanged SMTP password/clear payloads, retained newer drafts and synchronized/session-guarded operations.20unit/46backend/3native cases pass; administrator browser writes are controlled fixtures, not operational acceptance. All57 groups remain56 PARTIAL/C11 DEFERRED and33 routes. No schema/renderer/provider/AI-safety change. Human/device/provider/output/release qualifications remain.
@@ -0,0 +1,15 @@
# Generated-label localisation
**Implemented CONDITIONAL GO; final hygiene/publication pending.** Existing gallery, Reminders, application materials and correspondence owners remain.
Known built-in template IDs map to Bokmål names/categories/descriptions; English retains server metadata and unknown templates remain verbatim. Selection still passes the original theme object and preserves IDs, availability, Pro admission and renderer settings. A full frontend consumer caught an initial change to English descriptions; limiting the mapping to Bokmål restored the existing English contract, with7affected gallery/document-control cases passing.
Only recognized generated reminder strings and anchored numeric reasons translate; custom reasons remain verbatim. Tailoring headings/details map known kinds, preserving evidence/items and unknown kinds. Known cover-letter source/action enums translate; unknown values and authored prose remain unchanged. Existing CV history receives the corresponding display mapping in its own error/navigation boundary. No saved data or generated content is rewritten.
Resumed draft context stores absent metadata as absent and derives its fallback from the current locale at render time. Real company/job/source/subject and dirty content remain unchanged. Singular item/attachment labels use paired translations. Locale changes do not trigger a draft or setup reread.
Initial direct result70passed/6failed; final direct result **76/76 across four suites PASS**, app TypeScript/targeted lint PASS. Existing modified assertions and new tests are reconciled in the final inventory, rather than counting every initial failure as a new case. Browser consumers initially still expected raw English generated reminders in Bokmål; those finite fixtures now assert their translated generated labels while retaining exact custom-content, no-refetch, route, focus and mutation checks. Final native/visual/static/publication evidence follows.
Final native **9/9 PASS**, zero skips/flaky/retries,18:15:24–18:17:05UTC;52images/50all-enabled axe scans,0violations/355incomplete-node observations. [Portable results](evidence/localization-results.json) retain raw result/source/image identities. Three images were actually inspected:320px Bokmål gallery,1440px Bokmål generated reasons and1024px English dark retained draft. Others remain automated evidence. Incompletes include336contrast observations (short badges, backgrounds obscured by fixed chrome/focus/dialogs) and19hidden-focus vectors in MUI background/menu portal transitions; these retain manual/settled-state qualification, not a reader pass.
No provider/model operation, canonical Career data, revision/save contract, frozen package or renderer geometry changes. Human Bokmål/editorial review and physical/assistive acceptance are not inferred from automation. Existing deferred functionality and production qualifications remain. Static checks and normal push follow.
+1 -1
View File
@@ -20,7 +20,7 @@ These findings were carried from implemented checkpoints and verified in source,
## Execution boundaries
1. [Shared accessibility and forms](SHARED.md), including the paired translation foundation used by the following owners.
2. Generated-label localisation follows separately, preserving all stored metadata and user text.
2. [Generated-label localisation](LOCALISATION.md), preserving all stored metadata and user text; final hygiene/publication follows.
3. Documents and error navigation follows separately, including CV history display in its existing owner.
The independently authorized release repair is separate: development `bba52af4b8724f1a5c74013ce1476a20ca3972c2` was pushed/directly verified18:05:08UTC. Main contains equivalent fixture/focus repair `3e1057d` and Qwen compatibility `f7dc22e`, normally pushed and directly verified20October18:05UTC. Run833 is testing; deployment/model activation are unverified. [Release evidence](../../../infrastructure/2026-10-20-browser-fixture-and-focus-contracts.md) retains run832's314passing/16failed/oneflaky outcome and the two demonstrated causes. No failed assertion was hidden or timeout relaxed.
+2
View File
@@ -1,5 +1,7 @@
# Shared accessibility and forms
Publication: **d8c01873053e59c896e60911b8e5c14b00693c7f**, normally pushed/directly verified20October18:26:54UTC. The following localisation/error WIP was preserved. Earlier pending-publication observations are superseded by this receipt.
Final modal continuation **5/5 PASS**,18:23:09–18:24:16UTC; combined with the separate Strategy case,26all-enabled scans have0violations/176incomplete observations:172contrast and4hidden-focus MUI menu-transition contexts. The previous dangling-title and roleless Undo/Redo observations are absent in this fresh vector. No human/device acceptance follows.
**Implemented CONDITIONAL GO; final hygiene/publication pending.** Existing shell/form owners remain; no navigation or mutation protocol replacement.
@@ -0,0 +1,790 @@
{
"observedUtc": "2026-10-20T18:17:46.580335+00:00",
"sourceScope": "Post-run source identities; no claim of pre-run capture or that later catalogue/comment changes were in earlier exports",
"sources": {
"src/components/CvTemplateGallery.tsx": "51d082fe381a1ad7dcd17ddc374c7252e7ed1b5ac807174da8e5ac6a65acd239",
"src/components/RemindersView.tsx": "1eedbc443761001e86be1f322691ab4058553033e764b0c6cf9c93ce4fab05c0",
"src/components/ApplicationAssets.tsx": "453476dca53b2bbb898d082fdc6088426924774e21fb4114e118968d26573391",
"src/views/CorrespondenceInboxPage.tsx": "2d3a44b6e7ef665503a58506413fc9b67b6da797d31db97ef88d6c0d4a196a1e",
"src/i18n/translations.ts": "15cc09b7dfcea607b6a597d1379aaad5d3e1c01adcc518846a9047b398ba5458"
},
"native": [
{
"name": "localization-residual",
"stats": {
"startTime": "2026-10-20T18:15:24.291Z",
"duration": 100642.52,
"expected": 9,
"skipped": 0,
"unexpected": 0,
"flaky": 0
},
"path": ".artifacts/phase7/current/ui/localization-residual.json",
"sha256": "3889f59003202e7d3400afcf7e79685d7234aebb6af8c8fd8282e5f5efea22fe",
"command": {
"name": "localization-residual",
"command": [
"rtk",
"proxy",
"node",
"node_modules/@playwright/test/cli.js",
"test",
"e2e/phase7-cv-library.spec.ts",
"e2e/phase7-reminders.spec.ts",
"e2e/phase7-correspondence-read.spec.ts",
"e2e/phase7-correspondence-compose.spec.ts",
"--grep",
"locale-retained create|free gallery|finite library|live EN/Bokmal|eight finite bilingual|strict actual language204|strict real UI-language204",
"--retries=0",
"--reporter=list,json",
"--output=E:\\Storage\\Projects\\Active\\Job tracker\\.artifacts\\phase7\\current\\ui\\localization-residual-results"
],
"startedUtc": "2026-10-20T18:15:23.736513+00:00",
"finishedUtc": "2026-10-20T18:17:05.008996+00:00",
"exitCode": 0
}
}
],
"images": [
{
"name": "p7-reminders-nb-light-1440-live-locale-raw-reasons",
"path": ".artifacts/phase7/p7-11/localization-visuals/p7-reminders-nb-light-1440-live-locale-raw-reasons-5174c4aa.png",
"sha256": "5174c4aabee046139adc1b7d5bd5ce104b2df26f741682b78688aa2eab0ba252"
},
{
"name": "p7-reminders-en-light-320-long-four-group-rows",
"path": ".artifacts/phase7/p7-11/localization-visuals/p7-reminders-en-light-320-long-four-group-rows-1a3d139f.png",
"sha256": "1a3d139f441eb6545000cf7ae8d2bda57fffd9e5adeffe3e777e71f802378538"
},
{
"name": "p7-reminders-nb-dark-360-long-four-group-rows",
"path": ".artifacts/phase7/p7-11/localization-visuals/p7-reminders-nb-dark-360-long-four-group-rows-eb339e58.png",
"sha256": "eb339e581252bec77187a4ff9217fe9f6a16932b799bdb8ce7c54f3149eea624"
},
{
"name": "p7-reminders-en-dark-390-long-four-group-rows",
"path": ".artifacts/phase7/p7-11/localization-visuals/p7-reminders-en-dark-390-long-four-group-rows-1cb5b4c0.png",
"sha256": "1cb5b4c0304cb64f81b68d2db04710b6e9bd7156e1b6d0dd10967251cf2efb16"
},
{
"name": "p7-reminders-nb-light-768-long-four-group-rows",
"path": ".artifacts/phase7/p7-11/localization-visuals/p7-reminders-nb-light-768-long-four-group-rows-747d5625.png",
"sha256": "747d5625d5b2a85c1434fb878778c5d3357a0378c4b7ded85657a4809472e61d"
},
{
"name": "p7-reminders-en-dark-1024-long-four-group-rows",
"path": ".artifacts/phase7/p7-11/localization-visuals/p7-reminders-en-dark-1024-long-four-group-rows-d8b177b4.png",
"sha256": "d8b177b40404f43ae625f0fc524051c04148747340dccc38a981fff08b7252f5"
},
{
"name": "p7-reminders-nb-light-1280-long-four-group-rows",
"path": ".artifacts/phase7/p7-11/localization-visuals/p7-reminders-nb-light-1280-long-four-group-rows-16e0872b.png",
"sha256": "16e0872b2eb5d2367ffd5c12f313156179f2078a82ecbd307d94b5820dcde0a9"
},
{
"name": "p7-reminders-en-light-1440-long-four-group-rows",
"path": ".artifacts/phase7/p7-11/localization-visuals/p7-reminders-en-light-1440-long-four-group-rows-b45dbb83.png",
"sha256": "b45dbb831eaa4515dd6c946a3aae2241e18a55ee80981b81504a9627a5465c10"
},
{
"name": "p7-reminders-nb-dark-1920-long-four-group-rows",
"path": ".artifacts/phase7/p7-11/localization-visuals/p7-reminders-nb-dark-1920-long-four-group-rows-01f44aea.png",
"sha256": "01f44aea9eaac8614aa84223ff324451f6c1cdf26645de4e0f987fb39c51bebc"
},
{
"name": "p7-cv-library-nb-light-360-held-create",
"path": ".artifacts/phase7/p7-11/localization-visuals/p7-cv-library-nb-light-360-held-create-90323733.png",
"sha256": "90323733a3d443f6b874af3b4d94229aebcb9acbbc2c58683aadc64632bb4207"
},
{
"name": "p7-cv-library-nb-light-320-library",
"path": ".artifacts/phase7/p7-11/localization-visuals/p7-cv-library-nb-light-320-library-073cd027.png",
"sha256": "073cd027929f7042b476bfd90fc17751ed963a6fc7892f8dc60deb972efc5342"
},
{
"name": "p7-cv-library-nb-light-320-gallery",
"path": ".artifacts/phase7/p7-11/localization-visuals/p7-cv-library-nb-light-320-gallery-1010c8fe.png",
"sha256": "1010c8feb945f67adc27f1ab4b8372caafa50eda7a2ee5ad8aea877ec41050d2"
},
{
"name": "p7-cv-library-en-dark-360-library",
"path": ".artifacts/phase7/p7-11/localization-visuals/p7-cv-library-en-dark-360-library-57de942b.png",
"sha256": "57de942bd6fbe73fe1fd7bd1d5e28f848bae9c03ec47d8155dddd05d48e07ac9"
},
{
"name": "p7-cv-library-en-dark-360-gallery",
"path": ".artifacts/phase7/p7-11/localization-visuals/p7-cv-library-en-dark-360-gallery-1dce647e.png",
"sha256": "1dce647e813fcbba5608d07ec4f0b8341d479fa2b746642e49018577c00963d5"
},
{
"name": "p7-cv-library-nb-dark-390-library",
"path": ".artifacts/phase7/p7-11/localization-visuals/p7-cv-library-nb-dark-390-library-b4b38cb8.png",
"sha256": "b4b38cb86adfaed3f9be7b4f50d9ff39e4e96161b10d9d48c66a49affa52b34e"
},
{
"name": "p7-cv-library-nb-dark-390-gallery",
"path": ".artifacts/phase7/p7-11/localization-visuals/p7-cv-library-nb-dark-390-gallery-4687b2de.png",
"sha256": "4687b2de2c74bc28cc6c1258f18dd13db683a52c3d2b15c70bb3b078dd92525f"
},
{
"name": "p7-cv-library-en-light-768-library",
"path": ".artifacts/phase7/p7-11/localization-visuals/p7-cv-library-en-light-768-library-8a0b9fef.png",
"sha256": "8a0b9fefc5ce1900cc0cc900e6a3210909304122f19edb33b62dc586539848cc"
},
{
"name": "p7-cv-library-en-light-768-gallery",
"path": ".artifacts/phase7/p7-11/localization-visuals/p7-cv-library-en-light-768-gallery-d5f1fef5.png",
"sha256": "d5f1fef5e1bb2c233a76b2ba23b563f516c5d46dd1be69acec834f7edbfaf9ab"
},
{
"name": "p7-cv-library-nb-light-1024-library",
"path": ".artifacts/phase7/p7-11/localization-visuals/p7-cv-library-nb-light-1024-library-f4cb6099.png",
"sha256": "f4cb6099cf253e2a8100ffc75ea00f3f81a97184fbe6f69004e705acf89fe2d8"
},
{
"name": "p7-cv-library-nb-light-1024-gallery",
"path": ".artifacts/phase7/p7-11/localization-visuals/p7-cv-library-nb-light-1024-gallery-268fd1cf.png",
"sha256": "268fd1cfb7669c64252ec824ae00e9f9aae133b754f0075dfa96f92c87ef2880"
},
{
"name": "p7-cv-library-en-dark-1280-library",
"path": ".artifacts/phase7/p7-11/localization-visuals/p7-cv-library-en-dark-1280-library-4e8f38a4.png",
"sha256": "4e8f38a468c454e053d4ce2fa9bef76dac1daa33b75be6f1eebf450220d9f3dd"
},
{
"name": "p7-cv-library-en-dark-1280-gallery",
"path": ".artifacts/phase7/p7-11/localization-visuals/p7-cv-library-en-dark-1280-gallery-cd1a90bd.png",
"sha256": "cd1a90bd628f6c8946d95f89b52a48bd098ccc0d1e0a121eda13b637321e28b7"
},
{
"name": "p7-cv-library-nb-dark-1440-library",
"path": ".artifacts/phase7/p7-11/localization-visuals/p7-cv-library-nb-dark-1440-library-e83ac07e.png",
"sha256": "e83ac07eeedbbf82fa8c200f700ab96debb01a7a0fee53e67d4a71300895678b"
},
{
"name": "p7-cv-library-nb-dark-1440-gallery",
"path": ".artifacts/phase7/p7-11/localization-visuals/p7-cv-library-nb-dark-1440-gallery-36708ab6.png",
"sha256": "36708ab6d315803382962458c27896e1ad00f4e480e88f667d30b3db10f7cd6d"
},
{
"name": "p7-cv-library-en-light-1920-library",
"path": ".artifacts/phase7/p7-11/localization-visuals/p7-cv-library-en-light-1920-library-486e60b3.png",
"sha256": "486e60b38066f8113684087a240bf3244d21a69af631654257b0712ddfa1840b"
},
{
"name": "p7-cv-library-en-light-1920-gallery",
"path": ".artifacts/phase7/p7-11/localization-visuals/p7-cv-library-en-light-1920-gallery-1cf64a79.png",
"sha256": "1cf64a79bdbe4e16fa2073317f1dc675573f4d7fcd4725a96cfe4c149b6b1a0c"
},
{
"name": "p706-read-nb-light-1440-strict-locale-preserved-saved-detail",
"path": ".artifacts/phase7/p7-11/localization-visuals/p706-read-nb-light-1440-strict-locale-preserved-saved-detail-4a449716.png",
"sha256": "4a4497167b8d3dd221623f16bf1078502b0f2022a24d3b195dab24a874c432cb"
},
{
"name": "p706-read-en-light-1440-strict-locale-dirty-review-notes",
"path": ".artifacts/phase7/p7-11/localization-visuals/p706-read-en-light-1440-strict-locale-dirty-review-notes-e3fd5780.png",
"sha256": "e3fd57804f529bfaaa4a046654ac528504338435e637b90c36b90af4c5817909"
},
{
"name": "p706-read-en-light-320-long-inbox",
"path": ".artifacts/phase7/p7-11/localization-visuals/p706-read-en-light-320-long-inbox-a94adc4b.png",
"sha256": "a94adc4bb4a0efadf34063739776212c279ee1f837c7888f8fe8e8984e750034"
},
{
"name": "p706-read-nb-dark-360-long-review",
"path": ".artifacts/phase7/p7-11/localization-visuals/p706-read-nb-dark-360-long-review-887edc93.png",
"sha256": "887edc93f62989127fc027a84095681f26ea1bf96f74c424b9b2b89a35e4548b"
},
{
"name": "p706-read-en-dark-390-long-detail",
"path": ".artifacts/phase7/p7-11/localization-visuals/p706-read-en-dark-390-long-detail-f715ae3c.png",
"sha256": "f715ae3cf273100801d88ec57cfff66b63f23be0f938c8109fce1285d0e05ab4"
},
{
"name": "p706-read-nb-light-768-long-inbox",
"path": ".artifacts/phase7/p7-11/localization-visuals/p706-read-nb-light-768-long-inbox-c08361be.png",
"sha256": "c08361be8aeefdacf43ef677c02b5a47aad5b1e120e61ed398fadd9bb23855dd"
},
{
"name": "p706-read-en-dark-1024-long-review",
"path": ".artifacts/phase7/p7-11/localization-visuals/p706-read-en-dark-1024-long-review-9853b2fe.png",
"sha256": "9853b2fe95f84a910b89864d2b15b9a277838cbf5dba0431479153211d784b88"
},
{
"name": "p706-read-nb-light-1280-long-detail",
"path": ".artifacts/phase7/p7-11/localization-visuals/p706-read-nb-light-1280-long-detail-7100a3d0.png",
"sha256": "7100a3d05e42f03e36033207f2ca40ef0856f0d764d03be89d44e6406ce57730"
},
{
"name": "p706-read-en-light-1440-long-review",
"path": ".artifacts/phase7/p7-11/localization-visuals/p706-read-en-light-1440-long-review-c3e53f38.png",
"sha256": "c3e53f3899a814ab1c7829aedc9ed269bc4a04481de64b1e05d543fb41505f7f"
},
{
"name": "p706-read-nb-dark-1920-long-inbox",
"path": ".artifacts/phase7/p7-11/localization-visuals/p706-read-nb-dark-1920-long-inbox-c9aa052b.png",
"sha256": "c9aa052bcd0f02c4ac34ec079c99787ed53b8f30fa1bd18c525882f666f1dfa9"
},
{
"name": "p706-compose-nb-light-1440-strict-language-dirty-draft",
"path": ".artifacts/phase7/p7-11/localization-visuals/p706-compose-nb-light-1440-strict-language-dirty-draft-f7f74362.png",
"sha256": "f7f74362d765fb790ed0944ecc7616b83ae602b4866c9c34015478b22de22ecb"
},
{
"name": "p706-compose-en-light-320-long-draft-keyboard",
"path": ".artifacts/phase7/p7-11/localization-visuals/p706-compose-en-light-320-long-draft-keyboard-263be757.png",
"sha256": "263be757bdefe0da8649918b6adb1d049900735539ba4516e92fc99f39d7ec43"
},
{
"name": "p706-compose-en-light-320-owned-setup-keyboard",
"path": ".artifacts/phase7/p7-11/localization-visuals/p706-compose-en-light-320-owned-setup-keyboard-66af59e4.png",
"sha256": "66af59e4b1a7a9178c61f2e8ca761b8bb01348ac6e3b43c6be9beb4452b664b9"
},
{
"name": "p706-compose-nb-dark-360-long-draft-keyboard",
"path": ".artifacts/phase7/p7-11/localization-visuals/p706-compose-nb-dark-360-long-draft-keyboard-bfe733b5.png",
"sha256": "bfe733b572db285b85b8a8bca041f5dba4ac0c20c5b42224a73629f4e3d1dd1a"
},
{
"name": "p706-compose-en-dark-390-long-draft-keyboard",
"path": ".artifacts/phase7/p7-11/localization-visuals/p706-compose-en-dark-390-long-draft-keyboard-b4c1f542.png",
"sha256": "b4c1f5421c30eff3c94340977475173c3ffd560891eac640808fd8e0ee7d41a2"
},
{
"name": "p706-compose-en-dark-390-send-review-keyboard",
"path": ".artifacts/phase7/p7-11/localization-visuals/p706-compose-en-dark-390-send-review-keyboard-eb37211b.png",
"sha256": "eb37211be24aa9fdef32470824f508b8e4f594f19b5b2a8150236662edeaddc3"
},
{
"name": "p706-compose-en-390-focused-viewport",
"path": ".artifacts/phase7/p7-11/localization-visuals/p706-compose-en-390-focused-viewport-b93c4213.png",
"sha256": "b93c42133c6d18f195e5706d4c4163aac1c1ec0e9569f5c0997a9113497b77a5"
},
{
"name": "p706-compose-nb-light-768-long-draft-keyboard",
"path": ".artifacts/phase7/p7-11/localization-visuals/p706-compose-nb-light-768-long-draft-keyboard-67d78961.png",
"sha256": "67d78961f6211ef171a3df769fe5894232367fa76105b287754160de7f734d7c"
},
{
"name": "p706-compose-nb-light-768-owned-setup-keyboard",
"path": ".artifacts/phase7/p7-11/localization-visuals/p706-compose-nb-light-768-owned-setup-keyboard-816c6e7a.png",
"sha256": "816c6e7a09c523a2c9c52733fd003f1b7d1425a34e9f15ae16405499ae23b82e"
},
{
"name": "p706-compose-en-dark-1024-long-draft-keyboard",
"path": ".artifacts/phase7/p7-11/localization-visuals/p706-compose-en-dark-1024-long-draft-keyboard-2fe4e29c.png",
"sha256": "2fe4e29c71edadcb93829cb35f94041f48fa660c59ac752ba9783e05a4f2ddb0"
},
{
"name": "p706-compose-nb-light-1280-long-draft-keyboard",
"path": ".artifacts/phase7/p7-11/localization-visuals/p706-compose-nb-light-1280-long-draft-keyboard-318dca1a.png",
"sha256": "318dca1adf2201b2a5305b8535b2f9faf08622f4ed65eeb144ed3584785c94fd"
},
{
"name": "p706-compose-nb-light-1280-send-review-keyboard",
"path": ".artifacts/phase7/p7-11/localization-visuals/p706-compose-nb-light-1280-send-review-keyboard-c81f7b6a.png",
"sha256": "c81f7b6ad2bb388def37ae69eb2d8d866d47ab60100655a8993f5fa59ed6dea0"
},
{
"name": "p706-compose-nb-1280-focused-viewport",
"path": ".artifacts/phase7/p7-11/localization-visuals/p706-compose-nb-1280-focused-viewport-287c9542.png",
"sha256": "287c95427ee3ae4aa43e69a06c6215f57ed35da72c2efcb1e3c05e64fdfd955d"
},
{
"name": "p706-compose-en-light-1440-long-draft-keyboard",
"path": ".artifacts/phase7/p7-11/localization-visuals/p706-compose-en-light-1440-long-draft-keyboard-c3502140.png",
"sha256": "c35021406cb9c69b55edbb7ef923fc784c11e72ebbd6cd4d7cc7a3bcbe39a496"
},
{
"name": "p706-compose-en-light-1440-owned-setup-keyboard",
"path": ".artifacts/phase7/p7-11/localization-visuals/p706-compose-en-light-1440-owned-setup-keyboard-88330187.png",
"sha256": "88330187db1420131b05e672bbb6580055b8a03a234122028ddab2ece8906ddc"
},
{
"name": "p706-compose-nb-dark-1920-long-draft-keyboard",
"path": ".artifacts/phase7/p7-11/localization-visuals/p706-compose-nb-dark-1920-long-draft-keyboard-80054d2f.png",
"sha256": "80054d2fc5460c82bdab8453aff70307b6eed77c62b733d0be4b0e4062b2c35b"
}
],
"scans": [
{
"path": ".artifacts/phase7/p7-11/localization-visuals/p7-reminders-nb-light-1440-live-locale-raw-reasons-audit-51fb774c.json",
"sha256": "51fb774c6b8b877267d3e5fb4527854e39cd24464ad5b3119dd288aa7fb006f8",
"violations": 0,
"incompleteNodes": 2,
"incompleteRules": [
"color-contrast"
]
},
{
"path": ".artifacts/phase7/p7-11/localization-visuals/p7-reminders-en-light-320-long-four-group-rows-audit-0f06e3b3.json",
"sha256": "0f06e3b3cfd38456b25256607a990cd26840468c3d7896fcfc9a9870103f4806",
"violations": 0,
"incompleteNodes": 12,
"incompleteRules": [
"color-contrast"
]
},
{
"path": ".artifacts/phase7/p7-11/localization-visuals/p7-reminders-nb-dark-360-long-four-group-rows-audit-2a3072cb.json",
"sha256": "2a3072cb2edd1a06bde4dcd7253a49a5c91538356e98212b1485da8640bdb297",
"violations": 0,
"incompleteNodes": 12,
"incompleteRules": [
"color-contrast"
]
},
{
"path": ".artifacts/phase7/p7-11/localization-visuals/p7-reminders-en-dark-390-long-four-group-rows-audit-cd3baa00.json",
"sha256": "cd3baa00e21cd79db255f2648b9a4a1fb1f88d827d374a08ab1c9811c3ec699e",
"violations": 0,
"incompleteNodes": 12,
"incompleteRules": [
"color-contrast"
]
},
{
"path": ".artifacts/phase7/p7-11/localization-visuals/p7-reminders-nb-light-768-long-four-group-rows-audit-bb464ca5.json",
"sha256": "bb464ca5bdb9ade0f8a6d5dde0bb9cb47b319a0e897e501cdd41992b1d1568ea",
"violations": 0,
"incompleteNodes": 12,
"incompleteRules": [
"color-contrast"
]
},
{
"path": ".artifacts/phase7/p7-11/localization-visuals/p7-reminders-en-dark-1024-long-four-group-rows-audit-0efb5577.json",
"sha256": "0efb5577526eb9225a14ef226cc65cd766e50586cb4da12de9bd614073174bc2",
"violations": 0,
"incompleteNodes": 12,
"incompleteRules": [
"color-contrast"
]
},
{
"path": ".artifacts/phase7/p7-11/localization-visuals/p7-reminders-nb-light-1280-long-four-group-rows-audit-645dc2ed.json",
"sha256": "645dc2ed8ecf78ad072abd475800851bcb3099d1d78ece23d314b65321d9c45f",
"violations": 0,
"incompleteNodes": 12,
"incompleteRules": [
"color-contrast"
]
},
{
"path": ".artifacts/phase7/p7-11/localization-visuals/p7-reminders-en-light-1440-long-four-group-rows-audit-7ea9f77c.json",
"sha256": "7ea9f77cfe5a415c99740a985593f43b67d2abe4d04048fbc28c5083d1d13f1f",
"violations": 0,
"incompleteNodes": 12,
"incompleteRules": [
"color-contrast"
]
},
{
"path": ".artifacts/phase7/p7-11/localization-visuals/p7-reminders-nb-dark-1920-long-four-group-rows-audit-cc9f2c61.json",
"sha256": "cc9f2c61532fc140226fab8fc31a19c890092c6a0b92a4370e3bae4fffd88468",
"violations": 0,
"incompleteNodes": 12,
"incompleteRules": [
"color-contrast"
]
},
{
"path": ".artifacts/phase7/p7-11/localization-visuals/p7-cv-library-nb-light-360-held-create-audit-3a04077a.json",
"sha256": "3a04077a8d10f9c7138434dcc90ddb5a73c5fa130a32d94bdcd62ffb84def43d",
"violations": 0,
"incompleteNodes": 4,
"incompleteRules": [
"aria-hidden-focus",
"color-contrast"
]
},
{
"path": ".artifacts/phase7/p7-11/localization-visuals/p7-cv-library-nb-light-320-library-audit-9489d693.json",
"sha256": "9489d693d8c321ac6686fc0cfbf4c83b0317843c3bf3d14e7424f129b3df03bf",
"violations": 0,
"incompleteNodes": 2,
"incompleteRules": [
"aria-hidden-focus",
"color-contrast"
]
},
{
"path": ".artifacts/phase7/p7-11/localization-visuals/p7-cv-library-nb-light-320-gallery-audit-d8a29224.json",
"sha256": "d8a292243e7169b6c1bad338cd7a104dbcc9868aa44935a7598c8218cbe67bbd",
"violations": 0,
"incompleteNodes": 6,
"incompleteRules": [
"aria-hidden-focus",
"color-contrast"
]
},
{
"path": ".artifacts/phase7/p7-11/localization-visuals/p7-cv-library-en-dark-360-library-audit-a896a26f.json",
"sha256": "a896a26f8387c60e5efe86bfe800f29e47a25cf401d7ce597c475b87c07060cf",
"violations": 0,
"incompleteNodes": 2,
"incompleteRules": [
"aria-hidden-focus",
"color-contrast"
]
},
{
"path": ".artifacts/phase7/p7-11/localization-visuals/p7-cv-library-en-dark-360-gallery-audit-80318eb4.json",
"sha256": "80318eb47893a506554599047b01178860eb0d16ed37c20b8facb5721d0fd895",
"violations": 0,
"incompleteNodes": 5,
"incompleteRules": [
"aria-hidden-focus",
"color-contrast"
]
},
{
"path": ".artifacts/phase7/p7-11/localization-visuals/p7-cv-library-nb-dark-390-library-audit-2d7964d9.json",
"sha256": "2d7964d97922d1c0ad7dbce2e62886fce9aa4c6900e2f2e137cb88e2f2d0d0b7",
"violations": 0,
"incompleteNodes": 2,
"incompleteRules": [
"aria-hidden-focus",
"color-contrast"
]
},
{
"path": ".artifacts/phase7/p7-11/localization-visuals/p7-cv-library-nb-dark-390-gallery-audit-03b683be.json",
"sha256": "03b683be0b7490575f418dddb4bd0a3193660187f911d6b2bc0d3af253bf160b",
"violations": 0,
"incompleteNodes": 6,
"incompleteRules": [
"aria-hidden-focus",
"color-contrast"
]
},
{
"path": ".artifacts/phase7/p7-11/localization-visuals/p7-cv-library-en-light-768-library-audit-d5897bed.json",
"sha256": "d5897bed68a7f17ac01da4be0e79a8edc079fce8ffe8b03213e435b744689e38",
"violations": 0,
"incompleteNodes": 2,
"incompleteRules": [
"aria-hidden-focus",
"color-contrast"
]
},
{
"path": ".artifacts/phase7/p7-11/localization-visuals/p7-cv-library-en-light-768-gallery-audit-32f3e76b.json",
"sha256": "32f3e76b41903b307801ea2c04b21ecdcd52ab510851bc51c56cebd26f5f7695",
"violations": 0,
"incompleteNodes": 5,
"incompleteRules": [
"aria-hidden-focus",
"color-contrast"
]
},
{
"path": ".artifacts/phase7/p7-11/localization-visuals/p7-cv-library-nb-light-1024-library-audit-bdb0cf0e.json",
"sha256": "bdb0cf0ee01924842773abbc7392e403caf3a6ad842ea0bb4ef42aca759e90a5",
"violations": 0,
"incompleteNodes": 2,
"incompleteRules": [
"aria-hidden-focus",
"color-contrast"
]
},
{
"path": ".artifacts/phase7/p7-11/localization-visuals/p7-cv-library-nb-light-1024-gallery-audit-e882952a.json",
"sha256": "e882952a17921011c6cd15e45bf9452f8b792bbf2ddd442fb5004017905af669",
"violations": 0,
"incompleteNodes": 5,
"incompleteRules": [
"aria-hidden-focus",
"color-contrast"
]
},
{
"path": ".artifacts/phase7/p7-11/localization-visuals/p7-cv-library-en-dark-1280-library-audit-39c8726a.json",
"sha256": "39c8726ab2e1892e15670798da2368893285d9058be2bf59137f6f2b405c1302",
"violations": 0,
"incompleteNodes": 2,
"incompleteRules": [
"aria-hidden-focus",
"color-contrast"
]
},
{
"path": ".artifacts/phase7/p7-11/localization-visuals/p7-cv-library-en-dark-1280-gallery-audit-39f0db39.json",
"sha256": "39f0db39a11376f11076024a2d5c3ed40ec6df65aca1f3ad4bfb5bee4c262144",
"violations": 0,
"incompleteNodes": 5,
"incompleteRules": [
"aria-hidden-focus",
"color-contrast"
]
},
{
"path": ".artifacts/phase7/p7-11/localization-visuals/p7-cv-library-nb-dark-1440-library-audit-401c6c4d.json",
"sha256": "401c6c4d87458407bcd8ac0ec641d3d0acde973f3f43e12406c8c050ffb941ce",
"violations": 0,
"incompleteNodes": 2,
"incompleteRules": [
"aria-hidden-focus",
"color-contrast"
]
},
{
"path": ".artifacts/phase7/p7-11/localization-visuals/p7-cv-library-nb-dark-1440-gallery-audit-203d78e8.json",
"sha256": "203d78e849531012f7c88a182b2ef4b3f9ce77011b72925317769873522e8634",
"violations": 0,
"incompleteNodes": 5,
"incompleteRules": [
"aria-hidden-focus",
"color-contrast"
]
},
{
"path": ".artifacts/phase7/p7-11/localization-visuals/p7-cv-library-en-light-1920-library-audit-269f608c.json",
"sha256": "269f608c0cd041f5f45624677c5dae264c089f8e3385249487bcf7ec24794825",
"violations": 0,
"incompleteNodes": 2,
"incompleteRules": [
"aria-hidden-focus",
"color-contrast"
]
},
{
"path": ".artifacts/phase7/p7-11/localization-visuals/p7-cv-library-en-light-1920-gallery-audit-031be6ed.json",
"sha256": "031be6ed8c957cdc43f2ec53f08aad3d6c866f8456f11e0e67ddbc01fe97ae59",
"violations": 0,
"incompleteNodes": 5,
"incompleteRules": [
"aria-hidden-focus",
"color-contrast"
]
},
{
"path": ".artifacts/phase7/p7-11/localization-visuals/p706-read-nb-light-1440-strict-locale-preserved-saved-detail-audit-efb0ab77.json",
"sha256": "efb0ab77848f8692faf0d0975d7ee5a4f7bc8f2b0fad3c901e9a26fd5a80f7eb",
"violations": 0,
"incompleteNodes": 6,
"incompleteRules": [
"color-contrast"
]
},
{
"path": ".artifacts/phase7/p7-11/localization-visuals/p706-read-en-light-1440-strict-locale-dirty-review-notes-audit-4265ce03.json",
"sha256": "4265ce03a8813f0d8f4b61d5ee4939d78eb76cee1f714318e34afb34b7f95aec",
"violations": 0,
"incompleteNodes": 6,
"incompleteRules": [
"color-contrast"
]
},
{
"path": ".artifacts/phase7/p7-11/localization-visuals/p706-read-en-light-320-long-inbox-audit-50f8f83c.json",
"sha256": "50f8f83c2ab49ea0b30fd5fa19dad8c39719d60a9b904c3dfe8e9d3ac933315a",
"violations": 0,
"incompleteNodes": 4,
"incompleteRules": [
"color-contrast"
]
},
{
"path": ".artifacts/phase7/p7-11/localization-visuals/p706-read-nb-dark-360-long-review-audit-be3aec8f.json",
"sha256": "be3aec8fde7c23be9fb2aa478db45ad5d76e2d5607804bbb30292ad2c18699c7",
"violations": 0,
"incompleteNodes": 3,
"incompleteRules": [
"color-contrast"
]
},
{
"path": ".artifacts/phase7/p7-11/localization-visuals/p706-read-en-dark-390-long-detail-audit-9f0a7539.json",
"sha256": "9f0a75394a28afaf5ce23389f0f6295325640aa19ed5978a34b2e65813e2ad3f",
"violations": 0,
"incompleteNodes": 4,
"incompleteRules": [
"color-contrast"
]
},
{
"path": ".artifacts/phase7/p7-11/localization-visuals/p706-read-nb-light-768-long-inbox-audit-254b0273.json",
"sha256": "254b0273b6613be3d881b2d41e0e5f6d5a5815d2b77d1f62670bab802564dfa2",
"violations": 0,
"incompleteNodes": 4,
"incompleteRules": [
"color-contrast"
]
},
{
"path": ".artifacts/phase7/p7-11/localization-visuals/p706-read-en-dark-1024-long-review-audit-38f2af6c.json",
"sha256": "38f2af6cb7afdb5f75769f68e7a567f0ecc532c7ab1bb5f3b9db48048b4a0327",
"violations": 0,
"incompleteNodes": 3,
"incompleteRules": [
"color-contrast"
]
},
{
"path": ".artifacts/phase7/p7-11/localization-visuals/p706-read-nb-light-1280-long-detail-audit-f194a43a.json",
"sha256": "f194a43a4d076d0f351fca7c58004c152d8698d28916a0b2e589bc02cba77634",
"violations": 0,
"incompleteNodes": 4,
"incompleteRules": [
"color-contrast"
]
},
{
"path": ".artifacts/phase7/p7-11/localization-visuals/p706-read-en-light-1440-long-review-audit-fe447c3d.json",
"sha256": "fe447c3d612fa4dfa8fd89fd02a79b1d6535c446870b74a3c796d0e924e95634",
"violations": 0,
"incompleteNodes": 3,
"incompleteRules": [
"color-contrast"
]
},
{
"path": ".artifacts/phase7/p7-11/localization-visuals/p706-read-nb-dark-1920-long-inbox-audit-0bccb006.json",
"sha256": "0bccb006105ba6e269d3b8e119a702ccd598d597fc5284e1f2354c4acaf3fcd1",
"violations": 0,
"incompleteNodes": 4,
"incompleteRules": [
"color-contrast"
]
},
{
"path": ".artifacts/phase7/p7-11/localization-visuals/p706-compose-nb-light-1440-strict-language-dirty-draft-audit-153568d2.json",
"sha256": "153568d2e461ebb52d0646a710edee0ea11cdc72997ddc9edd5e36e41f4c9656",
"violations": 0,
"incompleteNodes": 11,
"incompleteRules": [
"color-contrast"
]
},
{
"path": ".artifacts/phase7/p7-11/localization-visuals/p706-compose-en-light-320-long-draft-keyboard-audit-b3f8f90d.json",
"sha256": "b3f8f90d8d715c0c96886efa2f1abdb9d9c9e69bac79789f778b39da085f36b8",
"violations": 0,
"incompleteNodes": 10,
"incompleteRules": [
"color-contrast"
]
},
{
"path": ".artifacts/phase7/p7-11/localization-visuals/p706-compose-en-light-320-owned-setup-keyboard-audit-65797a52.json",
"sha256": "65797a523e148efc6eaefb9c8063f95c029e367efde1d0291735f17c843addbd",
"violations": 0,
"incompleteNodes": 13,
"incompleteRules": [
"color-contrast"
]
},
{
"path": ".artifacts/phase7/p7-11/localization-visuals/p706-compose-nb-dark-360-long-draft-keyboard-audit-a35ebf17.json",
"sha256": "a35ebf171b7ed2973bc2428f689fffb83424466027e6388ca79dac9fede89d97",
"violations": 0,
"incompleteNodes": 10,
"incompleteRules": [
"color-contrast"
]
},
{
"path": ".artifacts/phase7/p7-11/localization-visuals/p706-compose-en-dark-390-long-draft-keyboard-audit-9b7f1f51.json",
"sha256": "9b7f1f51c6f0d3feb1bd94d9923ffa4feee0a8c6f4ccac83056c37319f518e55",
"violations": 0,
"incompleteNodes": 10,
"incompleteRules": [
"color-contrast"
]
},
{
"path": ".artifacts/phase7/p7-11/localization-visuals/p706-compose-en-dark-390-send-review-keyboard-audit-1fd29547.json",
"sha256": "1fd29547d441f7857cc1310ac895aedb1c0e15c0d3464c14a0b82041fc719f5b",
"violations": 0,
"incompleteNodes": 12,
"incompleteRules": [
"aria-hidden-focus",
"color-contrast"
]
},
{
"path": ".artifacts/phase7/p7-11/localization-visuals/p706-compose-nb-light-768-long-draft-keyboard-audit-06e4c6c9.json",
"sha256": "06e4c6c9450419f8c1de35cde0593ae053151910cb6edc064159e54eda6f377a",
"violations": 0,
"incompleteNodes": 10,
"incompleteRules": [
"color-contrast"
]
},
{
"path": ".artifacts/phase7/p7-11/localization-visuals/p706-compose-nb-light-768-owned-setup-keyboard-audit-c5fecd9b.json",
"sha256": "c5fecd9b2a1306747dade88d53714d5162f17909a4d7eb676b0ba6a48891c8be",
"violations": 0,
"incompleteNodes": 13,
"incompleteRules": [
"color-contrast"
]
},
{
"path": ".artifacts/phase7/p7-11/localization-visuals/p706-compose-en-dark-1024-long-draft-keyboard-audit-839b1fe3.json",
"sha256": "839b1fe3204e12ae95333dafaa6090f81b38fe13ee7a4481a3a15d0d4ccd9eaf",
"violations": 0,
"incompleteNodes": 10,
"incompleteRules": [
"color-contrast"
]
},
{
"path": ".artifacts/phase7/p7-11/localization-visuals/p706-compose-nb-light-1280-long-draft-keyboard-audit-cdccb40b.json",
"sha256": "cdccb40b2fd830f5cad43e936d0403b55b681bec48270356ce5a072715719a8a",
"violations": 0,
"incompleteNodes": 10,
"incompleteRules": [
"color-contrast"
]
},
{
"path": ".artifacts/phase7/p7-11/localization-visuals/p706-compose-nb-light-1280-send-review-keyboard-audit-ee1dd0e7.json",
"sha256": "ee1dd0e7572f455a2ddf538017982451ec1fa8399e5c4dbf25d487bd57a02629",
"violations": 0,
"incompleteNodes": 12,
"incompleteRules": [
"aria-hidden-focus",
"color-contrast"
]
},
{
"path": ".artifacts/phase7/p7-11/localization-visuals/p706-compose-en-light-1440-long-draft-keyboard-audit-a560b64c.json",
"sha256": "a560b64ca441dc14bda4a41fe641144ba3cf292106fff46d2834ab9ad72ba1e3",
"violations": 0,
"incompleteNodes": 10,
"incompleteRules": [
"color-contrast"
]
},
{
"path": ".artifacts/phase7/p7-11/localization-visuals/p706-compose-en-light-1440-owned-setup-keyboard-audit-c355f1dc.json",
"sha256": "c355f1dc15c020a27a55f8e0801b943b72d9c719992a5f423cb7e5908c4cf110",
"violations": 0,
"incompleteNodes": 13,
"incompleteRules": [
"color-contrast"
]
},
{
"path": ".artifacts/phase7/p7-11/localization-visuals/p706-compose-nb-dark-1920-long-draft-keyboard-audit-b166fa0c.json",
"sha256": "b166fa0c14493ba6ec4ee820ed187bc216e3fcf10cf9044a19e4be5a5030a1d2",
"violations": 0,
"incompleteNodes": 10,
"incompleteRules": [
"color-contrast"
]
}
],
"focusedUnit": {
"path": ".artifacts/phase7/p7-11/localization/green.json",
"sha256": "cc6e8a986cbfdc707299fee09b9eb10a130afe96caf5805a1fdf4d206f70e8bd",
"numPassedTests": 76,
"numFailedTests": 0,
"numPendingTests": 0,
"numPassedTestSuites": 4,
"qualification": "Overlapping focused vectors; not a new full-suite total. Final integrated run covers subsequent documented corrections."
}
}
@@ -0,0 +1,104 @@
{
"schemaVersion": 1,
"checkpoint": "p7-11",
"status": "PASS",
"checkedUtc": "2026-10-20T18:27:31.645877+00:00",
"commands": [
{
"name": "secret-scan",
"command": [
"rtk",
"proxy",
"C:\\Users\\Connor\\AppData\\Local\\Programs\\Python\\Python312\\python.exe",
"-X",
"utf8",
"scripts/supply-chain.py",
"scan-secrets"
],
"startedUtc": "2026-10-20T18:27:28.216719+00:00",
"finishedUtc": "2026-10-20T18:27:30.845935+00:00",
"exitCode": 0,
"log": ".artifacts/phase7/current/static/20261020T182728Z/secret-scan.log",
"logSha256Raw": "be5119e6057287d397e87759f855c91e634e3bfac61c8be7584d05592ba2dfcd"
},
{
"name": "working-whitespace",
"command": [
"rtk",
"proxy",
"git",
"diff",
"--check"
],
"startedUtc": "2026-10-20T18:27:30.846994+00:00",
"finishedUtc": "2026-10-20T18:27:30.947638+00:00",
"exitCode": 0,
"log": ".artifacts/phase7/current/static/20261020T182728Z/working-whitespace.log",
"logSha256Raw": "228a53f8fb33d6295717d33413b91ed30e8b6c8798498c0712bf14ad41f33b65"
},
{
"name": "staged-whitespace",
"command": [
"rtk",
"proxy",
"git",
"diff",
"--cached",
"--check"
],
"startedUtc": "2026-10-20T18:27:30.947638+00:00",
"finishedUtc": "2026-10-20T18:27:31.037564+00:00",
"exitCode": 0,
"log": ".artifacts/phase7/current/static/20261020T182728Z/staged-whitespace.log",
"logSha256Raw": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
{
"name": "phase7-whitespace",
"command": [
"rtk",
"proxy",
"git",
"diff",
"--check",
"b5319981ad3d1bd635daff861453d23c0de5320b"
],
"startedUtc": "2026-10-20T18:27:31.038587+00:00",
"finishedUtc": "2026-10-20T18:27:31.226719+00:00",
"exitCode": 0,
"log": ".artifacts/phase7/current/static/20261020T182728Z/phase7-whitespace.log",
"logSha256Raw": "34416ce3f522883664d5c28bf78c7cff2163564949ca86327bd5ece568ce1e56"
}
],
"untrackedSecretScan": {
"files": 4,
"findings": 0
},
"documentation": {
"files": 38,
"relativeLinks": 645,
"errors": []
},
"preservation": {
"groups": 57,
"statuses": {
"PARTIAL": 56,
"PASS": 0,
"FAIL": 0,
"DEFERRED": 1
},
"originalRoutesPreserved": true,
"currentConcreteRoutes": 33
},
"production": {
"main": "f7dc22e5e7efe1fd1d64f6ace2c04cf2f95bf558",
"originMain": "f7dc22e5e7efe1fd1d64f6ace2c04cf2f95bf558",
"status": "NO-GO",
"qualification": "Local fetched refs; direct push observation recorded separately."
},
"hashPolicy": "Portable JSON UTF8 LF; raw log hashes measure exact saved bytes.",
"limits": [
"Pattern scanner skips binary and files over2MiB; not historical Git/production secrets.",
"Local links check target existence, not URL/heading anchors.",
"Static evidence is not runtime/manual/device/provider/production acceptance."
]
}
+3 -3
View File
@@ -216,7 +216,7 @@ test('[P7-04 PRES-C05 C08 E08] locale-retained create draft locks during held fa
await expect(page.locator('html')).toHaveAttribute('lang', 'nb-NO');
dialog = await openCreate(page, 'nb');
await expect(dialog.getByLabel(translations.nb.cvEditorName, { exact: false })).toHaveValue(name);
await expect(dialog.getByRole('button', { name: selected.name, exact: true })).toHaveAttribute('aria-pressed', 'true');
await expect(dialog.getByRole('button', { name: translations.nb.cvTemplateMinimalName, exact: true })).toHaveAttribute('aria-pressed', 'true');
expect({ listReads, themeReads }).toEqual(readsBeforeLocale);
try {
await dialog.getByRole('button', { name: translations.nb.cvDashboardCreateCv, exact: true }).focus(); await page.keyboard.press('Enter');
@@ -225,7 +225,7 @@ test('[P7-04 PRES-C05 C08 E08] locale-retained create draft locks during held fa
await expect(dialog.getByLabel(translations.nb.cvEditorName, { exact: false })).toBeDisabled();
await expect(dialog.getByRole('button', { name: translations.nb.cancel, exact: true })).toBeDisabled();
await expect(dialog.getByRole('button', { name: translations.nb.cvEditorAtsTemplates, exact: true })).toBeDisabled();
await expect(dialog.getByRole('button', { name: selected.name, exact: true })).toHaveAttribute('aria-disabled', 'true');
await expect(dialog.getByRole('button', { name: translations.nb.cvTemplateMinimalName, exact: true })).toHaveAttribute('aria-disabled', 'true');
await page.keyboard.press('Enter'); await page.keyboard.press('Escape');
await expect(dialog).toBeVisible(); expect(payloads).toHaveLength(1);
const content = dialog.getByRole('region', { name: translations.nb.cvDashboardCreateTitle, exact: true });
@@ -244,7 +244,7 @@ test('[P7-04 PRES-C05 C08 E08] locale-retained create draft locks during held fa
} finally { release(); }
await expect(dialog.getByText('Controlled create unavailable', { exact: true })).toBeVisible();
await expect(dialog.getByLabel(translations.nb.cvEditorName, { exact: false })).toHaveValue(name);
await expect(dialog.getByRole('button', { name: selected.name, exact: true })).toHaveAttribute('aria-pressed', 'true');
await expect(dialog.getByRole('button', { name: translations.nb.cvTemplateMinimalName, exact: true })).toHaveAttribute('aria-pressed', 'true');
const createdResponse = page.waitForResponse(response => response.url() === `${apiUrl}/cv/variants` && response.request().method() === 'POST');
await dialog.getByRole('button', { name: translations.nb.cvDashboardCreateCv, exact: true }).click();
const created = await json<CvVariant>(await createdResponse);
+17 -3
View File
@@ -98,6 +98,20 @@ function action(row: JobApplication, language: Sample['language'] = 'en') {
: signal.actionKey === 'interview-prep' ? t.jobTableInterviewStage : signal.actionKey === 'follow-up' ? t.jobTableFollowUp : t.jobTableReadiness;
return `${label}: ${row.jobTitle}`;
}
function fixtureReason(row: JobApplication, language: Sample['language']) {
if (language === 'en') return row.workflowSignal!.reason;
const t = translations[language];
switch (row.workflowSignal!.actionKey) {
case 'package-work': return t.reminderReasonCvAndAnswers;
case 'interview-prep': return t.reminderReasonInterview;
case 'review-readiness': return t.reminderReasonNoGaps;
default: {
const feedback = /^Feedback requested (\d+)d ago$/.exec(row.workflowSignal!.reason);
expect(feedback).not.toBeNull();
return text(t.reminderReasonFeedbackDays, { days: feedback![1] });
}
}
}
function requests(session: Session, method: string, path: string) { return session.requests.filter(request => request.method === method && request.path === path); }
function hold() { let release!: () => void; const pending = new Promise<void>(resolve => { release = resolve; }); return { pending, release }; }
async function keyboard(page: Page, control: Locator) { await control.focus(); await expect(control).toBeFocused(); await page.keyboard.press('Enter'); }
@@ -261,7 +275,7 @@ test('[P7-05 Reminders] committed PATCH204 followed by a failed reload retries o
} finally { gate.release(); }
});
test('[P7-05 Reminders] live EN/Bokmal translates status/date controls and preserves raw reasons without a page-triggered refetch', async ({ page, session }, info) => {
test('[P7-05 Reminders] live EN/Bokmal translates status/date controls and generated reasons without a page-triggered refetch', async ({ page, session }, info) => {
test.setTimeout(90_000); const { rows } = await groups(page.request, session.owner);
await enter(page, session.owner); await expect(region(page).getByRole('article')).toHaveCount(4);
await expect.poll(() => requests(session, 'GET', '/api/jobapplications/reminders').length).toBeGreaterThanOrEqual(2);
@@ -269,7 +283,7 @@ test('[P7-05 Reminders] live EN/Bokmal translates status/date controls and prese
const preference = response(page, 'PUT', `${apiUrl}/auth/preferences/language`);
await keyboard(page, page.locator('header').getByRole('button', { name: 'Norsk', exact: true })); expect((await preference).status()).toBe(204);
await expect(page.locator('html')).toHaveAttribute('lang', 'nb-NO'); await expect(region(page, 'nb').getByRole('article')).toHaveCount(4);
for (const row of rows) await expect(article(page, row.jobTitle)).toContainText(row.workflowSignal!.reason);
for (const row of rows) await expect(article(page, row.jobTitle)).toContainText(fixtureReason(row, 'nb'));
await expect(article(page, rows[1].jobTitle).getByText(translations.nb.statusInterview, { exact: true })).toBeVisible();
const authoritative = (await read(page.request)).find(row => row.id === rows[3].id)!;
const date = await page.evaluate(value => new Date(value).toLocaleDateString('nb-NO'), authoritative.followUpAt!);
@@ -297,7 +311,7 @@ test('[P7-05 Reminders] eight finite bilingual theme widths preserve long rows a
await enter(page, session.owner, sample); await expect(region(page, sample.language).getByRole('article')).toHaveCount(4);
for (const row of rows) {
await expect(article(page, row.jobTitle).getByRole('heading', { level: 4 })).toContainText(row.jobTitle);
await expect(article(page, row.jobTitle)).toContainText(row.workflowSignal!.reason);
await expect(article(page, row.jobTitle)).toContainText(fixtureReason(row, sample.language));
const open = article(page, row.jobTitle).getByRole('button', { name: action(row, sample.language), exact: true });
await open.scrollIntoViewIfNeeded(); await open.focus(); await expect(open).toBeFocused();
for (const days of [3, 7]) { await snooze(page, row, days, sample.language).focus(); await expect(snooze(page, row, days, sample.language)).toBeFocused(); }
+19 -2
View File
@@ -3,7 +3,7 @@ import "@testing-library/jest-dom";
import { act, fireEvent, render as rtlRender, screen, waitFor, within } from "@testing-library/react";
import {
ApplicationCoverLetterSection, ApplicationCvSection, ApplicationPackageDraftsSection, SubmittedPackagesSection,
ApplicationCoverLetterSection, ApplicationCvSection, ApplicationPackageDraftsSection, ApplicationTailoringSection, SubmittedPackagesSection,
} from "./components/ApplicationAssets";
import { api } from "./api";
import { I18nProvider } from "./i18n/I18nProvider";
@@ -104,6 +104,23 @@ function deferred<T>() {
return { promise, resolve, reject };
}
test("[P7-11 tailoring] generated metadata and known history enums follow locale while evidence, unknown metadata and dirty prose stay verbatim", async () => {
routeGet({ tailoring: { ...tailoring, suggestions: [...tailoring.suggestions, { kind: "custom", title: "My custom title", detail: "My custom detail", items: ["My verified evidence"] }] }, coverLetter: { ...coverLetter, versions: [...coverLetter.versions, { version: 0, source: "custom-source", aiAction: "custom-action", length: 5, createdAtUtc: "2026-07-19T09:00:00Z", isCurrent: false }] } });
render(<><ApplicationTailoringSection jobId={7} /><ApplicationCoverLetterSection jobId={7} /></>);
await screen.findByText("Skills to highlight");
const letter = await screen.findByRole("textbox", { name: "Cover letter" });
fireEvent.change(letter, { target: { value: "My unsaved English letter" } });
const reads = mockedApi.get.mock.calls.length;
changeLanguage("nb");
expect(screen.getByText(copy("assetsTailoringHighlightTitle"))).toBeInTheDocument();
expect(screen.getByText(copy("assetsTailoringHighlightDetail"))).toBeInTheDocument();
expect(screen.getByText(`${copy("historySourceAi")} · ${copy("coverActionImprove")}`)).toBeInTheDocument();
expect(screen.getByText("custom-source · custom-action")).toBeInTheDocument();
for (const text of ["My custom title", "My custom detail", "My verified evidence", "C#", "SQL"]) expect(screen.getByText(text)).toBeInTheDocument();
expect(screen.getByRole("textbox", { name: copy("jobDetailsCoverLetter") })).toHaveValue("My unsaved English letter");
expect(mockedApi.get).toHaveBeenCalledTimes(reads); expect(mockedApi.put).not.toHaveBeenCalled(); expect(mockedApi.post).not.toHaveBeenCalled();
});
test("document package history is read-only and separates captured notes from answers", async () => {
const summary = { id: 91, version: 1, cvVariantId: 3, cvVariantName: "Original CV", cvVariantVersion: 2, cvThemeId: "nordic", hasCv: false, hasCoverLetter: true, createdAtUtc: "2026-07-19T10:00:00Z", attachments: [] };
mockedApi.get.mockImplementation((url: string) => Promise.resolve({ data: url.endsWith('/91') ? {
@@ -197,7 +214,7 @@ test("cover letter loads the current text and its history", async () => {
expect(await screen.findByDisplayValue("Dear team")).toBeInTheDocument();
expect(screen.getByText("v2")).toBeInTheDocument();
expect(screen.getByText("ai · improve")).toBeInTheDocument();
expect(screen.getByText("AI suggestion · Improve")).toBeInTheDocument();
expect(screen.getByText("Current")).toBeInTheDocument();
});
@@ -404,6 +404,15 @@ export function SubmittedPackagesSection({ jobId, allowCapture = true }: { jobId
// ---------- Tailoring ----------
const tailoringLabels = new Map<string, [TranslationKey, TranslationKey]>([
["highlight-skills", ["assetsTailoringHighlightTitle", "assetsTailoringHighlightDetail"]],
["prioritise-experience", ["assetsTailoringExperienceTitle", "assetsTailoringExperienceDetail"]],
["emphasise-projects", ["assetsTailoringProjectsTitle", "assetsTailoringProjectsDetail"]],
["career-evidence", ["assetsTailoringEvidenceTitle", "assetsTailoringEvidenceDetail"]],
["include-keywords", ["assetsTailoringKeywordsTitle", "assetsTailoringKeywordsDetail"]],
["gaps", ["assetsTailoringGapsTitle", "assetsTailoringGapsDetail"]],
]);
export function ApplicationTailoringSection({ jobId }: { jobId: number }) {
const { t } = useI18n();
const { data, error, loading, reload } = useAsset<TailoringPlan>(
@@ -437,11 +446,13 @@ export function ApplicationTailoringSection({ jobId }: { jobId: number }) {
{t("assetsNoSuggestions")}
</Typography>
) : (
(data?.suggestions ?? []).map((s) => (
(data?.suggestions ?? []).map((s) => {
const labels = tailoringLabels.get(s.kind);
return (
<Box key={s.kind}>
<Typography variant="body2" sx={{ fontWeight: 700 }}>{s.title}</Typography>
<Typography variant="body2" sx={{ fontWeight: 700 }}>{labels ? t(labels[0]) : s.title}</Typography>
{s.detail && (
<Typography variant="caption" color="text.secondary">{s.detail}</Typography>
<Typography variant="caption" color="text.secondary">{labels ? t(labels[1]) : s.detail}</Typography>
)}
<Stack direction="row" flexWrap="wrap" gap={0.5} sx={{ mt: 0.75 }}>
{s.items.map((item) => (
@@ -449,7 +460,7 @@ export function ApplicationTailoringSection({ jobId }: { jobId: number }) {
))}
</Stack>
</Box>
))
); })
)}
</Stack>
</Shell>
@@ -458,6 +469,18 @@ export function ApplicationTailoringSection({ jobId }: { jobId: number }) {
// ---------- Cover letter ----------
const historySources = new Map<string, TranslationKey>([
["manual", "historySourceManual"], ["ai", "historySourceAi"],
["template", "historySourceTemplate"], ["restore", "historySourceRestore"],
]);
const historyAiActions = new Map<string, TranslationKey>([
["generate", "coverActionGenerate"], ["regenerate", "coverActionRegenerate"],
["improve", "coverActionImprove"], ["shorten", "coverActionShorten"],
["expand", "coverActionExpand"], ["professional", "coverActionProfessional"],
["natural", "coverActionNatural"], ["grammar", "coverActionGrammar"],
["tone", "coverAiTone"], ["tailor", "coverActionTailor"],
]);
const COVER_LETTER_TEMPLATES = {
en: `Dear Hiring Manager,
@@ -615,7 +638,12 @@ export function ApplicationCoverLetterSection({ jobId, onDirtyChange }: { jobId:
</Typography>
) : (
<Stack spacing={0.5}>
{(data?.versions ?? []).map((v) => (
{(data?.versions ?? []).map((v) => {
const sourceKey = historySources.get(v.source);
const actionKey = historyAiActions.get(v.aiAction ?? "");
const source = sourceKey ? t(sourceKey) : v.source;
const action = actionKey ? t(actionKey) : v.aiAction;
return (
<Stack
key={v.version}
direction="row"
@@ -626,7 +654,7 @@ export function ApplicationCoverLetterSection({ jobId, onDirtyChange }: { jobId:
<Box sx={{ minWidth: 0 }}>
<Stack direction="row" spacing={0.75} alignItems="center" flexWrap="wrap">
<Typography variant="body2" sx={{ fontWeight: 600 }}>v{v.version}</Typography>
<Chip size="small" variant="outlined" label={v.aiAction ? `${v.source} · ${v.aiAction}` : v.source} />
<Chip size="small" variant="outlined" label={action ? `${source} · ${action}` : source} />
{v.isCurrent && <Chip size="small" color="primary" variant="outlined" label={t("assetsCurrent")} />}
</Stack>
<Typography variant="caption" color="text.secondary">
@@ -648,7 +676,7 @@ export function ApplicationCoverLetterSection({ jobId, onDirtyChange }: { jobId:
</Tooltip>
)}
</Stack>
))}
); })}
</Stack>
)}
</Shell>
@@ -4,6 +4,7 @@ import { Box, Button, Chip, Paper, Stack, Typography } from "@mui/material";
import { CvTheme, getVisibleCvThemes } from "../cvBuilder";
import { useI18n } from "../i18n/I18nProvider";
import type { TranslationKey } from "../i18n/translations";
import CvTemplateThumbnail from "./CvTemplateThumbnail";
type Props = {
@@ -15,8 +16,20 @@ type Props = {
disabled?: boolean;
};
const templateLabels = new Map<string, [TranslationKey, TranslationKey, TranslationKey]>([
["modern", ["cvTemplateModernName", "cvTemplateCategoryModern", "cvTemplateModernDescription"]],
["minimal", ["cvTemplateMinimalName", "cvTemplateCategoryMinimal", "cvTemplateMinimalDescription"]],
["executive", ["cvTemplateExecutiveName", "cvTemplateCategoryExecutive", "cvTemplateExecutiveDescription"]],
["technical", ["cvTemplateTechnicalName", "cvTemplateCategoryTechnical", "cvTemplateTechnicalDescription"]],
["ats-classic", ["cvTemplateAtsClassicName", "cvTemplateCategoryAts", "cvTemplateAtsClassicDescription"]],
["nordic", ["cvTemplateNordicName", "cvTemplateCategoryModern", "cvTemplateNordicDescription"]],
["elegant", ["cvTemplateElegantName", "cvTemplateCategoryCreative", "cvTemplateElegantDescription"]],
["creative", ["cvTemplateCreativeName", "cvTemplateCategoryCreative", "cvTemplateCreativeDescription"]],
["code", ["cvTemplateCodeName", "cvTemplateCategoryTechnical", "cvTemplateCodeDescription"]],
]);
export default function CvTemplateGallery({ themes, selectedId, onSelect, previewHeight = 170, showDescriptions = true, disabled = false }: Props) {
const { t } = useI18n();
const { t, language } = useI18n();
const id = useId();
const [atsOnly, setAtsOnly] = useState(true);
const visibleThemes = useMemo(() => getVisibleCvThemes(themes, atsOnly, selectedId), [atsOnly, selectedId, themes]);
@@ -33,16 +46,18 @@ export default function CvTemplateGallery({ themes, selectedId, onSelect, previe
const active = theme.id === selectedId;
const locked = theme.available === false;
const unavailable = locked || disabled;
const labels = language === "nb" ? templateLabels.get(theme.id) : undefined;
const name = labels ? t(labels[0]) : theme.name;
return (
<Paper key={theme.id} variant="outlined" role="button" tabIndex={unavailable ? -1 : 0} aria-label={theme.name} aria-describedby={`${id}-${theme.id}-category ${showDescriptions ? `${id}-${theme.id}-description ` : ""}${id}-${theme.id}-features`} aria-disabled={unavailable} aria-pressed={active}
<Paper key={theme.id} variant="outlined" role="button" tabIndex={unavailable ? -1 : 0} aria-label={name} aria-describedby={`${id}-${theme.id}-category ${showDescriptions ? `${id}-${theme.id}-description ` : ""}${id}-${theme.id}-features`} aria-disabled={unavailable} aria-pressed={active}
onClick={() => { if (!unavailable) onSelect(theme); }}
onKeyDown={(event) => { if (!unavailable && (event.key === "Enter" || event.key === " ")) { event.preventDefault(); onSelect(theme); } }}
sx={{ p: 1, cursor: unavailable ? "not-allowed" : "pointer", position: "relative", minWidth: 0, overflowWrap: "anywhere", borderColor: active ? "primary.main" : "divider", borderWidth: active ? 2 : 1, "&:hover": unavailable ? undefined : { borderColor: "primary.main" } }}>
{active && <CheckCircleIcon aria-label={t("cvEditorSelectedTemplate")} color="primary" sx={{ position: "absolute", zIndex: 2, top: 6, right: 6, bgcolor: "background.paper", borderRadius: "50%" }} />}
<Box sx={{ mb: 1 }}><CvTemplateThumbnail theme={theme} height={previewHeight} /></Box>
<Typography variant="body2" sx={{ fontWeight: 750 }}>{theme.name}</Typography>
<Typography id={`${id}-${theme.id}-category`} variant="caption" color="text.secondary" sx={{ display: "block" }}>{theme.category}</Typography>
{showDescriptions && <Typography id={`${id}-${theme.id}-description`} variant="caption" color="text.secondary" sx={{ display: "block", mt: 0.25, lineHeight: 1.35 }}>{theme.description}</Typography>}
<Typography variant="body2" sx={{ fontWeight: 750 }}>{name}</Typography>
<Typography id={`${id}-${theme.id}-category`} variant="caption" color="text.secondary" sx={{ display: "block" }}>{labels ? t(labels[1]) : theme.category}</Typography>
{showDescriptions && <Typography id={`${id}-${theme.id}-description`} variant="caption" color="text.secondary" sx={{ display: "block", mt: 0.25, lineHeight: 1.35 }}>{labels ? t(labels[2]) : theme.description}</Typography>}
<Stack id={`${id}-${theme.id}-features`} direction="row" spacing={0.5} alignItems="center" flexWrap="wrap" useFlexGap sx={{ mt: 0.75 }}>
{theme.atsFriendly && <Chip size="small" label={t("cvEditorAtsFriendly")} color="success" variant="outlined" sx={{ height: 20, fontSize: 10 }} />}
{theme.requiresPro && <Chip size="small" label={locked ? t("cvEditorPro") : t("cvEditorProUnlocked")} color="secondary" variant="outlined" sx={{ height: 20, fontSize: 10 }} />}
@@ -12,6 +12,33 @@ import { useI18n } from "../i18n/I18nProvider";
import { useViewResource, type ViewResourceError } from "../hooks/useViewResource";
import { PageHeader } from "../layout/PageLayout";
import { statusLabel } from "../pipeline";
import type { TranslationKey } from "../i18n/translations";
const generatedReasons = new Map<string, TranslationKey>([
["Interview stage reached but prep notes are still missing.", "reminderReasonInterview"],
["Tailored CV and saved application answers still need work.", "reminderReasonCvAndAnswers"],
["Tailored CV missing for this role.", "reminderReasonCv"],
["Saved application answers still need work.", "reminderReasonAnswers"],
["No response yet and no follow-up is scheduled.", "reminderReasonUnscheduled"],
["Follow-up is due for this role.", "reminderReasonDue"],
["No urgent workflow gaps are blocking this job right now.", "reminderReasonNoGaps"],
]);
const generatedDayReasons: [RegExp, TranslationKey][] = [
[/^No reply after (\d+)d$/, "reminderReasonNoReplyDays"],
[/^Stalled after (\d+)d$/, "reminderReasonStalledDays"],
[/^Feedback requested (\d+)d ago$/, "reminderReasonFeedbackDays"],
[/^Waiting (\d+)d$/, "reminderReasonWaitingDays"],
];
function reminderReason(reason: string, t: ReturnType<typeof useI18n>["t"]) {
const key = generatedReasons.get(reason);
if (key) return t(key);
for (const [pattern, dayKey] of generatedDayReasons) {
const match = pattern.exec(reason);
if (match) return t(dayKey, { days: match[1] });
}
return reason;
}
type FollowUpFailure = {
job: Pick<JobApplication, "id" | "jobTitle">;
@@ -62,7 +89,7 @@ function ReminderSection({ title, items, disabled, onOpen, onSetFollowUp }: { ti
</Typography>
<Box sx={{ display: "flex", gap: 1, mt: 0.5, flexWrap: "wrap", minWidth: 0, "& .MuiChip-root": { maxWidth: "100%", height: "auto", minHeight: 24 }, "& .MuiChip-label": { whiteSpace: "normal", overflowWrap: "anywhere", py: 0.5 } }}>
{j.needsFollowUp ? <Chip size="small" color="warning" label={t("remindersFollowUpLabel")} /> : null}
{(j.workflowSignal?.reason ?? j.followUpReason) ? <Chip size="small" label={j.workflowSignal?.reason ?? j.followUpReason} variant="outlined" /> : null}
{(j.workflowSignal?.reason ?? j.followUpReason) ? <Chip size="small" label={reminderReason((j.workflowSignal?.reason ?? j.followUpReason)!, t)} variant="outlined" /> : null}
{j.followUpAt ? <Chip size="small" label={t("remindersFollowUpDate", { date: new Date(j.followUpAt).toLocaleDateString(language === "nb" ? "nb-NO" : "en") })} variant="outlined" /> : null}
<Chip size="small" label={statusLabel(t, j.status)} variant="outlined" />
</Box>
@@ -118,6 +118,24 @@ function renderPage(initialPath = '/') {
}
describe('CorrespondenceInboxPage', () => {
test('[P7-11 inbox] resumed draft fallback metadata follows locale without mutating stored prose or refetching', async () => {
const original = mockedApi.get.getMockImplementation();
mockedApi.get.mockImplementation((url: string, config?: any) => {
if (url === '/email/drafts') return Promise.resolve({ data: [savedComposeDraft({ jobApplicationId: 98 })] } as any);
if (url === '/email/providers') return Promise.resolve({ data: [{ provider: 'gmail', displayName: 'Gmail', connected: false, address: null, canRead: false, canSend: false }] } as any);
return original!(url, config);
});
renderPage();
fireEvent.click(await screen.findByRole('button', { name: /resume owned compose draft/i }));
fireEvent.change(composeField('emailInboxMessage'), { target: { value: 'My unsaved English message' } });
const reads = mockedApi.get.mock.calls.length;
await changeLanguage('nb');
expect(screen.getByText(`${copy('emailInboxJobNumber')} #98 · ${copy('emailInboxSavedDraft')}`)).toBeInTheDocument();
expect(composeField('emailInboxProvider')).toHaveValue(`Gmail · ${copy('emailInboxReconnectBeforeSending')}`);
expect(composeField('emailInboxMessage')).toHaveValue('My unsaved English message');
expect(composeField('emailInboxSubject')).toHaveValue('Owned compose draft');
expect(mockedApi.get).toHaveBeenCalledTimes(reads); expect(mockedApi.put).not.toHaveBeenCalled(); expect(mockedApi.post).not.toHaveBeenCalled();
});
beforeEach(() => {
window.localStorage.clear();
document.documentElement.lang = 'en';
@@ -176,11 +194,12 @@ describe('CorrespondenceInboxPage', () => {
renderPage();
expect(await screen.findByRole('heading', { name: /job email/i })).toBeInTheDocument();
expect(await screen.findByText(/1 items/i)).toBeInTheDocument();
expect(await screen.findByText("1 item", { exact: true })).toBeInTheDocument();
expect(await screen.findByText(/acme systems/i)).toBeInTheDocument();
expect(await screen.findByText(/backend engineer/i)).toBeInTheDocument();
expect(screen.getByText(/2 labels/i)).toBeInTheDocument();
expect(screen.getByText(/1 attachments/i)).toBeInTheDocument();
expect(screen.getByText("1 attachment", { exact: true })).toBeInTheDocument();
expect(screen.getByText("1 item", { exact: true })).toBeInTheDocument();
expect(screen.getByText(/Gmail: owner@gmail\.test · Read only; reconnect to enable send/i)).toBeInTheDocument();
expect(screen.getByText(/^Outlook: Not connected$/i)).toBeInTheDocument();
expect(screen.queryByText(/Outlook: Not connected.*Read only/i)).not.toBeInTheDocument();
@@ -0,0 +1,25 @@
import React from "react";
import "@testing-library/jest-dom";
import { act, fireEvent, render, screen } from "@testing-library/react";
import CvTemplateGallery from "./components/CvTemplateGallery";
import { I18nProvider } from "./i18n/I18nProvider";
import { translations } from "./i18n/translations";
import type { CvTheme } from "./cvBuilder";
const theme: CvTheme = { id: "modern", name: "Modern", category: "Modern Professional", description: "Clean SaaS-style layout with a confident accent header.", layout: "header-band", accent: "#2563eb", photoShape: "none", supportsIcons: false, atsFriendly: true, requiresPro: false, available: true, swatches: ["#2563eb"] };
beforeEach(() => window.localStorage.clear());
test("[P7-11 templates] known metadata follows locale while custom metadata and selection objects stay verbatim", () => {
const select = jest.fn();
const custom = { ...theme, id: "custom", name: "My Modern", category: "My category", description: "My authored description" };
render(<I18nProvider><CvTemplateGallery themes={[theme, custom]} selectedId="modern" onSelect={select} /></I18nProvider>);
expect(screen.getByRole("button", { name: "Modern" })).toBeInTheDocument();
act(() => window.dispatchEvent(new StorageEvent("storage", { key: "uiLanguage", newValue: "nb" })));
expect(screen.getByRole("button", { name: translations.nb.cvTemplateModernName })).toHaveAccessibleDescription(expect.stringContaining(translations.nb.cvTemplateModernDescription));
expect(screen.getByText(translations.nb.cvTemplateCategoryModern)).toBeInTheDocument();
expect(screen.getByText(custom.description)).toBeInTheDocument();
fireEvent.click(screen.getByRole("button", { name: translations.nb.cvTemplateModernName }));
expect(select).toHaveBeenCalledWith(theme);
expect(theme.name).toBe("Modern");
});
@@ -46,6 +46,21 @@ function signal(actionKey: WorkflowSignal["actionKey"], workspaceTab: WorkflowSi
};
}
test("[P7-11 reminders] generated fixed and parameterized reasons follow locale while custom wording stays verbatim", async () => {
mockedApi.get.mockResolvedValue({ data: [
job(81, { workflowSignal: signal("package-work", "tailored-cv", "Tailored CV missing for this role.") }),
job(82, { workflowSignal: null, followUpReason: "No reply after 14d" }),
job(83, { workflowSignal: null, followUpReason: "No reply after 14d (my own note)" }),
] } as any);
renderPage();
await screen.findByText("Tailored CV missing for this role.");
changeLanguage("nb");
expect(screen.getByText(copy("reminderReasonCv"))).toBeInTheDocument();
expect(screen.getByText(copy("reminderReasonNoReplyDays", { days: 14 }))).toBeInTheDocument();
expect(screen.getByText("No reply after 14d (my own note)")).toBeInTheDocument();
expect(mockedApi.get).toHaveBeenCalledTimes(1); assertNoOtherWrites();
});
function job(id: number, overrides: Partial<JobApplication> = {}): JobApplication {
return {
id, jobTitle: `Reminder role ${id}`, company: { id, name: `Company ${id}` }, status: "Waiting",
@@ -463,10 +463,10 @@ export default function CorrespondenceInboxPage() {
const item = items.find((candidate) => candidate.jobApplicationId === stored.jobApplicationId);
openDraft({
...stored,
companyName: item?.companyName || `${t("emailInboxJobNumber")} #${stored.jobApplicationId}`,
jobTitle: item?.jobTitle || t("emailInboxSavedDraft"),
companyName: item?.companyName || "",
jobTitle: item?.jobTitle || "",
providerName: provider?.displayName || stored.provider,
fromAddress: provider?.address || t("emailInboxReconnectBeforeSending"),
fromAddress: provider?.address || "",
});
setSendResult(null); setSendError(null); setDraftError(null);
} finally { finishComposeOperation(); }
@@ -676,7 +676,7 @@ export default function CorrespondenceInboxPage() {
<PageSection surface title={t("correspondenceInbox")} description={t("emailInboxSubtitle")}>
<Box sx={{ display: "flex", justifyContent: "space-between", gap: 2, alignItems: "center", flexWrap: "wrap", mb: 2 }}>
<Box sx={{ display: "flex", gap: 1, flexWrap: "wrap", alignItems: "center" }}>
{view === "inbox" && hasCurrentInbox ? <Chip icon={<MailOutlineIcon />} label={t("emailInboxItems", { count: inboxTotal })} variant="outlined" /> : null}
{view === "inbox" && hasCurrentInbox ? <Chip icon={<MailOutlineIcon />} label={t(inboxTotal === 1 ? "emailInboxItem" : "emailInboxItems", { count: inboxTotal })} variant="outlined" /> : null}
{view === "inbox" && hasCurrentInbox ? <Chip label={t("emailInboxLinkedOnPage", { count: filteredSummary.linked })} variant="outlined" color={filteredSummary.linked > 0 ? "success" : "default"} /> : null}
{view === "inbox" && hasCurrentInbox ? <Chip label={t("emailInboxInboundOnPage", { count: filteredSummary.inbound })} variant="outlined" /> : null}
<Button variant={view === "inbox" ? "outlined" : "text"} aria-pressed={view === "inbox"} disabled={!!unlinkingThreadId || composeBusy} size="small" onClick={() => updateFilters({ view: "inbox" })}>{t("emailInboxLinkedMessages")}</Button>
@@ -782,13 +782,13 @@ export default function CorrespondenceInboxPage() {
<Box sx={{ display: "flex", justifyContent: "space-between", gap: 2, flexWrap: "wrap", mb: 1.5 }}>
<Box>
<Typography id="email-draft-title" component="h3" variant="h6">{t("emailInboxDraftTitle")}</Typography>
<Typography variant="body2" sx={{ color: "text.secondary", overflowWrap: "anywhere" }}>{draft.companyName} · {draft.jobTitle}</Typography>
<Typography variant="body2" sx={{ color: "text.secondary", overflowWrap: "anywhere" }}>{draft.companyName || `${t("emailInboxJobNumber")} #${draft.jobApplicationId}`} · {draft.jobTitle || t("emailInboxSavedDraft")}</Typography>
{draftDirty ? <Chip size="small" variant="outlined" color="warning" label={t("assetsUnsavedChanges")} sx={{ mt: 1 }} /> : null}
</Box>
<Button onClick={() => void discardDraft()} disabled={composeBusy}>{t("emailInboxDiscardDraft")}</Button>
</Box>
<Box sx={{ display: "grid", gridTemplateColumns: { xs: "1fr", md: "1fr 1fr" }, gap: 1.25 }}>
<TextField label={t("emailInboxProvider")} value={`${draft.providerName} · ${draft.fromAddress}`} slotProps={{ input: { readOnly: true } }} />
<TextField label={t("emailInboxProvider")} value={`${draft.providerName} · ${draft.fromAddress || t("emailInboxReconnectBeforeSending")}`} slotProps={{ input: { readOnly: true } }} />
<TextField label={t("emailInboxThread")} value={draft.threadId || t("emailInboxNewMessage")} slotProps={{ input: { readOnly: true } }} />
<TextField label={t("emailInboxRecipient")} type="email" value={draft.to} onChange={(event) => updateDraft({ to: event.target.value })} disabled={composeBusy || !!sendResult} inputProps={{ maxLength: 320 }} />
<TextField label={t("emailInboxSubject")} value={draft.subject} onChange={(event) => updateDraft({ subject: event.target.value })} disabled={composeBusy || !!sendResult} inputProps={{ maxLength: 998 }} />
@@ -839,7 +839,7 @@ export default function CorrespondenceInboxPage() {
{item.provider ? <Chip size="small" label={item.provider === "microsoft" ? "Outlook" : item.provider === "gmail" ? "Gmail" : item.provider.toUpperCase()} variant="outlined" /> : null}
{item.externalThreadId ? <Chip size="small" label={t("emailInboxThreadId", { id: item.externalThreadId })} color="success" variant="outlined" /> : <Chip size="small" label={t("emailInboxManualInternal")} variant="outlined" />}
{item.labelCount > 0 ? <Chip size="small" label={t("emailInboxLabels", { count: item.labelCount })} variant="outlined" /> : null}
{item.attachmentCount > 0 ? <Chip size="small" label={t("emailInboxAttachments", { count: item.attachmentCount })} variant="outlined" /> : null}
{item.attachmentCount > 0 ? <Chip size="small" label={t(item.attachmentCount === 1 ? "emailInboxAttachmentCount" : "emailInboxAttachments", { count: item.attachmentCount })} variant="outlined" /> : null}
<Button size="small" variant="text" disabled={!!unlinkingThreadId} aria-expanded={selectedMessageId === item.id} aria-controls={selectedMessageId === item.id ? `${rowId}-${item.id}-message` : undefined} onClick={() => void showMessage(item)}>{selectedMessageId === item.id ? t("emailInboxHideMessage") : t("emailInboxViewMessage")}</Button>
<Button size="small" variant="text" disabled={!!unlinkingThreadId} onClick={() => navigate(`/jobs/${item.jobApplicationId}?section=communication`, { state: { from: location.pathname + location.search + location.hash } })}>{t("emailInboxOpenJob")}</Button>
{item.provider === "gmail" && item.externalThreadId ? (