fix(i18n): localize generated labels without changing saved content
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
|
||||
@@ -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."
|
||||
]
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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(); }
|
||||
|
||||
@@ -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 ? (
|
||||
|
||||
Reference in New Issue
Block a user