",
+ "target": [
+ ".MuiPaper-elevation24"
+ ]
+ }
+ ]
+ },
+ {
+ "attachment": "p7-companies-en-light-1440-latest20-owned-relationship-audit",
+ "target": [
+ ".css-p2q5gv.MuiBox-root:nth-child(11) > .css-ayodbu.MuiTypography-caption"
+ ],
+ "data": {
+ "contrastRatio": 0,
+ "fontSize": "9.0pt (12px)",
+ "fontWeight": "normal",
+ "messageKey": "elmPartiallyObscured",
+ "expectedContrastRatio": "4.5:1"
+ },
+ "relatedNodes": [
+ {
+ "html": "
",
+ "target": [
+ ".MuiPaper-elevation24"
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "kind": "incomplete",
+ "rule": "color-contrast",
+ "check": "color-contrast",
+ "message": "Element's background color could not be determined because it is overlapped by another element",
+ "occurrences": 51,
+ "representativeContexts": [
+ {
+ "attachment": "p7-companies-nb-dark-360-failed-save-durable-draft-audit",
+ "target": [
+ "#_r_o_"
+ ],
+ "data": {
+ "contrastRatio": 0,
+ "fontSize": "10.5pt (14px)",
+ "fontWeight": "normal",
+ "messageKey": "bgOverlap",
+ "expectedContrastRatio": "4.5:1"
+ },
+ "relatedNodes": []
+ },
+ {
+ "attachment": "p7-companies-nb-dark-360-failed-save-durable-draft-audit",
+ "target": [
+ "#_r_p_"
+ ],
+ "data": {
+ "contrastRatio": 0,
+ "fontSize": "10.5pt (14px)",
+ "fontWeight": "normal",
+ "messageKey": "bgOverlap",
+ "expectedContrastRatio": "4.5:1"
+ },
+ "relatedNodes": []
+ }
+ ]
+ }
+ ],
+ "qualification": "Every violation/incomplete category counted; complete node/check vectors remain exact raw report and extracted JSON. Parent contextual triage pending."
+ }
+ },
+ "companies-regression": {
+ "command": {
+ "name": "companies-regression",
+ "command": [
+ "rtk",
+ "proxy",
+ "node",
+ "node_modules/@playwright/test/cli.js",
+ "test",
+ "e2e/responsive-accessibility-audit.spec.ts",
+ "--grep",
+ "company save failure stays",
+ "--retries=0",
+ "--reporter=list,json",
+ "--output=E:\\Storage\\Projects\\Active\\Job tracker\\.artifacts\\phase7\\current\\ui\\companies-regression-results"
+ ],
+ "startedUtc": "2026-10-13T06:46:53.723160+00:00",
+ "finishedUtc": "2026-10-13T06:47:34.802561+00:00",
+ "exitCode": 0
+ },
+ "commandRaw": {
+ "path": ".artifacts/phase7/p7-05/companies/final-columns/companies-regression-command.json",
+ "sha256": "87149789d6c282f5acc56eddba4b2cfedf019075128bb71e5cd30e8e37a42e9e",
+ "bytes": 556,
+ "hashPolicy": "Exact measured raw bytes"
+ },
+ "reportRaw": {
+ "path": ".artifacts/phase7/p7-05/companies/final-columns/companies-regression.json",
+ "sha256": "d6d4cdca97e81eed5d2199d217dbada55df837dbfb2ee49152cd01a94d399934",
+ "bytes": 143231,
+ "hashPolicy": "Exact measured raw bytes"
+ },
+ "logRaw": {
+ "path": ".artifacts/phase7/p7-05/companies/final-columns/companies-regression.log",
+ "sha256": "8e229be2008a86ffcb940c9983ca461e97400f4b92bb9d87c7c11489c913e7c5",
+ "bytes": 827,
+ "hashPolicy": "Exact measured raw bytes"
+ },
+ "stats": {
+ "startTime": "2026-10-13T06:46:54.250Z",
+ "duration": 40521.646,
+ "expected": 2,
+ "skipped": 0,
+ "unexpected": 0,
+ "flaky": 0
+ },
+ "cases": [
+ {
+ "title": "[P6-02] en company save failure stays in the dialog and retry persists the retained draft",
+ "file": "responsive-accessibility-audit.spec.ts",
+ "line": 66,
+ "expectedStatus": "passed",
+ "project": "chromium",
+ "resultStatuses": [
+ "passed"
+ ],
+ "retries": [
+ 0
+ ],
+ "errors": [
+ []
+ ],
+ "durationMs": 17251
+ },
+ {
+ "title": "[P6-02] nb company save failure stays in the dialog and retry persists the retained draft",
+ "file": "responsive-accessibility-audit.spec.ts",
+ "line": 66,
+ "expectedStatus": "passed",
+ "project": "chromium",
+ "resultStatuses": [
+ "passed"
+ ],
+ "retries": [
+ 0
+ ],
+ "errors": [
+ []
+ ],
+ "durationMs": 16251
+ }
+ ],
+ "reportErrors": [],
+ "configuration": {
+ "version": "1.62.1",
+ "workers": 1,
+ "projects": [
+ {
+ "name": "chromium",
+ "retries": 0
+ }
+ ]
+ },
+ "audit": {
+ "scans": [],
+ "violationNodes": 0,
+ "incompleteNodes": 0,
+ "checkContexts": [],
+ "qualification": "Every violation/incomplete category counted; complete node/check vectors remain exact raw report and extracted JSON. Parent contextual triage pending."
+ }
+ },
+ "companies-named": {
+ "command": {
+ "name": "companies-named",
+ "command": [
+ "rtk",
+ "proxy",
+ "node",
+ "node_modules/@playwright/test/cli.js",
+ "test",
+ "e2e/named-controls-modal-state.spec.ts",
+ "--retries=0",
+ "--reporter=list,json",
+ "--output=E:\\Storage\\Projects\\Active\\Job tracker\\.artifacts\\phase7\\current\\ui\\companies-named-results"
+ ],
+ "startedUtc": "2026-10-13T06:47:34.807583+00:00",
+ "finishedUtc": "2026-10-13T06:48:23.908394+00:00",
+ "exitCode": 0
+ },
+ "commandRaw": {
+ "path": ".artifacts/phase7/p7-05/companies/final-columns/companies-named-command.json",
+ "sha256": "1db88e9b06980b9d6c8b918b4f48643539ee74f2960331fcbca36b448b874f22",
+ "bytes": 492,
+ "hashPolicy": "Exact measured raw bytes"
+ },
+ "reportRaw": {
+ "path": ".artifacts/phase7/p7-05/companies/final-columns/companies-named.json",
+ "sha256": "e17e178b7e626f2a0ee6b622f86b741dcfddfb46da0959f88a4cb37db89b4527",
+ "bytes": 296885,
+ "hashPolicy": "Exact measured raw bytes"
+ },
+ "logRaw": {
+ "path": ".artifacts/phase7/p7-05/companies/final-columns/companies-named.log",
+ "sha256": "a8f6ff80a876e68aa20c19e707e690687b93408f6b9b64403e0b8ee9edbc8775",
+ "bytes": 1305,
+ "hashPolicy": "Exact measured raw bytes"
+ },
+ "stats": {
+ "startTime": "2026-10-13T06:47:35.328Z",
+ "duration": 48545.494,
+ "expected": 4,
+ "skipped": 0,
+ "unexpected": 0,
+ "flaky": 0
+ },
+ "cases": [
+ {
+ "title": "[PRES-E07 PRES-E08 PRES-J08 PRES-C08 PRES-C09 PRES-W06 PRES-W08] light/en/1440 named controls, modal keyboard and retained draft",
+ "file": "named-controls-modal-state.spec.ts",
+ "line": 62,
+ "expectedStatus": "passed",
+ "project": "chromium",
+ "resultStatuses": [
+ "passed"
+ ],
+ "retries": [
+ 0
+ ],
+ "errors": [
+ []
+ ],
+ "durationMs": 11470
+ },
+ {
+ "title": "[PRES-E07 PRES-E08 PRES-J08 PRES-C08 PRES-C09 PRES-W06 PRES-W08] dark/nb/1440 named controls, modal keyboard and retained draft",
+ "file": "named-controls-modal-state.spec.ts",
+ "line": 62,
+ "expectedStatus": "passed",
+ "project": "chromium",
+ "resultStatuses": [
+ "passed"
+ ],
+ "retries": [
+ 0
+ ],
+ "errors": [
+ []
+ ],
+ "durationMs": 10327
+ },
+ {
+ "title": "[PRES-E07 PRES-E08 PRES-J08 PRES-C08 PRES-C09 PRES-W06 PRES-W08] light/nb/390 named controls, modal keyboard and retained draft",
+ "file": "named-controls-modal-state.spec.ts",
+ "line": 62,
+ "expectedStatus": "passed",
+ "project": "chromium",
+ "resultStatuses": [
+ "passed"
+ ],
+ "retries": [
+ 0
+ ],
+ "errors": [
+ []
+ ],
+ "durationMs": 9834
+ },
+ {
+ "title": "[PRES-E07 PRES-E08 PRES-J08 PRES-C08 PRES-C09 PRES-W06 PRES-W08] dark/en/390 named controls, modal keyboard and retained draft",
+ "file": "named-controls-modal-state.spec.ts",
+ "line": 62,
+ "expectedStatus": "passed",
+ "project": "chromium",
+ "resultStatuses": [
+ "passed"
+ ],
+ "retries": [
+ 0
+ ],
+ "errors": [
+ []
+ ],
+ "durationMs": 9793
+ }
+ ],
+ "reportErrors": [],
+ "configuration": {
+ "version": "1.62.1",
+ "workers": 1,
+ "projects": [
+ {
+ "name": "chromium",
+ "retries": 0
+ }
+ ]
+ },
+ "audit": {
+ "scans": [
+ {
+ "test": "[PRES-E07 PRES-E08 PRES-J08 PRES-C08 PRES-C09 PRES-W06 PRES-W08] light/en/1440 named controls, modal keyboard and retained draft",
+ "attachment": "command-axe",
+ "geometry": null,
+ "engine": null,
+ "toolOptions": null,
+ "violationNodes": 0,
+ "incompleteNodes": 1,
+ "raw": {
+ "path": ".artifacts/phase7/p7-05/companies/final-columns/embedded/companies-named/001-command-axe.json",
+ "sha256": "d7488dd58cfcf3e8ea86ca38d65d46a7ee5cbeb5c3278acb35ea83763e5329cd",
+ "bytes": 1567,
+ "hashPolicy": "Exact measured raw bytes"
+ },
+ "originalEmbeddedReport": {
+ "path": ".artifacts/phase7/p7-05/companies/final-columns/companies-named.json",
+ "sha256": "e17e178b7e626f2a0ee6b622f86b741dcfddfb46da0959f88a4cb37db89b4527",
+ "bytes": 296885,
+ "hashPolicy": "Exact measured raw bytes"
+ },
+ "context": null,
+ "rules": [
+ {
+ "kind": "incomplete",
+ "id": "aria-valid-attr-value",
+ "nodes": 1
+ }
+ ]
+ },
+ {
+ "test": "[PRES-E07 PRES-E08 PRES-J08 PRES-C08 PRES-C09 PRES-W06 PRES-W08] light/en/1440 named controls, modal keyboard and retained draft",
+ "attachment": "cv-controls-axe",
+ "geometry": null,
+ "engine": null,
+ "toolOptions": null,
+ "violationNodes": 0,
+ "incompleteNodes": 27,
+ "raw": {
+ "path": ".artifacts/phase7/p7-05/companies/final-columns/embedded/companies-named/002-cv-controls-axe.json",
+ "sha256": "07a8fca196ac21e5e7f7c0566054ffbc4cef768ad4802967e7325aa81391d318",
+ "bytes": 33100,
+ "hashPolicy": "Exact measured raw bytes"
+ },
+ "originalEmbeddedReport": {
+ "path": ".artifacts/phase7/p7-05/companies/final-columns/companies-named.json",
+ "sha256": "e17e178b7e626f2a0ee6b622f86b741dcfddfb46da0959f88a4cb37db89b4527",
+ "bytes": 296885,
+ "hashPolicy": "Exact measured raw bytes"
+ },
+ "context": null,
+ "rules": [
+ {
+ "kind": "incomplete",
+ "id": "aria-hidden-focus",
+ "nodes": 1
+ },
+ {
+ "kind": "incomplete",
+ "id": "aria-prohibited-attr",
+ "nodes": 2
+ },
+ {
+ "kind": "incomplete",
+ "id": "color-contrast",
+ "nodes": 24
+ }
+ ]
+ },
+ {
+ "test": "[PRES-E07 PRES-E08 PRES-J08 PRES-C08 PRES-C09 PRES-W06 PRES-W08] light/en/1440 named controls, modal keyboard and retained draft",
+ "attachment": "cover-controls-axe",
+ "geometry": null,
+ "engine": null,
+ "toolOptions": null,
+ "violationNodes": 0,
+ "incompleteNodes": 6,
+ "raw": {
+ "path": ".artifacts/phase7/p7-05/companies/final-columns/embedded/companies-named/003-cover-controls-axe.json",
+ "sha256": "ab2d077d7c39f3b5d8cfa71f371e2ec08fb174200621d74301649f4baa7a5ec3",
+ "bytes": 7574,
+ "hashPolicy": "Exact measured raw bytes"
+ },
+ "originalEmbeddedReport": {
+ "path": ".artifacts/phase7/p7-05/companies/final-columns/companies-named.json",
+ "sha256": "e17e178b7e626f2a0ee6b622f86b741dcfddfb46da0959f88a4cb37db89b4527",
+ "bytes": 296885,
+ "hashPolicy": "Exact measured raw bytes"
+ },
+ "context": null,
+ "rules": [
+ {
+ "kind": "incomplete",
+ "id": "color-contrast",
+ "nodes": 6
+ }
+ ]
+ },
+ {
+ "test": "[PRES-E07 PRES-E08 PRES-J08 PRES-C08 PRES-C09 PRES-W06 PRES-W08] light/en/1440 named controls, modal keyboard and retained draft",
+ "attachment": "prompt-axe",
+ "geometry": null,
+ "engine": null,
+ "toolOptions": null,
+ "violationNodes": 0,
+ "incompleteNodes": 2,
+ "raw": {
+ "path": ".artifacts/phase7/p7-05/companies/final-columns/embedded/companies-named/004-prompt-axe.json",
+ "sha256": "6506cc09950e7b1cb78c0a1cd5c97ec28bad0f9fca4a0b7963ff0e7dcdfad20a",
+ "bytes": 2749,
+ "hashPolicy": "Exact measured raw bytes"
+ },
+ "originalEmbeddedReport": {
+ "path": ".artifacts/phase7/p7-05/companies/final-columns/companies-named.json",
+ "sha256": "e17e178b7e626f2a0ee6b622f86b741dcfddfb46da0959f88a4cb37db89b4527",
+ "bytes": 296885,
+ "hashPolicy": "Exact measured raw bytes"
+ },
+ "context": null,
+ "rules": [
+ {
+ "kind": "incomplete",
+ "id": "color-contrast",
+ "nodes": 2
+ }
+ ]
+ },
+ {
+ "test": "[PRES-E07 PRES-E08 PRES-J08 PRES-C08 PRES-C09 PRES-W06 PRES-W08] light/en/1440 named controls, modal keyboard and retained draft",
+ "attachment": "confirm-axe",
+ "geometry": null,
+ "engine": null,
+ "toolOptions": null,
+ "violationNodes": 0,
+ "incompleteNodes": 2,
+ "raw": {
+ "path": ".artifacts/phase7/p7-05/companies/final-columns/embedded/companies-named/005-confirm-axe.json",
+ "sha256": "15ee543140cd0d42b7254759ebd4cb045644eca882248c7567e80788bccfa001",
+ "bytes": 2582,
+ "hashPolicy": "Exact measured raw bytes"
+ },
+ "originalEmbeddedReport": {
+ "path": ".artifacts/phase7/p7-05/companies/final-columns/companies-named.json",
+ "sha256": "e17e178b7e626f2a0ee6b622f86b741dcfddfb46da0959f88a4cb37db89b4527",
+ "bytes": 296885,
+ "hashPolicy": "Exact measured raw bytes"
+ },
+ "context": null,
+ "rules": [
+ {
+ "kind": "incomplete",
+ "id": "color-contrast",
+ "nodes": 2
+ }
+ ]
+ },
+ {
+ "test": "[PRES-E07 PRES-E08 PRES-J08 PRES-C08 PRES-C09 PRES-W06 PRES-W08] dark/nb/1440 named controls, modal keyboard and retained draft",
+ "attachment": "command-axe",
+ "geometry": null,
+ "engine": null,
+ "toolOptions": null,
+ "violationNodes": 0,
+ "incompleteNodes": 1,
+ "raw": {
+ "path": ".artifacts/phase7/p7-05/companies/final-columns/embedded/companies-named/006-command-axe.json",
+ "sha256": "9a090f86819f95f47779ce5872bf42014ea86fc0d68ab8dc038faac949a68979",
+ "bytes": 1565,
+ "hashPolicy": "Exact measured raw bytes"
+ },
+ "originalEmbeddedReport": {
+ "path": ".artifacts/phase7/p7-05/companies/final-columns/companies-named.json",
+ "sha256": "e17e178b7e626f2a0ee6b622f86b741dcfddfb46da0959f88a4cb37db89b4527",
+ "bytes": 296885,
+ "hashPolicy": "Exact measured raw bytes"
+ },
+ "context": null,
+ "rules": [
+ {
+ "kind": "incomplete",
+ "id": "aria-valid-attr-value",
+ "nodes": 1
+ }
+ ]
+ },
+ {
+ "test": "[PRES-E07 PRES-E08 PRES-J08 PRES-C08 PRES-C09 PRES-W06 PRES-W08] dark/nb/1440 named controls, modal keyboard and retained draft",
+ "attachment": "cv-controls-axe",
+ "geometry": null,
+ "engine": null,
+ "toolOptions": null,
+ "violationNodes": 0,
+ "incompleteNodes": 32,
+ "raw": {
+ "path": ".artifacts/phase7/p7-05/companies/final-columns/embedded/companies-named/007-cv-controls-axe.json",
+ "sha256": "a1c163d9e5af086f98bc32557add906946931b714409e0698a934a76c132b886",
+ "bytes": 40251,
+ "hashPolicy": "Exact measured raw bytes"
+ },
+ "originalEmbeddedReport": {
+ "path": ".artifacts/phase7/p7-05/companies/final-columns/companies-named.json",
+ "sha256": "e17e178b7e626f2a0ee6b622f86b741dcfddfb46da0959f88a4cb37db89b4527",
+ "bytes": 296885,
+ "hashPolicy": "Exact measured raw bytes"
+ },
+ "context": null,
+ "rules": [
+ {
+ "kind": "incomplete",
+ "id": "aria-hidden-focus",
+ "nodes": 1
+ },
+ {
+ "kind": "incomplete",
+ "id": "aria-prohibited-attr",
+ "nodes": 2
+ },
+ {
+ "kind": "incomplete",
+ "id": "color-contrast",
+ "nodes": 29
+ }
+ ]
+ },
+ {
+ "test": "[PRES-E07 PRES-E08 PRES-J08 PRES-C08 PRES-C09 PRES-W06 PRES-W08] dark/nb/1440 named controls, modal keyboard and retained draft",
+ "attachment": "cover-controls-axe",
+ "geometry": null,
+ "engine": null,
+ "toolOptions": null,
+ "violationNodes": 0,
+ "incompleteNodes": 6,
+ "raw": {
+ "path": ".artifacts/phase7/p7-05/companies/final-columns/embedded/companies-named/008-cover-controls-axe.json",
+ "sha256": "b3bb3539d3eb907469b34cdab359d0bb88dd6a7bcfbd84d085bbc66db93ee3a7",
+ "bytes": 7605,
+ "hashPolicy": "Exact measured raw bytes"
+ },
+ "originalEmbeddedReport": {
+ "path": ".artifacts/phase7/p7-05/companies/final-columns/companies-named.json",
+ "sha256": "e17e178b7e626f2a0ee6b622f86b741dcfddfb46da0959f88a4cb37db89b4527",
+ "bytes": 296885,
+ "hashPolicy": "Exact measured raw bytes"
+ },
+ "context": null,
+ "rules": [
+ {
+ "kind": "incomplete",
+ "id": "color-contrast",
+ "nodes": 6
+ }
+ ]
+ },
+ {
+ "test": "[PRES-E07 PRES-E08 PRES-J08 PRES-C08 PRES-C09 PRES-W06 PRES-W08] dark/nb/1440 named controls, modal keyboard and retained draft",
+ "attachment": "prompt-axe",
+ "geometry": null,
+ "engine": null,
+ "toolOptions": null,
+ "violationNodes": 0,
+ "incompleteNodes": 2,
+ "raw": {
+ "path": ".artifacts/phase7/p7-05/companies/final-columns/embedded/companies-named/009-prompt-axe.json",
+ "sha256": "6506cc09950e7b1cb78c0a1cd5c97ec28bad0f9fca4a0b7963ff0e7dcdfad20a",
+ "bytes": 2749,
+ "hashPolicy": "Exact measured raw bytes"
+ },
+ "originalEmbeddedReport": {
+ "path": ".artifacts/phase7/p7-05/companies/final-columns/companies-named.json",
+ "sha256": "e17e178b7e626f2a0ee6b622f86b741dcfddfb46da0959f88a4cb37db89b4527",
+ "bytes": 296885,
+ "hashPolicy": "Exact measured raw bytes"
+ },
+ "context": null,
+ "rules": [
+ {
+ "kind": "incomplete",
+ "id": "color-contrast",
+ "nodes": 2
+ }
+ ]
+ },
+ {
+ "test": "[PRES-E07 PRES-E08 PRES-J08 PRES-C08 PRES-C09 PRES-W06 PRES-W08] dark/nb/1440 named controls, modal keyboard and retained draft",
+ "attachment": "confirm-axe",
+ "geometry": null,
+ "engine": null,
+ "toolOptions": null,
+ "violationNodes": 0,
+ "incompleteNodes": 2,
+ "raw": {
+ "path": ".artifacts/phase7/p7-05/companies/final-columns/embedded/companies-named/010-confirm-axe.json",
+ "sha256": "15ee543140cd0d42b7254759ebd4cb045644eca882248c7567e80788bccfa001",
+ "bytes": 2582,
+ "hashPolicy": "Exact measured raw bytes"
+ },
+ "originalEmbeddedReport": {
+ "path": ".artifacts/phase7/p7-05/companies/final-columns/companies-named.json",
+ "sha256": "e17e178b7e626f2a0ee6b622f86b741dcfddfb46da0959f88a4cb37db89b4527",
+ "bytes": 296885,
+ "hashPolicy": "Exact measured raw bytes"
+ },
+ "context": null,
+ "rules": [
+ {
+ "kind": "incomplete",
+ "id": "color-contrast",
+ "nodes": 2
+ }
+ ]
+ },
+ {
+ "test": "[PRES-E07 PRES-E08 PRES-J08 PRES-C08 PRES-C09 PRES-W06 PRES-W08] light/nb/390 named controls, modal keyboard and retained draft",
+ "attachment": "command-axe",
+ "geometry": null,
+ "engine": null,
+ "toolOptions": null,
+ "violationNodes": 0,
+ "incompleteNodes": 1,
+ "raw": {
+ "path": ".artifacts/phase7/p7-05/companies/final-columns/embedded/companies-named/011-command-axe.json",
+ "sha256": "613aa95f75620ae3a76f9ffd882c765235a1d4f81b0afd8cd5cfd8dadaf5ad16",
+ "bytes": 1565,
+ "hashPolicy": "Exact measured raw bytes"
+ },
+ "originalEmbeddedReport": {
+ "path": ".artifacts/phase7/p7-05/companies/final-columns/companies-named.json",
+ "sha256": "e17e178b7e626f2a0ee6b622f86b741dcfddfb46da0959f88a4cb37db89b4527",
+ "bytes": 296885,
+ "hashPolicy": "Exact measured raw bytes"
+ },
+ "context": null,
+ "rules": [
+ {
+ "kind": "incomplete",
+ "id": "aria-valid-attr-value",
+ "nodes": 1
+ }
+ ]
+ },
+ {
+ "test": "[PRES-E07 PRES-E08 PRES-J08 PRES-C08 PRES-C09 PRES-W06 PRES-W08] light/nb/390 named controls, modal keyboard and retained draft",
+ "attachment": "cv-controls-axe",
+ "geometry": null,
+ "engine": null,
+ "toolOptions": null,
+ "violationNodes": 0,
+ "incompleteNodes": 26,
+ "raw": {
+ "path": ".artifacts/phase7/p7-05/companies/final-columns/embedded/companies-named/012-cv-controls-axe.json",
+ "sha256": "f38d861a25cf1372ba0cb6b8b3681c231e05c3ea07ad7c948db3c0f440c8171c",
+ "bytes": 31806,
+ "hashPolicy": "Exact measured raw bytes"
+ },
+ "originalEmbeddedReport": {
+ "path": ".artifacts/phase7/p7-05/companies/final-columns/companies-named.json",
+ "sha256": "e17e178b7e626f2a0ee6b622f86b741dcfddfb46da0959f88a4cb37db89b4527",
+ "bytes": 296885,
+ "hashPolicy": "Exact measured raw bytes"
+ },
+ "context": null,
+ "rules": [
+ {
+ "kind": "incomplete",
+ "id": "aria-hidden-focus",
+ "nodes": 1
+ },
+ {
+ "kind": "incomplete",
+ "id": "aria-prohibited-attr",
+ "nodes": 2
+ },
+ {
+ "kind": "incomplete",
+ "id": "color-contrast",
+ "nodes": 23
+ }
+ ]
+ },
+ {
+ "test": "[PRES-E07 PRES-E08 PRES-J08 PRES-C08 PRES-C09 PRES-W06 PRES-W08] light/nb/390 named controls, modal keyboard and retained draft",
+ "attachment": "cover-controls-axe",
+ "geometry": null,
+ "engine": null,
+ "toolOptions": null,
+ "violationNodes": 0,
+ "incompleteNodes": 10,
+ "raw": {
+ "path": ".artifacts/phase7/p7-05/companies/final-columns/embedded/companies-named/013-cover-controls-axe.json",
+ "sha256": "864ded43358c8a5cd7d0a380f4533f9b60a61702bacf511a87f4e93242f82117",
+ "bytes": 12902,
+ "hashPolicy": "Exact measured raw bytes"
+ },
+ "originalEmbeddedReport": {
+ "path": ".artifacts/phase7/p7-05/companies/final-columns/companies-named.json",
+ "sha256": "e17e178b7e626f2a0ee6b622f86b741dcfddfb46da0959f88a4cb37db89b4527",
+ "bytes": 296885,
+ "hashPolicy": "Exact measured raw bytes"
+ },
+ "context": null,
+ "rules": [
+ {
+ "kind": "incomplete",
+ "id": "color-contrast",
+ "nodes": 10
+ }
+ ]
+ },
+ {
+ "test": "[PRES-E07 PRES-E08 PRES-J08 PRES-C08 PRES-C09 PRES-W06 PRES-W08] light/nb/390 named controls, modal keyboard and retained draft",
+ "attachment": "prompt-axe",
+ "geometry": null,
+ "engine": null,
+ "toolOptions": null,
+ "violationNodes": 0,
+ "incompleteNodes": 2,
+ "raw": {
+ "path": ".artifacts/phase7/p7-05/companies/final-columns/embedded/companies-named/014-prompt-axe.json",
+ "sha256": "5acee1cf1e0c295407825d5823cec31d726f9334f81e501f9e4a1b8b305c8197",
+ "bytes": 2749,
+ "hashPolicy": "Exact measured raw bytes"
+ },
+ "originalEmbeddedReport": {
+ "path": ".artifacts/phase7/p7-05/companies/final-columns/companies-named.json",
+ "sha256": "e17e178b7e626f2a0ee6b622f86b741dcfddfb46da0959f88a4cb37db89b4527",
+ "bytes": 296885,
+ "hashPolicy": "Exact measured raw bytes"
+ },
+ "context": null,
+ "rules": [
+ {
+ "kind": "incomplete",
+ "id": "color-contrast",
+ "nodes": 2
+ }
+ ]
+ },
+ {
+ "test": "[PRES-E07 PRES-E08 PRES-J08 PRES-C08 PRES-C09 PRES-W06 PRES-W08] light/nb/390 named controls, modal keyboard and retained draft",
+ "attachment": "confirm-axe",
+ "geometry": null,
+ "engine": null,
+ "toolOptions": null,
+ "violationNodes": 0,
+ "incompleteNodes": 2,
+ "raw": {
+ "path": ".artifacts/phase7/p7-05/companies/final-columns/embedded/companies-named/015-confirm-axe.json",
+ "sha256": "15ee543140cd0d42b7254759ebd4cb045644eca882248c7567e80788bccfa001",
+ "bytes": 2582,
+ "hashPolicy": "Exact measured raw bytes"
+ },
+ "originalEmbeddedReport": {
+ "path": ".artifacts/phase7/p7-05/companies/final-columns/companies-named.json",
+ "sha256": "e17e178b7e626f2a0ee6b622f86b741dcfddfb46da0959f88a4cb37db89b4527",
+ "bytes": 296885,
+ "hashPolicy": "Exact measured raw bytes"
+ },
+ "context": null,
+ "rules": [
+ {
+ "kind": "incomplete",
+ "id": "color-contrast",
+ "nodes": 2
+ }
+ ]
+ },
+ {
+ "test": "[PRES-E07 PRES-E08 PRES-J08 PRES-C08 PRES-C09 PRES-W06 PRES-W08] dark/en/390 named controls, modal keyboard and retained draft",
+ "attachment": "command-axe",
+ "geometry": null,
+ "engine": null,
+ "toolOptions": null,
+ "violationNodes": 0,
+ "incompleteNodes": 1,
+ "raw": {
+ "path": ".artifacts/phase7/p7-05/companies/final-columns/embedded/companies-named/016-command-axe.json",
+ "sha256": "a9cc67f434b1dd0cb6ad3c05f8185b51b4d33b779dcdcbd16a12c97ecf99c1a3",
+ "bytes": 1567,
+ "hashPolicy": "Exact measured raw bytes"
+ },
+ "originalEmbeddedReport": {
+ "path": ".artifacts/phase7/p7-05/companies/final-columns/companies-named.json",
+ "sha256": "e17e178b7e626f2a0ee6b622f86b741dcfddfb46da0959f88a4cb37db89b4527",
+ "bytes": 296885,
+ "hashPolicy": "Exact measured raw bytes"
+ },
+ "context": null,
+ "rules": [
+ {
+ "kind": "incomplete",
+ "id": "aria-valid-attr-value",
+ "nodes": 1
+ }
+ ]
+ },
+ {
+ "test": "[PRES-E07 PRES-E08 PRES-J08 PRES-C08 PRES-C09 PRES-W06 PRES-W08] dark/en/390 named controls, modal keyboard and retained draft",
+ "attachment": "cv-controls-axe",
+ "geometry": null,
+ "engine": null,
+ "toolOptions": null,
+ "violationNodes": 0,
+ "incompleteNodes": 26,
+ "raw": {
+ "path": ".artifacts/phase7/p7-05/companies/final-columns/embedded/companies-named/017-cv-controls-axe.json",
+ "sha256": "7bac4c2e5468606c948de093f278eb25412424a119d8d1c17b86d52d90254ab5",
+ "bytes": 31720,
+ "hashPolicy": "Exact measured raw bytes"
+ },
+ "originalEmbeddedReport": {
+ "path": ".artifacts/phase7/p7-05/companies/final-columns/companies-named.json",
+ "sha256": "e17e178b7e626f2a0ee6b622f86b741dcfddfb46da0959f88a4cb37db89b4527",
+ "bytes": 296885,
+ "hashPolicy": "Exact measured raw bytes"
+ },
+ "context": null,
+ "rules": [
+ {
+ "kind": "incomplete",
+ "id": "aria-hidden-focus",
+ "nodes": 1
+ },
+ {
+ "kind": "incomplete",
+ "id": "aria-prohibited-attr",
+ "nodes": 2
+ },
+ {
+ "kind": "incomplete",
+ "id": "color-contrast",
+ "nodes": 23
+ }
+ ]
+ },
+ {
+ "test": "[PRES-E07 PRES-E08 PRES-J08 PRES-C08 PRES-C09 PRES-W06 PRES-W08] dark/en/390 named controls, modal keyboard and retained draft",
+ "attachment": "cover-controls-axe",
+ "geometry": null,
+ "engine": null,
+ "toolOptions": null,
+ "violationNodes": 0,
+ "incompleteNodes": 10,
+ "raw": {
+ "path": ".artifacts/phase7/p7-05/companies/final-columns/embedded/companies-named/018-cover-controls-axe.json",
+ "sha256": "c6b7bf9a877c6f170f60404fb5f49618d5c7be3200a364862c21bfbfac574192",
+ "bytes": 12868,
+ "hashPolicy": "Exact measured raw bytes"
+ },
+ "originalEmbeddedReport": {
+ "path": ".artifacts/phase7/p7-05/companies/final-columns/companies-named.json",
+ "sha256": "e17e178b7e626f2a0ee6b622f86b741dcfddfb46da0959f88a4cb37db89b4527",
+ "bytes": 296885,
+ "hashPolicy": "Exact measured raw bytes"
+ },
+ "context": null,
+ "rules": [
+ {
+ "kind": "incomplete",
+ "id": "color-contrast",
+ "nodes": 10
+ }
+ ]
+ },
+ {
+ "test": "[PRES-E07 PRES-E08 PRES-J08 PRES-C08 PRES-C09 PRES-W06 PRES-W08] dark/en/390 named controls, modal keyboard and retained draft",
+ "attachment": "prompt-axe",
+ "geometry": null,
+ "engine": null,
+ "toolOptions": null,
+ "violationNodes": 0,
+ "incompleteNodes": 2,
+ "raw": {
+ "path": ".artifacts/phase7/p7-05/companies/final-columns/embedded/companies-named/019-prompt-axe.json",
+ "sha256": "4c866a4344dce8a5e931357b4fda2ae25940b32983cf852af31fdfe34e0f929e",
+ "bytes": 2749,
+ "hashPolicy": "Exact measured raw bytes"
+ },
+ "originalEmbeddedReport": {
+ "path": ".artifacts/phase7/p7-05/companies/final-columns/companies-named.json",
+ "sha256": "e17e178b7e626f2a0ee6b622f86b741dcfddfb46da0959f88a4cb37db89b4527",
+ "bytes": 296885,
+ "hashPolicy": "Exact measured raw bytes"
+ },
+ "context": null,
+ "rules": [
+ {
+ "kind": "incomplete",
+ "id": "color-contrast",
+ "nodes": 2
+ }
+ ]
+ },
+ {
+ "test": "[PRES-E07 PRES-E08 PRES-J08 PRES-C08 PRES-C09 PRES-W06 PRES-W08] dark/en/390 named controls, modal keyboard and retained draft",
+ "attachment": "confirm-axe",
+ "geometry": null,
+ "engine": null,
+ "toolOptions": null,
+ "violationNodes": 0,
+ "incompleteNodes": 2,
+ "raw": {
+ "path": ".artifacts/phase7/p7-05/companies/final-columns/embedded/companies-named/020-confirm-axe.json",
+ "sha256": "15ee543140cd0d42b7254759ebd4cb045644eca882248c7567e80788bccfa001",
+ "bytes": 2582,
+ "hashPolicy": "Exact measured raw bytes"
+ },
+ "originalEmbeddedReport": {
+ "path": ".artifacts/phase7/p7-05/companies/final-columns/companies-named.json",
+ "sha256": "e17e178b7e626f2a0ee6b622f86b741dcfddfb46da0959f88a4cb37db89b4527",
+ "bytes": 296885,
+ "hashPolicy": "Exact measured raw bytes"
+ },
+ "context": null,
+ "rules": [
+ {
+ "kind": "incomplete",
+ "id": "color-contrast",
+ "nodes": 2
+ }
+ ]
+ }
+ ],
+ "violationNodes": 0,
+ "incompleteNodes": 163,
+ "checkContexts": [
+ {
+ "kind": "incomplete",
+ "rule": "aria-valid-attr-value",
+ "check": "aria-valid-attr-value",
+ "message": "ARIA attribute element ID does not exist on the page or is a descendant of a different shadow DOM tree: aria-labelledby=\"_r_k_\"",
+ "occurrences": 2,
+ "representativeContexts": [
+ {
+ "attachment": "command-axe",
+ "target": [
+ ".MuiPaper-elevation24"
+ ],
+ "data": {
+ "messageKey": "noIdShadow",
+ "needsReview": "aria-labelledby=\"_r_k_\""
+ },
+ "relatedNodes": []
+ },
+ {
+ "attachment": "command-axe",
+ "target": [
+ ".MuiPaper-elevation24"
+ ],
+ "data": {
+ "messageKey": "noIdShadow",
+ "needsReview": "aria-labelledby=\"_r_k_\""
+ },
+ "relatedNodes": []
+ }
+ ]
+ },
+ {
+ "kind": "incomplete",
+ "rule": "aria-hidden-focus",
+ "check": "focusable-modal-open",
+ "message": "Check that focusable elements are not tabbable in the current state",
+ "occurrences": 4,
+ "representativeContexts": [
+ {
+ "attachment": "cv-controls-axe",
+ "target": [
+ "#menu-"
+ ],
+ "data": null,
+ "relatedNodes": [
+ {
+ "html": "
Arial",
+ "target": [
+ "li[data-value=\"Arial, Helvetica, sans-serif\"]"
+ ]
+ }
+ ]
+ },
+ {
+ "attachment": "cv-controls-axe",
+ "target": [
+ "#menu-"
+ ],
+ "data": null,
+ "relatedNodes": [
+ {
+ "html": "
Arial",
+ "target": [
+ "li[data-value=\"Arial, Helvetica, sans-serif\"]"
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "kind": "incomplete",
+ "rule": "aria-prohibited-attr",
+ "check": "aria-prohibited-attr",
+ "message": "aria-label attribute is not well supported on a span with no valid role attribute.",
+ "occurrences": 8,
+ "representativeContexts": [
+ {
+ "attachment": "cv-controls-axe",
+ "target": [
+ "span[aria-label=\"Undo\"]"
+ ],
+ "data": {
+ "role": null,
+ "nodeName": "span",
+ "messageKey": "noRoleSingular",
+ "prohibited": [
+ "aria-label"
+ ]
+ },
+ "relatedNodes": []
+ },
+ {
+ "attachment": "cv-controls-axe",
+ "target": [
+ "span[aria-label=\"Redo\"]"
+ ],
+ "data": {
+ "role": null,
+ "nodeName": "span",
+ "messageKey": "noRoleSingular",
+ "prohibited": [
+ "aria-label"
+ ]
+ },
+ "relatedNodes": []
+ }
+ ]
+ },
+ {
+ "kind": "incomplete",
+ "rule": "color-contrast",
+ "check": "color-contrast",
+ "message": "Element content is too short to determine if it is actual text content",
+ "occurrences": 8,
+ "representativeContexts": [
+ {
+ "attachment": "cv-controls-axe",
+ "target": [
+ ".MuiBadge-badge"
+ ],
+ "data": {
+ "contrastRatio": 0,
+ "fontSize": "9.0pt (12px)",
+ "fontWeight": "normal",
+ "messageKey": "shortTextContent",
+ "expectedContrastRatio": "4.5:1"
+ },
+ "relatedNodes": []
+ },
+ {
+ "attachment": "cover-controls-axe",
+ "target": [
+ ".MuiBadge-badge"
+ ],
+ "data": {
+ "contrastRatio": 0,
+ "fontSize": "9.0pt (12px)",
+ "fontWeight": "normal",
+ "messageKey": "shortTextContent",
+ "expectedContrastRatio": "4.5:1"
+ },
+ "relatedNodes": []
+ }
+ ]
+ },
+ {
+ "kind": "incomplete",
+ "rule": "color-contrast",
+ "check": "color-contrast",
+ "message": "Element's background color could not be determined because it is overlapped by another element",
+ "occurrences": 124,
+ "representativeContexts": [
+ {
+ "attachment": "cv-controls-axe",
+ "target": [
+ "#cv-studio-tab-2"
+ ],
+ "data": {
+ "contrastRatio": 0,
+ "fontSize": "10.5pt (14px)",
+ "fontWeight": "normal",
+ "messageKey": "bgOverlap",
+ "expectedContrastRatio": "4.5:1"
+ },
+ "relatedNodes": []
+ },
+ {
+ "attachment": "cv-controls-axe",
+ "target": [
+ ".MuiSelect-select.MuiSelect-outlined[aria-labelledby=\"_r_3k_\"]"
+ ],
+ "data": {
+ "contrastRatio": 0,
+ "fontSize": "10.5pt (14px)",
+ "fontWeight": "normal",
+ "messageKey": "bgOverlap",
+ "expectedContrastRatio": "4.5:1"
+ },
+ "relatedNodes": []
+ }
+ ]
+ },
+ {
+ "kind": "incomplete",
+ "rule": "color-contrast",
+ "check": "color-contrast",
+ "message": "Element's background color could not be determined because it's partially obscured by another element",
+ "occurrences": 15,
+ "representativeContexts": [
+ {
+ "attachment": "cv-controls-axe",
+ "target": [
+ "iframe[title=\"CV preview page 1\"]",
+ "h1"
+ ],
+ "data": {
+ "contrastRatio": 0,
+ "fontSize": "24.0pt (32px)",
+ "fontWeight": "bold",
+ "messageKey": "elmPartiallyObscured",
+ "expectedContrastRatio": "3:1"
+ },
+ "relatedNodes": [
+ {
+ "html": "",
+ "target": [
+ "iframe[title=\"CV preview page 1\"]",
+ "header"
+ ]
+ }
+ ]
+ },
+ {
+ "attachment": "cv-controls-axe",
+ "target": [
+ ".css-7wzbvf > h2"
+ ],
+ "data": {
+ "contrastRatio": 0,
+ "fontSize": "9.8pt (13px)",
+ "fontWeight": "normal",
+ "messageKey": "elmPartiallyObscured",
+ "expectedContrastRatio": "4.5:1"
+ },
+ "relatedNodes": [
+ {
+ "html": "
",
+ "target": [
+ ".css-9ib07s"
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "kind": "incomplete",
+ "rule": "aria-valid-attr-value",
+ "check": "aria-valid-attr-value",
+ "message": "ARIA attribute element ID does not exist on the page or is a descendant of a different shadow DOM tree: aria-labelledby=\"_r_l_\"",
+ "occurrences": 2,
+ "representativeContexts": [
+ {
+ "attachment": "command-axe",
+ "target": [
+ ".MuiPaper-elevation24"
+ ],
+ "data": {
+ "messageKey": "noIdShadow",
+ "needsReview": "aria-labelledby=\"_r_l_\""
+ },
+ "relatedNodes": []
+ },
+ {
+ "attachment": "command-axe",
+ "target": [
+ ".MuiPaper-elevation24"
+ ],
+ "data": {
+ "messageKey": "noIdShadow",
+ "needsReview": "aria-labelledby=\"_r_l_\""
+ },
+ "relatedNodes": []
+ }
+ ]
+ }
+ ],
+ "qualification": "Every violation/incomplete category counted; complete node/check vectors remain exact raw report and extracted JSON. Parent contextual triage pending."
+ }
+ }
+ },
+ "gates": [
+ {
+ "command": {
+ "name": "typecheck",
+ "command": [
+ "rtk",
+ "proxy",
+ "npm.cmd",
+ "run",
+ "typecheck"
+ ],
+ "startedUtc": "2026-10-13T06:45:30.187364+00:00",
+ "finishedUtc": "2026-10-13T06:45:39.658515+00:00",
+ "exitCode": 0
+ },
+ "commandRaw": {
+ "path": ".artifacts/phase7/p7-05/companies/final-columns/gates/typecheck-command.json",
+ "sha256": "12a4a9482653f9917d9601d96cfb43a69db22b475755aa7be7f2b4d7787bf901",
+ "bytes": 247,
+ "hashPolicy": "Exact measured raw bytes"
+ },
+ "logRaw": {
+ "path": ".artifacts/phase7/p7-05/companies/final-columns/gates/typecheck.log",
+ "sha256": "89b0ec97b537eca6560c13ca2dfea9e3a97dd1bb35580c4ff39d2462db25e080",
+ "bytes": 148,
+ "hashPolicy": "Exact measured raw bytes"
+ }
+ },
+ {
+ "command": {
+ "name": "lint",
+ "command": [
+ "rtk",
+ "proxy",
+ "npm.cmd",
+ "run",
+ "lint"
+ ],
+ "startedUtc": "2026-10-13T06:45:39.661518+00:00",
+ "finishedUtc": "2026-10-13T06:45:57.255298+00:00",
+ "exitCode": 0
+ },
+ "commandRaw": {
+ "path": ".artifacts/phase7/p7-05/companies/final-columns/gates/lint-command.json",
+ "sha256": "6330417266ff4cfaa4dd70d3519eedd541434ff6c214f2e580b798b8409bf795",
+ "bytes": 237,
+ "hashPolicy": "Exact measured raw bytes"
+ },
+ "logRaw": {
+ "path": ".artifacts/phase7/p7-05/companies/final-columns/gates/lint.log",
+ "sha256": "b040dbeca9b4adb3f6f4c25ac49de0d37e921375b2d9b57e87b542a10f599913",
+ "bytes": 136,
+ "hashPolicy": "Exact measured raw bytes"
+ }
+ },
+ {
+ "command": {
+ "name": "browser-typecheck",
+ "command": [
+ "rtk",
+ "proxy",
+ "npm.cmd",
+ "exec",
+ "tsc",
+ "--",
+ "--project",
+ "../.artifacts/phase7/e2e-tsconfig.json",
+ "--noEmit"
+ ],
+ "startedUtc": "2026-10-13T06:45:57.257822+00:00",
+ "finishedUtc": "2026-10-13T06:46:01.172290+00:00",
+ "exitCode": 0
+ },
+ "commandRaw": {
+ "path": ".artifacts/phase7/p7-05/companies/final-columns/gates/browser-typecheck-command.json",
+ "sha256": "8a57906379c60a2d64863678fa828f352baa67769b21058fc046e6168b3e4279",
+ "bytes": 343,
+ "hashPolicy": "Exact measured raw bytes"
+ },
+ "logRaw": {
+ "path": ".artifacts/phase7/p7-05/companies/final-columns/gates/browser-typecheck.log",
+ "sha256": "d2993bc68ad9a63f711c8f5473155e2ba00fdcd461220cc3849185f2f9ad93e6",
+ "bytes": 78,
+ "hashPolicy": "Exact measured raw bytes"
+ }
+ },
+ {
+ "command": {
+ "name": "build",
+ "command": [
+ "rtk",
+ "proxy",
+ "npm.cmd",
+ "run",
+ "build"
+ ],
+ "startedUtc": "2026-10-13T06:46:01.173780+00:00",
+ "finishedUtc": "2026-10-13T06:46:07.920148+00:00",
+ "exitCode": 0
+ },
+ "commandRaw": {
+ "path": ".artifacts/phase7/p7-05/companies/final-columns/gates/build-command.json",
+ "sha256": "ef87421d7427f7252babe5330cbd14fad49c1b5ded3facda890020c1941d1535",
+ "bytes": 239,
+ "hashPolicy": "Exact measured raw bytes"
+ },
+ "logRaw": {
+ "path": ".artifacts/phase7/p7-05/companies/final-columns/gates/build.log",
+ "sha256": "60a16036389171bc9c8b1860d3f3077430582e57ce73bafd9432762f3916c874",
+ "bytes": 670,
+ "hashPolicy": "Exact measured raw bytes"
+ }
+ }
+ ],
+ "sourceSnapshots": [
+ {
+ "originalSourcePath": "job-tracker-ui/src/components/CompaniesTable.tsx",
+ "raw": {
+ "path": ".artifacts/phase7/p7-05/companies/final-columns/source/job-tracker-ui/src/components/CompaniesTable.tsx",
+ "sha256": "fbff1c14f98472d5f6b208bd669dd0fb947807fc8dec1096b9850927f2a38c0c",
+ "bytes": 21117,
+ "hashPolicy": "Exact measured raw bytes"
+ },
+ "canonicalLf": {
+ "path": ".artifacts/phase7/p7-05/companies/final-columns/source/job-tracker-ui/src/components/CompaniesTable.tsx",
+ "sha256": "0065acb28866cea27fa2165ec248eef51109373b0c2c0faae0ae12fa3235ba1f",
+ "bytes": 20708,
+ "hashPolicy": "UTF-8 text with CRLF normalized to LF"
+ },
+ "qualification": "Current candidate snapshot at collection time; parent must qualify unchanged as-run source before acceptance."
+ },
+ {
+ "originalSourcePath": "job-tracker-ui/src/companies-relationship.test.tsx",
+ "raw": {
+ "path": ".artifacts/phase7/p7-05/companies/final-columns/source/job-tracker-ui/src/companies-relationship.test.tsx",
+ "sha256": "0471d3def86afbabdd683ab98d99fe07fe59f1bdc23767b916e087716117202f",
+ "bytes": 14947,
+ "hashPolicy": "Exact measured raw bytes"
+ },
+ "canonicalLf": {
+ "path": ".artifacts/phase7/p7-05/companies/final-columns/source/job-tracker-ui/src/companies-relationship.test.tsx",
+ "sha256": "4a4e1b4315ee73e01dd64c4032e5dd78245e0f0ec27e40359e1dcac32d3cb470",
+ "bytes": 14716,
+ "hashPolicy": "UTF-8 text with CRLF normalized to LF"
+ },
+ "qualification": "Current candidate snapshot at collection time; parent must qualify unchanged as-run source before acceptance."
+ },
+ {
+ "originalSourcePath": "job-tracker-ui/src/locale-fetch-stability.test.tsx",
+ "raw": {
+ "path": ".artifacts/phase7/p7-05/companies/final-columns/source/job-tracker-ui/src/locale-fetch-stability.test.tsx",
+ "sha256": "e647fcd387c54ffd50f45f1f8ae3f4133a2858fd0594ec9c0df51ce328a95814",
+ "bytes": 1539,
+ "hashPolicy": "Exact measured raw bytes"
+ },
+ "canonicalLf": {
+ "path": ".artifacts/phase7/p7-05/companies/final-columns/source/job-tracker-ui/src/locale-fetch-stability.test.tsx",
+ "sha256": "e647fcd387c54ffd50f45f1f8ae3f4133a2858fd0594ec9c0df51ce328a95814",
+ "bytes": 1539,
+ "hashPolicy": "UTF-8 text with CRLF normalized to LF"
+ },
+ "qualification": "Current candidate snapshot at collection time; parent must qualify unchanged as-run source before acceptance."
+ },
+ {
+ "originalSourcePath": "job-tracker-ui/e2e/phase7-companies.spec.ts",
+ "raw": {
+ "path": ".artifacts/phase7/p7-05/companies/final-columns/source/job-tracker-ui/e2e/phase7-companies.spec.ts",
+ "sha256": "44937bc1e0471e3ce76e947b3b462b818701a93ccfc8f746632c640dec4a0f6a",
+ "bytes": 47943,
+ "hashPolicy": "Exact measured raw bytes"
+ },
+ "canonicalLf": {
+ "path": ".artifacts/phase7/p7-05/companies/final-columns/source/job-tracker-ui/e2e/phase7-companies.spec.ts",
+ "sha256": "44937bc1e0471e3ce76e947b3b462b818701a93ccfc8f746632c640dec4a0f6a",
+ "bytes": 47943,
+ "hashPolicy": "UTF-8 text with CRLF normalized to LF"
+ },
+ "qualification": "Current candidate snapshot at collection time; parent must qualify unchanged as-run source before acceptance."
+ },
+ {
+ "originalSourcePath": "job-tracker-ui/src/i18n/translations.ts",
+ "raw": {
+ "path": ".artifacts/phase7/p7-05/companies/final-columns/source/job-tracker-ui/src/i18n/translations.ts",
+ "sha256": "ada83bac9fb2dd9d75c5125515d4118ed8b5c219738c0a5ce5c0ced07a5aef90",
+ "bytes": 320130,
+ "hashPolicy": "Exact measured raw bytes"
+ },
+ "canonicalLf": {
+ "path": ".artifacts/phase7/p7-05/companies/final-columns/source/job-tracker-ui/src/i18n/translations.ts",
+ "sha256": "e71fcb064e0d04f944d31076a5b51b98671c7711af280a0d35b17f325d321bb3",
+ "bytes": 314840,
+ "hashPolicy": "UTF-8 text with CRLF normalized to LF"
+ },
+ "qualification": "Current candidate snapshot at collection time; parent must qualify unchanged as-run source before acceptance."
+ },
+ {
+ "originalSourcePath": "job-tracker-ui/src/loaded-theme-states.test.tsx",
+ "raw": {
+ "path": ".artifacts/phase7/p7-05/companies/final-columns/source/job-tracker-ui/src/loaded-theme-states.test.tsx",
+ "sha256": "a54e28943c2219842fdb5066b95490c4663568b2adbfcb02cb1984c62e9fe6a3",
+ "bytes": 2953,
+ "hashPolicy": "Exact measured raw bytes"
+ },
+ "canonicalLf": {
+ "path": ".artifacts/phase7/p7-05/companies/final-columns/source/job-tracker-ui/src/loaded-theme-states.test.tsx",
+ "sha256": "a54e28943c2219842fdb5066b95490c4663568b2adbfcb02cb1984c62e9fe6a3",
+ "bytes": 2953,
+ "hashPolicy": "UTF-8 text with CRLF normalized to LF"
+ },
+ "qualification": "Current candidate snapshot at collection time; parent must qualify unchanged as-run source before acceptance."
+ },
+ {
+ "originalSourcePath": "job-tracker-ui/src/pipeline.test.ts",
+ "raw": {
+ "path": ".artifacts/phase7/p7-05/companies/final-columns/source/job-tracker-ui/src/pipeline.test.ts",
+ "sha256": "9f49b69ab2039abaa71629ba24f300273f8ff8c70ef0786b998ee6c547f59bdd",
+ "bytes": 4490,
+ "hashPolicy": "Exact measured raw bytes"
+ },
+ "canonicalLf": {
+ "path": ".artifacts/phase7/p7-05/companies/final-columns/source/job-tracker-ui/src/pipeline.test.ts",
+ "sha256": "fe49e18750109c4da6668978dbe33fb613441e373cec6ad61803c124c7fb122d",
+ "bytes": 4381,
+ "hashPolicy": "UTF-8 text with CRLF normalized to LF"
+ },
+ "qualification": "Current candidate snapshot at collection time; parent must qualify unchanged as-run source before acceptance."
+ },
+ {
+ "originalSourcePath": "job-tracker-ui/e2e/responsive-accessibility-audit.spec.ts",
+ "raw": {
+ "path": ".artifacts/phase7/p7-05/companies/final-columns/source/job-tracker-ui/e2e/responsive-accessibility-audit.spec.ts",
+ "sha256": "b69420b00c516855b4664807f2ceb174c004f1491a77bd4a2670c7dfee47a026",
+ "bytes": 59427,
+ "hashPolicy": "Exact measured raw bytes"
+ },
+ "canonicalLf": {
+ "path": ".artifacts/phase7/p7-05/companies/final-columns/source/job-tracker-ui/e2e/responsive-accessibility-audit.spec.ts",
+ "sha256": "b69420b00c516855b4664807f2ceb174c004f1491a77bd4a2670c7dfee47a026",
+ "bytes": 59427,
+ "hashPolicy": "UTF-8 text with CRLF normalized to LF"
+ },
+ "qualification": "Current candidate snapshot at collection time; parent must qualify unchanged as-run source before acceptance."
+ },
+ {
+ "originalSourcePath": "job-tracker-ui/e2e/named-controls-modal-state.spec.ts",
+ "raw": {
+ "path": ".artifacts/phase7/p7-05/companies/final-columns/source/job-tracker-ui/e2e/named-controls-modal-state.spec.ts",
+ "sha256": "e7f2b17cf76d2e22e657b513261a7c47c60117fdc9c66a5402db1f0c87256fff",
+ "bytes": 13083,
+ "hashPolicy": "Exact measured raw bytes"
+ },
+ "canonicalLf": {
+ "path": ".artifacts/phase7/p7-05/companies/final-columns/source/job-tracker-ui/e2e/named-controls-modal-state.spec.ts",
+ "sha256": "0249f46ae51f2cf2dec5bd0ea97010dce1460fb35397edb94222b0e2da3d3848",
+ "bytes": 12898,
+ "hashPolicy": "UTF-8 text with CRLF normalized to LF"
+ },
+ "qualification": "Current candidate snapshot at collection time; parent must qualify unchanged as-run source before acceptance."
+ }
+ ],
+ "buildId": "xGOe21zkLLWUJyAGIwn7w",
+ "export": {
+ "count": 74,
+ "sha256": "706c2bb89bd5dd818c7950443c4f9e61293d125e7fd204eca25d4e91b1286933",
+ "policy": "Windows Path sort; original output path + TAB + exact raw file SHA + LF"
+ },
+ "visualReview": {
+ "reviewed": 20,
+ "retained": 50,
+ "images": [
+ {
+ "raw": {
+ "path": ".artifacts/phase7/p7-05/companies/final-columns/embedded/companies/002-p7-companies-en-light-1440-saved-reloaded.png",
+ "sha256": "4fc1d7cd00f5838f9eca6b2795a816ae8546ab13141511c5c8666efa8be30528",
+ "bytes": 57605,
+ "hashPolicy": "Exact measured raw bytes"
+ },
+ "originalEmbeddedReport": {
+ "path": ".artifacts/phase7/p7-05/companies/final-columns/companies.json",
+ "sha256": "33b99b07dd6285380c201faf629785fdabfa8f736325a06335864c770b95b67a",
+ "bytes": 1486659,
+ "hashPolicy": "Exact measured raw bytes"
+ },
+ "lane": "companies",
+ "test": "[P7-05 Companies] nine-field save persists and owned relationship projects latest20 excluding deleted jobs",
+ "name": "p7-companies-en-light-1440-saved-reloaded",
+ "manualReview": "ACTUALLY_DISPLAYED_AND_REVIEWED_BY_PARENT"
+ },
+ {
+ "raw": {
+ "path": ".artifacts/phase7/p7-05/companies/final-columns/embedded/companies/004-p7-companies-en-light-1440-latest20-owned-relationship.png",
+ "sha256": "775278e9e9a39a36dca42e86572f9183d0cbb694f511b78e88616ac48f3e48a8",
+ "bytes": 92400,
+ "hashPolicy": "Exact measured raw bytes"
+ },
+ "originalEmbeddedReport": {
+ "path": ".artifacts/phase7/p7-05/companies/final-columns/companies.json",
+ "sha256": "33b99b07dd6285380c201faf629785fdabfa8f736325a06335864c770b95b67a",
+ "bytes": 1486659,
+ "hashPolicy": "Exact measured raw bytes"
+ },
+ "lane": "companies",
+ "test": "[P7-05 Companies] nine-field save persists and owned relationship projects latest20 excluding deleted jobs",
+ "name": "p7-companies-en-light-1440-latest20-owned-relationship",
+ "manualReview": "ACTUALLY_DISPLAYED_AND_REVIEWED_BY_PARENT"
+ },
+ {
+ "raw": {
+ "path": ".artifacts/phase7/p7-05/companies/final-columns/embedded/companies/009-p7-companies-nb-dark-360-held-save.png",
+ "sha256": "a9086b8364022e178d8d651d302ee949522184433d0938fa3321d07658f45022",
+ "bytes": 29762,
+ "hashPolicy": "Exact measured raw bytes"
+ },
+ "originalEmbeddedReport": {
+ "path": ".artifacts/phase7/p7-05/companies/final-columns/companies.json",
+ "sha256": "33b99b07dd6285380c201faf629785fdabfa8f736325a06335864c770b95b67a",
+ "bytes": 1486659,
+ "hashPolicy": "Exact measured raw bytes"
+ },
+ "lane": "companies",
+ "test": "[P7-05 Companies] held save locks one snapshot, retains a failed draft and recovers through its original Save",
+ "name": "p7-companies-nb-dark-360-held-save",
+ "manualReview": "ACTUALLY_DISPLAYED_AND_REVIEWED_BY_PARENT"
+ },
+ {
+ "raw": {
+ "path": ".artifacts/phase7/p7-05/companies/final-columns/embedded/companies/011-p7-companies-nb-dark-360-failed-save-durable-draft.png",
+ "sha256": "8e2a969c7a5006d34145248a673f7dda961ff115ee8c5453996cfaff1c68e33f",
+ "bytes": 33713,
+ "hashPolicy": "Exact measured raw bytes"
+ },
+ "originalEmbeddedReport": {
+ "path": ".artifacts/phase7/p7-05/companies/final-columns/companies.json",
+ "sha256": "33b99b07dd6285380c201faf629785fdabfa8f736325a06335864c770b95b67a",
+ "bytes": 1486659,
+ "hashPolicy": "Exact measured raw bytes"
+ },
+ "lane": "companies",
+ "test": "[P7-05 Companies] held save locks one snapshot, retains a failed draft and recovers through its original Save",
+ "name": "p7-companies-nb-dark-360-failed-save-durable-draft",
+ "manualReview": "ACTUALLY_DISPLAYED_AND_REVIEWED_BY_PARENT"
+ },
+ {
+ "raw": {
+ "path": ".artifacts/phase7/p7-05/companies/final-columns/embedded/companies/013-p7-companies-nb-dark-360-recovery-save-held.png",
+ "sha256": "a9086b8364022e178d8d651d302ee949522184433d0938fa3321d07658f45022",
+ "bytes": 29762,
+ "hashPolicy": "Exact measured raw bytes"
+ },
+ "originalEmbeddedReport": {
+ "path": ".artifacts/phase7/p7-05/companies/final-columns/companies.json",
+ "sha256": "33b99b07dd6285380c201faf629785fdabfa8f736325a06335864c770b95b67a",
+ "bytes": 1486659,
+ "hashPolicy": "Exact measured raw bytes"
+ },
+ "lane": "companies",
+ "test": "[P7-05 Companies] held save locks one snapshot, retains a failed draft and recovers through its original Save",
+ "name": "p7-companies-nb-dark-360-recovery-save-held",
+ "manualReview": "ACTUALLY_DISPLAYED_AND_REVIEWED_BY_PARENT"
+ },
+ {
+ "raw": {
+ "path": ".artifacts/phase7/p7-05/companies/final-columns/embedded/companies/018-p7-companies-en-dark-390-relationship-retry-held.png",
+ "sha256": "45513cbd64210753e91c0509ed14ba436c77d712cdba6e3b5b6484da67cef954",
+ "bytes": 8005,
+ "hashPolicy": "Exact measured raw bytes"
+ },
+ "originalEmbeddedReport": {
+ "path": ".artifacts/phase7/p7-05/companies/final-columns/companies.json",
+ "sha256": "33b99b07dd6285380c201faf629785fdabfa8f736325a06335864c770b95b67a",
+ "bytes": 1486659,
+ "hashPolicy": "Exact measured raw bytes"
+ },
+ "lane": "companies",
+ "test": "[P7-05 Companies] late relationship A cannot replace B and Retry reads the same company with stable focus",
+ "name": "p7-companies-en-dark-390-relationship-retry-held",
+ "manualReview": "ACTUALLY_DISPLAYED_AND_REVIEWED_BY_PARENT"
+ },
+ {
+ "raw": {
+ "path": ".artifacts/phase7/p7-05/companies/final-columns/embedded/companies/020-p7-companies-en-dark-390-relationship-recovered.png",
+ "sha256": "4119be69500b6df357a9fa936a928ed170aff70e0abbcefd68462abc904c3df3",
+ "bytes": 21526,
+ "hashPolicy": "Exact measured raw bytes"
+ },
+ "originalEmbeddedReport": {
+ "path": ".artifacts/phase7/p7-05/companies/final-columns/companies.json",
+ "sha256": "33b99b07dd6285380c201faf629785fdabfa8f736325a06335864c770b95b67a",
+ "bytes": 1486659,
+ "hashPolicy": "Exact measured raw bytes"
+ },
+ "lane": "companies",
+ "test": "[P7-05 Companies] late relationship A cannot replace B and Retry reads the same company with stable focus",
+ "name": "p7-companies-en-dark-390-relationship-recovered",
+ "manualReview": "ACTUALLY_DISPLAYED_AND_REVIEWED_BY_PARENT"
+ },
+ {
+ "raw": {
+ "path": ".artifacts/phase7/p7-05/companies/final-columns/embedded/companies/024-p7-companies-nb-light-768-open-draft-breakpoint-transition.png",
+ "sha256": "fa6ed0db702a1043f00eec7bb97f401e1ca3e1a72a2e4b74dab66ac6645a8880",
+ "bytes": 39576,
+ "hashPolicy": "Exact measured raw bytes"
+ },
+ "originalEmbeddedReport": {
+ "path": ".artifacts/phase7/p7-05/companies/final-columns/companies.json",
+ "sha256": "33b99b07dd6285380c201faf629785fdabfa8f736325a06335864c770b95b67a",
+ "bytes": 1486659,
+ "hashPolicy": "Exact measured raw bytes"
+ },
+ "lane": "companies",
+ "test": "[P7-05 Companies] edit deep link preserves query hash state and Back; open drafts survive767/768 transition",
+ "name": "p7-companies-nb-light-768-open-draft-breakpoint-transition",
+ "manualReview": "ACTUALLY_DISPLAYED_AND_REVIEWED_BY_PARENT"
+ },
+ {
+ "raw": {
+ "path": ".artifacts/phase7/p7-05/companies/final-columns/embedded/companies/027-p7-companies-nb-light-767-manual-open-draft-breakpoint-transition.png",
+ "sha256": "c084a986791c1c734cfb01cb6a03f71f7118d479d49ac5ab1f4dc5f64f07108e",
+ "bytes": 29188,
+ "hashPolicy": "Exact measured raw bytes"
+ },
+ "originalEmbeddedReport": {
+ "path": ".artifacts/phase7/p7-05/companies/final-columns/companies.json",
+ "sha256": "33b99b07dd6285380c201faf629785fdabfa8f736325a06335864c770b95b67a",
+ "bytes": 1486659,
+ "hashPolicy": "Exact measured raw bytes"
+ },
+ "lane": "companies",
+ "test": "[P7-05 Companies] edit deep link preserves query hash state and Back; open drafts survive767/768 transition",
+ "name": "p7-companies-nb-light-767-manual-open-draft-breakpoint-transition",
+ "manualReview": "ACTUALLY_DISPLAYED_AND_REVIEWED_BY_PARENT"
+ },
+ {
+ "raw": {
+ "path": ".artifacts/phase7/p7-05/companies/final-columns/embedded/companies/032-p7-companies-en-light-1440-live-cross-tab-locale-retained-failure.png",
+ "sha256": "b7fb5929defc84c39042a610b61f5c74085d293e5a997202519c3d3b3ee12f69",
+ "bytes": 79063,
+ "hashPolicy": "Exact measured raw bytes"
+ },
+ "originalEmbeddedReport": {
+ "path": ".artifacts/phase7/p7-05/companies/final-columns/companies.json",
+ "sha256": "33b99b07dd6285380c201faf629785fdabfa8f736325a06335864c770b95b67a",
+ "bytes": 1486659,
+ "hashPolicy": "Exact measured raw bytes"
+ },
+ "lane": "companies",
+ "test": "[P7-05 Companies] live locale changes translate failures and relationship status without refetching or losing drafts",
+ "name": "p7-companies-en-light-1440-live-cross-tab-locale-retained-failure",
+ "manualReview": "ACTUALLY_DISPLAYED_AND_REVIEWED_BY_PARENT"
+ },
+ {
+ "raw": {
+ "path": ".artifacts/phase7/p7-05/companies/final-columns/embedded/companies/036-p7-companies-en-light-320-loaded-finite-sample.png",
+ "sha256": "00baf2e0fc785ced4553c06c2e2e3d0288dd6bd53f47ce9ec442c331d16c103c",
+ "bytes": 34576,
+ "hashPolicy": "Exact measured raw bytes"
+ },
+ "originalEmbeddedReport": {
+ "path": ".artifacts/phase7/p7-05/companies/final-columns/companies.json",
+ "sha256": "33b99b07dd6285380c201faf629785fdabfa8f736325a06335864c770b95b67a",
+ "bytes": 1486659,
+ "hashPolicy": "Exact measured raw bytes"
+ },
+ "lane": "companies",
+ "test": "[P7-05 Companies] eight finite language/theme widths reflow with named table scrolling and keyboard dialogs",
+ "name": "p7-companies-en-light-320-loaded-finite-sample",
+ "manualReview": "ACTUALLY_DISPLAYED_AND_REVIEWED_BY_PARENT"
+ },
+ {
+ "raw": {
+ "path": ".artifacts/phase7/p7-05/companies/final-columns/embedded/companies/038-p7-companies-en-light-320-mobile-edit-keyboard-trap.png",
+ "sha256": "1dc0f26b2073343aa9dbbbff8120cce9ac867d75ddaa49f3f25b090fa2a98b3e",
+ "bytes": 33503,
+ "hashPolicy": "Exact measured raw bytes"
+ },
+ "originalEmbeddedReport": {
+ "path": ".artifacts/phase7/p7-05/companies/final-columns/companies.json",
+ "sha256": "33b99b07dd6285380c201faf629785fdabfa8f736325a06335864c770b95b67a",
+ "bytes": 1486659,
+ "hashPolicy": "Exact measured raw bytes"
+ },
+ "lane": "companies",
+ "test": "[P7-05 Companies] eight finite language/theme widths reflow with named table scrolling and keyboard dialogs",
+ "name": "p7-companies-en-light-320-mobile-edit-keyboard-trap",
+ "manualReview": "ACTUALLY_DISPLAYED_AND_REVIEWED_BY_PARENT"
+ },
+ {
+ "raw": {
+ "path": ".artifacts/phase7/p7-05/companies/final-columns/embedded/companies/040-p7-companies-nb-dark-360-loaded-finite-sample.png",
+ "sha256": "6a5b2a68c566eb5c83f3259c9400b12aa06a43513175fa17e8c2e97483c4e8d2",
+ "bytes": 34063,
+ "hashPolicy": "Exact measured raw bytes"
+ },
+ "originalEmbeddedReport": {
+ "path": ".artifacts/phase7/p7-05/companies/final-columns/companies.json",
+ "sha256": "33b99b07dd6285380c201faf629785fdabfa8f736325a06335864c770b95b67a",
+ "bytes": 1486659,
+ "hashPolicy": "Exact measured raw bytes"
+ },
+ "lane": "companies",
+ "test": "[P7-05 Companies] eight finite language/theme widths reflow with named table scrolling and keyboard dialogs",
+ "name": "p7-companies-nb-dark-360-loaded-finite-sample",
+ "manualReview": "ACTUALLY_DISPLAYED_AND_REVIEWED_BY_PARENT"
+ },
+ {
+ "raw": {
+ "path": ".artifacts/phase7/p7-05/companies/final-columns/embedded/companies/042-p7-companies-en-dark-390-loaded-finite-sample.png",
+ "sha256": "5dd092a4aa4a79ee2aabfe8d1ab228dc177091168810843f275ef71b5abdaf9b",
+ "bytes": 34552,
+ "hashPolicy": "Exact measured raw bytes"
+ },
+ "originalEmbeddedReport": {
+ "path": ".artifacts/phase7/p7-05/companies/final-columns/companies.json",
+ "sha256": "33b99b07dd6285380c201faf629785fdabfa8f736325a06335864c770b95b67a",
+ "bytes": 1486659,
+ "hashPolicy": "Exact measured raw bytes"
+ },
+ "lane": "companies",
+ "test": "[P7-05 Companies] eight finite language/theme widths reflow with named table scrolling and keyboard dialogs",
+ "name": "p7-companies-en-dark-390-loaded-finite-sample",
+ "manualReview": "ACTUALLY_DISPLAYED_AND_REVIEWED_BY_PARENT"
+ },
+ {
+ "raw": {
+ "path": ".artifacts/phase7/p7-05/companies/final-columns/embedded/companies/044-p7-companies-nb-light-767-loaded-finite-sample.png",
+ "sha256": "a8816d18b911d8d0aee5e8c7036858b40c5131753477c65e317f4b9086bb8c40",
+ "bytes": 35748,
+ "hashPolicy": "Exact measured raw bytes"
+ },
+ "originalEmbeddedReport": {
+ "path": ".artifacts/phase7/p7-05/companies/final-columns/companies.json",
+ "sha256": "33b99b07dd6285380c201faf629785fdabfa8f736325a06335864c770b95b67a",
+ "bytes": 1486659,
+ "hashPolicy": "Exact measured raw bytes"
+ },
+ "lane": "companies",
+ "test": "[P7-05 Companies] eight finite language/theme widths reflow with named table scrolling and keyboard dialogs",
+ "name": "p7-companies-nb-light-767-loaded-finite-sample",
+ "manualReview": "ACTUALLY_DISPLAYED_AND_REVIEWED_BY_PARENT"
+ },
+ {
+ "raw": {
+ "path": ".artifacts/phase7/p7-05/companies/final-columns/embedded/companies/046-p7-companies-en-dark-768-loaded-finite-sample.png",
+ "sha256": "00cebc63b37b6d7f3a6b558d4bd3c062ba5bcf52ba336e7c9f5c618ef73a5550",
+ "bytes": 37632,
+ "hashPolicy": "Exact measured raw bytes"
+ },
+ "originalEmbeddedReport": {
+ "path": ".artifacts/phase7/p7-05/companies/final-columns/companies.json",
+ "sha256": "33b99b07dd6285380c201faf629785fdabfa8f736325a06335864c770b95b67a",
+ "bytes": 1486659,
+ "hashPolicy": "Exact measured raw bytes"
+ },
+ "lane": "companies",
+ "test": "[P7-05 Companies] eight finite language/theme widths reflow with named table scrolling and keyboard dialogs",
+ "name": "p7-companies-en-dark-768-loaded-finite-sample",
+ "manualReview": "ACTUALLY_DISPLAYED_AND_REVIEWED_BY_PARENT"
+ },
+ {
+ "raw": {
+ "path": ".artifacts/phase7/p7-05/companies/final-columns/embedded/companies/048-p7-companies-nb-light-1024-loaded-finite-sample.png",
+ "sha256": "f8120ab73cb77082bd1c2f19a48a5a42da849724ef0970845b31157038f8d316",
+ "bytes": 43423,
+ "hashPolicy": "Exact measured raw bytes"
+ },
+ "originalEmbeddedReport": {
+ "path": ".artifacts/phase7/p7-05/companies/final-columns/companies.json",
+ "sha256": "33b99b07dd6285380c201faf629785fdabfa8f736325a06335864c770b95b67a",
+ "bytes": 1486659,
+ "hashPolicy": "Exact measured raw bytes"
+ },
+ "lane": "companies",
+ "test": "[P7-05 Companies] eight finite language/theme widths reflow with named table scrolling and keyboard dialogs",
+ "name": "p7-companies-nb-light-1024-loaded-finite-sample",
+ "manualReview": "ACTUALLY_DISPLAYED_AND_REVIEWED_BY_PARENT"
+ },
+ {
+ "raw": {
+ "path": ".artifacts/phase7/p7-05/companies/final-columns/embedded/companies/050-p7-companies-nb-light-1024-landscape-relationship-scroll-trap.png",
+ "sha256": "98db3180936fafb9f2e61d959a8f1cddac766ad578fc8409ec66a43c1e476e53",
+ "bytes": 40292,
+ "hashPolicy": "Exact measured raw bytes"
+ },
+ "originalEmbeddedReport": {
+ "path": ".artifacts/phase7/p7-05/companies/final-columns/companies.json",
+ "sha256": "33b99b07dd6285380c201faf629785fdabfa8f736325a06335864c770b95b67a",
+ "bytes": 1486659,
+ "hashPolicy": "Exact measured raw bytes"
+ },
+ "lane": "companies",
+ "test": "[P7-05 Companies] eight finite language/theme widths reflow with named table scrolling and keyboard dialogs",
+ "name": "p7-companies-nb-light-1024-landscape-relationship-scroll-trap",
+ "manualReview": "ACTUALLY_DISPLAYED_AND_REVIEWED_BY_PARENT"
+ },
+ {
+ "raw": {
+ "path": ".artifacts/phase7/p7-05/companies/final-columns/embedded/companies/053-p7-companies-en-light-1440-loaded-finite-sample.png",
+ "sha256": "d73db8275f6445eda078f6a84dde74a90857661f1747aa2a0176d85f537cdc10",
+ "bytes": 67549,
+ "hashPolicy": "Exact measured raw bytes"
+ },
+ "originalEmbeddedReport": {
+ "path": ".artifacts/phase7/p7-05/companies/final-columns/companies.json",
+ "sha256": "33b99b07dd6285380c201faf629785fdabfa8f736325a06335864c770b95b67a",
+ "bytes": 1486659,
+ "hashPolicy": "Exact measured raw bytes"
+ },
+ "lane": "companies",
+ "test": "[P7-05 Companies] eight finite language/theme widths reflow with named table scrolling and keyboard dialogs",
+ "name": "p7-companies-en-light-1440-loaded-finite-sample",
+ "manualReview": "ACTUALLY_DISPLAYED_AND_REVIEWED_BY_PARENT"
+ },
+ {
+ "raw": {
+ "path": ".artifacts/phase7/p7-05/companies/final-columns/embedded/companies/055-p7-companies-nb-dark-1920-loaded-finite-sample.png",
+ "sha256": "241bcbda1d7b88ada9fb043d65c1bd890231eafbe74295e1b30a9e3a55c63004",
+ "bytes": 71561,
+ "hashPolicy": "Exact measured raw bytes"
+ },
+ "originalEmbeddedReport": {
+ "path": ".artifacts/phase7/p7-05/companies/final-columns/companies.json",
+ "sha256": "33b99b07dd6285380c201faf629785fdabfa8f736325a06335864c770b95b67a",
+ "bytes": 1486659,
+ "hashPolicy": "Exact measured raw bytes"
+ },
+ "lane": "companies",
+ "test": "[P7-05 Companies] eight finite language/theme widths reflow with named table scrolling and keyboard dialogs",
+ "name": "p7-companies-nb-dark-1920-loaded-finite-sample",
+ "manualReview": "ACTUALLY_DISPLAYED_AND_REVIEWED_BY_PARENT"
+ }
+ ],
+ "qualification": "All20 new final-columns PNGs actually displayed and reviewed; other30 old-regression images retained, not claimed reviewed. Eight balanced320/360/390/767/768/1024x320/1440/1920 EN/NB light/dark samples and held/failed/recovered saves, relationship and breakpoint drafts. Source/pipeline columns retain readable width; table dates/headings/actions do not split. Named desktop table is legitimately two-dimensional; modal content vertically scrolls. Full-page landscape capture paints fixed shell at the current scrolled position; no complete zoom/occlusion acceptance is inferred."
+ },
+ "backend": {
+ "passed": 2,
+ "failed": 0,
+ "command": {
+ "command": [
+ "rtk",
+ "proxy",
+ "C:/Users/Connor/.dotnet/dotnet.exe",
+ "test",
+ "JobTrackerApi.Tests/JobTrackerApi.Tests.csproj",
+ "--configuration",
+ "Release",
+ "--no-build",
+ "--filter",
+ "FullyQualifiedName~CompanyRelationshipTests|FullyQualifiedName~OwnershipIsolationTests|FullyQualifiedName~AuthorizationCoverageTests",
+ "--logger",
+ "trx;LogFileName=career.trx",
+ "--results-directory",
+ "E:\\Storage\\Projects\\Active\\Job tracker\\.artifacts\\phase7\\current\\backend-career\\p705-companies-20261012T225035Z"
+ ],
+ "startedUtc": "2026-10-12T22:50:35.554927+00:00",
+ "finishedUtc": "2026-10-12T22:50:37.800249+00:00",
+ "exitCode": 0,
+ "logSha256": "f1969025a0682555e302bff1b705eabf76344244416a8c3bd3cc56cc2fbf9aef",
+ "counts": {
+ "total": 2,
+ "passed": 2,
+ "failed": 0,
+ "notExecuted": 0
+ },
+ "resultSha256": "21942c3c9c53eb12b44ddff69f49ba85e4124e9a0c422d1db5c483ead423ad73"
+ },
+ "files": [
+ {
+ "path": ".artifacts/phase7/p7-05/companies/backend/career.log",
+ "sha256": "f1969025a0682555e302bff1b705eabf76344244416a8c3bd3cc56cc2fbf9aef",
+ "bytes": 614,
+ "hashPolicy": "Exact raw bytes"
+ },
+ {
+ "path": ".artifacts/phase7/p7-05/companies/backend/career.trx",
+ "sha256": "21942c3c9c53eb12b44ddff69f49ba85e4124e9a0c422d1db5c483ead423ad73",
+ "bytes": 4398,
+ "hashPolicy": "Exact raw bytes"
+ },
+ {
+ "path": ".artifacts/phase7/p7-05/companies/backend/command.json",
+ "sha256": "9c85e5c53caed9178952d2d1d3848e92b59cf27f60aa5f5f169c91df037fcc2a",
+ "bytes": 981,
+ "hashPolicy": "Exact raw bytes"
+ }
+ ],
+ "binary": "Same verified Release DLL, no backend rebuild/full suite. Filter included other class names but only two CompanyRelationship Facts matched; native separately proves actual owner/anonymous/foreign denial."
+ },
+ "attempts": [
+ {
+ "path": ".artifacts/phase7/p7-05/companies/attempt1-O5y/results.json",
+ "sha256": "3c1504e4dfa4d4251b11bcce367896dae236c66b781fdd1874ab6038c91a1310",
+ "bytes": 154557,
+ "hashPolicy": "Exact raw bytes"
+ },
+ {
+ "path": ".artifacts/phase7/p7-05/companies/focus-diagnostic-O5y/results.json",
+ "sha256": "064376e8dc1fb1603d1f793bc64a16f710b37f51ba57ebf033d83d263573f92a",
+ "bytes": 97869,
+ "hashPolicy": "Exact raw bytes"
+ },
+ {
+ "path": ".artifacts/phase7/p7-05/companies/attempt2-vUr/results.json",
+ "sha256": "007e3a7efa075b03b6a7b49e24f7c9e37cd75ca96e1e9b4b9dfcfaa730944767",
+ "bytes": 154376,
+ "hashPolicy": "Exact raw bytes"
+ },
+ {
+ "path": ".artifacts/phase7/p7-05/companies/exit-candidate-QjD/results.json",
+ "sha256": "7cd12bf3e50b5ec77653be6dd9b2ebd1cbf8453f3fee17b394c8f34df59e05c6",
+ "bytes": 238328,
+ "hashPolicy": "Exact raw bytes"
+ },
+ {
+ "path": ".artifacts/phase7/p7-05/companies/title-candidate-XxO/results.json",
+ "sha256": "7bd5925611586b5bc81c0f80790ce59625467bea2a326780e750f23c49c2f89e",
+ "bytes": 238557,
+ "hashPolicy": "Exact raw bytes"
+ },
+ {
+ "path": ".artifacts/phase7/p7-05/companies/final-native/results.json",
+ "sha256": "627d6f35f80a095e52970d49adeab906c4d9101d47f37edcbe1a2ab15deb5782",
+ "bytes": 161650,
+ "hashPolicy": "Exact raw bytes"
+ },
+ {
+ "path": ".artifacts/phase7/p7-05/companies/final-visual/results.json",
+ "sha256": "113211b0f207d4eb4e4b60face58b4c36e50b352243bb0bc0a8599a8be457217",
+ "bytes": 160973,
+ "hashPolicy": "Exact raw bytes"
+ },
+ {
+ "path": ".artifacts/phase7/p7-05/companies/final/results.json",
+ "sha256": "e5e7e1bd251ba118e93f0b8cf8e26e98f119f2e41926556b0e825fbeb53ce765",
+ "bytes": 237349,
+ "hashPolicy": "Exact raw bytes"
+ }
+ ],
+ "unitHistory": [
+ {
+ "path": ".artifacts/phase7/p7-05/companies/unit-baseline/report-receipt.json",
+ "sha256": "2d372888e409bf086cc39b3d4a1a1653f31cebf3dffe83608a837d21028d2b1a",
+ "bytes": 738,
+ "hashPolicy": "Exact raw bytes"
+ },
+ {
+ "path": ".artifacts/phase7/p7-05/companies/unit-red/report-receipt.json",
+ "sha256": "712d426734d9c3dc8ccef88d74c2225c1073d85a86b3b34b2605e52a412834fa",
+ "bytes": 678,
+ "hashPolicy": "Exact raw bytes"
+ },
+ {
+ "path": ".artifacts/phase7/p7-05/companies/unit-red-corrected/report-receipt.json",
+ "sha256": "d6c04bb2f9a2fd90771314242d1d065fb947dc3018affd2726c2b2a1ab950d42",
+ "bytes": 738,
+ "hashPolicy": "Exact raw bytes"
+ },
+ {
+ "path": ".artifacts/phase7/p7-05/companies/unit-candidate/report-receipt.json",
+ "sha256": "ac1128f8ce956844f09d791b8b0d0fd9e7c585274039c73e49dfe44398265f8d",
+ "bytes": 714,
+ "hashPolicy": "Exact raw bytes"
+ },
+ {
+ "path": ".artifacts/phase7/p7-05/companies/unit-final/report-receipt.json",
+ "sha256": "47f0b50261a7a4bd80566fe746314c76730e27ca866671673ec2f0dc16ee4eeb",
+ "bytes": 690,
+ "hashPolicy": "Exact raw bytes"
+ },
+ {
+ "path": ".artifacts/phase7/p7-05/companies/unit-final-green/report-receipt.json",
+ "sha256": "b02817f57141a070975f88d7c72300c9a0644450a48a78391b89b8d03a5850ec",
+ "bytes": 726,
+ "hashPolicy": "Exact raw bytes"
+ },
+ {
+ "path": ".artifacts/phase7/p7-05/companies/unit-stage-red/report-receipt.json",
+ "sha256": "27e995d4dfcdb14f4f62c4c0a208a866c544585cb1c5d39e4c5f2e2ac6a80e60",
+ "bytes": 714,
+ "hashPolicy": "Exact raw bytes"
+ },
+ {
+ "path": ".artifacts/phase7/p7-05/companies/unit-final-focus-green/report-receipt.json",
+ "sha256": "823d212ebd3e9ae37cd7a879a6ee330d4f241ea226a7b09f368ed1eefd9741d1",
+ "bytes": 762,
+ "hashPolicy": "Exact raw bytes"
+ },
+ {
+ "path": ".artifacts/phase7/p7-05/companies/unit-final-exit-green/report-receipt.json",
+ "sha256": "6588280596307d95819877a412be63df4bb834a4807796ba37989a07a210282d",
+ "bytes": 756,
+ "hashPolicy": "Exact raw bytes"
+ },
+ {
+ "path": ".artifacts/phase7/p7-05/companies/unit-title-red/report-receipt.json",
+ "sha256": "8331c6ba4527a06012384148bcb6fce10d9392584086da5f78b2f7b9927d5593",
+ "bytes": 714,
+ "hashPolicy": "Exact raw bytes"
+ },
+ {
+ "path": ".artifacts/phase7/p7-05/companies/unit-final-title-green/report-receipt.json",
+ "sha256": "faeab77973d1d9781035197051b4be2d861fa219ac17cf601b736ba8e6a12dc3",
+ "bytes": 762,
+ "hashPolicy": "Exact raw bytes"
+ },
+ {
+ "path": ".artifacts/phase7/p7-05/companies/unit-final-layout-green/report-receipt.json",
+ "sha256": "0a0bcaabf0ac99b295ed89b99d454d6a3c3de6bc5f2fc830749a6aba5a357b35",
+ "bytes": 768,
+ "hashPolicy": "Exact raw bytes"
+ },
+ {
+ "path": ".artifacts/phase7/p7-05/companies/unit-final-columns-green/report-receipt.json",
+ "sha256": "a9a87de8bcbd5acbad0fef176a1ee49dbb0951e37f38ea175edc818b0a714ea5",
+ "bytes": 774,
+ "hashPolicy": "Exact raw bytes"
+ }
+ ],
+ "failedTypeReceipts": [
+ {
+ "path": ".artifacts/phase7/p7-05/companies/type-title-failed/receipt.json",
+ "sha256": "2541336ecb3b42a83eb7fbefacb76655e3740adea6abbff3ddc702d325b7c03d",
+ "bytes": 806,
+ "hashPolicy": "Exact raw bytes"
+ },
+ {
+ "path": ".artifacts/phase7/p7-05/companies/browser-history-type-failed/receipt.json",
+ "sha256": "73607f5fd3c51129c004c17a61bb3b94f890ee24fcecf6cfa3631530abd7503b",
+ "bytes": 890,
+ "hashPolicy": "Exact raw bytes"
+ }
+ ],
+ "attemptQualification": "Baseline6/2 passes. Seven new contracts reproduce6pass7fail; first deep-link async fixture was awaited and corrected RED remains6/7. Candidate13/2 passes, expanded26/4 exposes redundant idle save notice; only mount pending notice and preserve original status assertions. Strengthened original raw pipeline/date cases reproduce11/1 and restore arbitrary company text, while canonical application statuses localize. Original relationship case reproduces11/1 stale loaded title and now prefers current response company metadata. Initial and early-exit native4/2 plus focused0/1 explicitly record BODY focus; queued post-exit React effect after MUI removal repairs existing action restoration. Final30/4 and6+2+4 pass with no skips/retries/xfail/weakened assertions or axe waiver. Parent review of the previous20 PNGs prompted bounded nowrap heading/date/action and source/pipeline width adjustments; latest20 were freshly reviewed.",
+ "accessibility": {
+ "newScans": 20,
+ "newViolations": 0,
+ "newIncomplete": 91,
+ "existingNamedScans": 20,
+ "existingNamedViolations": 0,
+ "existingNamedIncomplete": 163,
+ "oldCompanyScans": 0,
+ "fullContextTriage": {
+ "path": ".artifacts/phase7/p7-05/companies/final-columns/axe-triage.json",
+ "sha256": "5fcb9382232f3b5191b46e5dba5d4bdbcd1272eae04806126e110b17b2313467",
+ "bytes": 435234,
+ "hashPolicy": "Exact raw bytes"
+ },
+ "categories": "New20 badge short-text/51 overlap/9 partial occlusion/11 open-modal focus. Old named20 WCAG-tagged scans:8 badge/124 overlap/15 partial/4 modal/8 disabled UndoRedo labels/4 aria-labelledby shadows. Complete vectors and related targets retained; no incomplete is converted to conformance.",
+ "scopeQualification": "New Companies scans assert all enabled violations including target-size. Existing named-controls scans use WCAG tags;12 dialog scans assert all tag-scoped violations,8 full-page scans gate five historical naming/contrast rule IDs. All raw scanned violations independently measured0; their full raw scope is retained without calling them all-enabled.",
+ "contextualTriage": {
+ "interpretations": {
+ "shortTextContent": "Badge text0 too short for contrast analysis. No measured ratio or false-positive closure.",
+ "bgOverlap": "Contrast background unknown; absent related nodes cannot identify a responsible overlapping element.",
+ "elmPartiallyObscured": "Explicit related clipping surfaces retained; scroll geometry may explain capture context but does not prove contrast or every scroll-position reachability.",
+ "focusable-modal-open": "Manual tabbability/AT qualification. New Companies root-background modal vectors and old closed Arial Select Menu vectors are separate contexts.",
+ "noIdShadow": "Old command Dialog has explicit localized aria-label but a labelledby ID requiring review. Native named Dialog lookup does not resolve invalid IDREF semantics.",
+ "noRoleSingular": "Old CV Undo/Redo tooltip wrapper spans carry aria-label without semantic role. Naming/AT behavior remains pending."
+ },
+ "demonstratedNewNative": [
+ "six ordinary journeys passed with no skips/retries/flakes",
+ "320px Edit Save Tab wraps to Name and ShiftTab wraps back to Save",
+ "1024x320 Relationship Close Tab wraps to Open and ShiftTab wraps back to Close",
+ "Relationship PageDown scrolling and bounded table ArrowRight scrolling when overflowing",
+ "held Save locks nine fields plus Save/Cancel and blocks Escape with one request",
+ "Retry focuses stable named relationship region",
+ "successful save and deep-link/manual responsive/landscape closing restore current numeric-ID action"
+ ],
+ "unresolvedAcceptance": [
+ "All incomplete vectors remain unresolved qualifications, never automatic passing checks.",
+ "Human screen-reader browse/reading/announcement behavior and every background-control sequence in all modal states remain unverified.",
+ "contrastRatio0 in incomplete checks is indeterminate rather than a measured0:1 or a passing ratio.",
+ "Finite rootOverflow0 and PNGs do not establish all vertical occlusion states, real zoom, physical touch/software keyboard or deployed font/photo acceptance.",
+ "Old named-controls tag-scoped cross-application scans are not all-enabled Companies scans."
+ ]
+ },
+ "scopeAndCounts": {
+ "companies": {
+ "sourceScope": "All enabled axe rules plus explicit target-size; full-page scans, all complete violations asserted.",
+ "scans": 20,
+ "violationNodes": 0,
+ "incompleteNodes": 91,
+ "categories": {
+ "shortTextContent": 20,
+ "focusable-modal-open": 11,
+ "elmPartiallyObscured": 9,
+ "bgOverlap": 51
+ }
+ },
+ "companies-regression": {
+ "sourceScope": "No axe scans in existing two-case responsive regression subset.",
+ "scans": 0,
+ "violationNodes": 0,
+ "incompleteNodes": 0,
+ "categories": {}
+ },
+ "companies-named": {
+ "sourceScope": "wcag2a/wcag2aa/wcag21a/wcag21aa tags.12 command/prompt/confirm scans include role=dialog;8 CV/cover scans full page. Dialog assertions gate all tag-scoped violations; loaded full-page assertions whitelist five naming/contrast rule IDs, while raw attachments retain all scanned violations/incompletes. All raw violation nodes measured0.",
+ "scans": 20,
+ "violationNodes": 0,
+ "incompleteNodes": 163,
+ "categories": {
+ "noIdShadow": 4,
+ "focusable-modal-open": 4,
+ "noRoleSingular": 8,
+ "shortTextContent": 8,
+ "bgOverlap": 124,
+ "elmPartiallyObscured": 15
+ }
+ }
+ }
+ },
+ "persistenceQualification": "Actual isolated SQLite fixture creation, nine-field PUT/readback/reload, source normalization, latest20 active contacts/deleted-job exclusion, owned/foreign/anonymous relationship/edit denial and job navigation. Original date payload and formatting remain; backend readback equality is full calendar-second equality despite SQLite Kind/Z serialization differences, not verified UTC/DST equivalence. Arbitrary pipeline strings and server error details remain verbatim. No standalone create UI or provider/model activity.",
+ "countProvenance": {
+ "latestWholeFrontend": "573/89 inherited",
+ "entryFrontendDerived": "682/93",
+ "newCases": 7,
+ "newSuites": 0,
+ "frontendDerivedOnly": "689/93",
+ "entryBrowserDerived": 257,
+ "newOrdinaryBrowserCases": 6,
+ "browserDerivedOnly": 263,
+ "backendDerivedOnly": 1034,
+ "sidecarInherited": 49,
+ "freshIntegratedAcceptance": "P7-12 pending"
+ },
+ "preservation": "All57 remain56 PARTIAL/C11 DEFERRED;32 routes. J01/J08/E08/W11 exact nine-field save/read/context/navigation/owner contracts supported; no API/schema/stored-data/calendar/source/AI/rendering or production change.",
+ "remaining": "Offers/Reminders and later checkpoints pending. M01-M12 human AT/device/real zoom/output/provider qualification open. Existing useCompanies cached consumers were not changed or broadly requalified. P7-06F deferred/P5-13 withheld/Phase8 separate/main unchanged/productionNO-GO.",
+ "staticChecks": {
+ "schemaVersion": 1,
+ "checkpoint": "p7-05",
+ "status": "PASS",
+ "checkedUtc": "2026-10-13T06:58:05.578020+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-13T06:58:01.949800+00:00",
+ "finishedUtc": "2026-10-13T06:58:04.861383+00:00",
+ "exitCode": 0,
+ "log": ".artifacts/phase7/current/static/20261013T065801Z/secret-scan.log",
+ "logSha256Raw": "c5a88d5ebdc042adba125c1eaacbe44f9f4a5c19898c0f195573c00d511aa28d"
+ },
+ {
+ "name": "working-whitespace",
+ "command": [
+ "rtk",
+ "proxy",
+ "git",
+ "diff",
+ "--check"
+ ],
+ "startedUtc": "2026-10-13T06:58:04.861383+00:00",
+ "finishedUtc": "2026-10-13T06:58:04.963155+00:00",
+ "exitCode": 0,
+ "log": ".artifacts/phase7/current/static/20261013T065801Z/working-whitespace.log",
+ "logSha256Raw": "4011fa7ef9791e39a64f9327c032b5969eac6512908f788160860f86dbac7a49"
+ },
+ {
+ "name": "staged-whitespace",
+ "command": [
+ "rtk",
+ "proxy",
+ "git",
+ "diff",
+ "--cached",
+ "--check"
+ ],
+ "startedUtc": "2026-10-13T06:58:04.963655+00:00",
+ "finishedUtc": "2026-10-13T06:58:05.040920+00:00",
+ "exitCode": 0,
+ "log": ".artifacts/phase7/current/static/20261013T065801Z/staged-whitespace.log",
+ "logSha256Raw": "d2993bc68ad9a63f711c8f5473155e2ba00fdcd461220cc3849185f2f9ad93e6"
+ },
+ {
+ "name": "phase7-whitespace",
+ "command": [
+ "rtk",
+ "proxy",
+ "git",
+ "diff",
+ "--check",
+ "b5319981ad3d1bd635daff861453d23c0de5320b"
+ ],
+ "startedUtc": "2026-10-13T06:58:05.040920+00:00",
+ "finishedUtc": "2026-10-13T06:58:05.184972+00:00",
+ "exitCode": 0,
+ "log": ".artifacts/phase7/current/static/20261013T065801Z/phase7-whitespace.log",
+ "logSha256Raw": "0e46215e1541a31f97ddf4225979cdea0a745fce18958c41cc8dc307e6b16bfe"
+ }
+ ],
+ "untrackedSecretScan": {
+ "files": 3,
+ "findings": 0
+ },
+ "documentation": {
+ "files": 12,
+ "relativeLinks": 425,
+ "errors": []
+ },
+ "preservation": {
+ "groups": 57,
+ "statuses": {
+ "PARTIAL": 56,
+ "PASS": 0,
+ "FAIL": 0,
+ "DEFERRED": 1
+ },
+ "originalRoutesPreserved": true,
+ "currentConcreteRoutes": 32
+ },
+ "production": {
+ "main": "27657f31131cd95d42f0e1290853ec9c9b3959a4",
+ "originMain": "27657f31131cd95d42f0e1290853ec9c9b3959a4",
+ "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."
+ ]
+ },
+ "peerReview": "Read-only connected-contract review found no actionable introduced regression; no extra runtime claimed."
+}
diff --git a/docs/redesign/phase-7/p7-05/evidence/companies-static-checks.json b/docs/redesign/phase-7/p7-05/evidence/companies-static-checks.json
new file mode 100644
index 0000000..406ab8f
--- /dev/null
+++ b/docs/redesign/phase-7/p7-05/evidence/companies-static-checks.json
@@ -0,0 +1,104 @@
+{
+ "schemaVersion": 1,
+ "checkpoint": "p7-05",
+ "status": "PASS",
+ "checkedUtc": "2026-10-13T06:58:05.578020+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-13T06:58:01.949800+00:00",
+ "finishedUtc": "2026-10-13T06:58:04.861383+00:00",
+ "exitCode": 0,
+ "log": ".artifacts/phase7/current/static/20261013T065801Z/secret-scan.log",
+ "logSha256Raw": "c5a88d5ebdc042adba125c1eaacbe44f9f4a5c19898c0f195573c00d511aa28d"
+ },
+ {
+ "name": "working-whitespace",
+ "command": [
+ "rtk",
+ "proxy",
+ "git",
+ "diff",
+ "--check"
+ ],
+ "startedUtc": "2026-10-13T06:58:04.861383+00:00",
+ "finishedUtc": "2026-10-13T06:58:04.963155+00:00",
+ "exitCode": 0,
+ "log": ".artifacts/phase7/current/static/20261013T065801Z/working-whitespace.log",
+ "logSha256Raw": "4011fa7ef9791e39a64f9327c032b5969eac6512908f788160860f86dbac7a49"
+ },
+ {
+ "name": "staged-whitespace",
+ "command": [
+ "rtk",
+ "proxy",
+ "git",
+ "diff",
+ "--cached",
+ "--check"
+ ],
+ "startedUtc": "2026-10-13T06:58:04.963655+00:00",
+ "finishedUtc": "2026-10-13T06:58:05.040920+00:00",
+ "exitCode": 0,
+ "log": ".artifacts/phase7/current/static/20261013T065801Z/staged-whitespace.log",
+ "logSha256Raw": "d2993bc68ad9a63f711c8f5473155e2ba00fdcd461220cc3849185f2f9ad93e6"
+ },
+ {
+ "name": "phase7-whitespace",
+ "command": [
+ "rtk",
+ "proxy",
+ "git",
+ "diff",
+ "--check",
+ "b5319981ad3d1bd635daff861453d23c0de5320b"
+ ],
+ "startedUtc": "2026-10-13T06:58:05.040920+00:00",
+ "finishedUtc": "2026-10-13T06:58:05.184972+00:00",
+ "exitCode": 0,
+ "log": ".artifacts/phase7/current/static/20261013T065801Z/phase7-whitespace.log",
+ "logSha256Raw": "0e46215e1541a31f97ddf4225979cdea0a745fce18958c41cc8dc307e6b16bfe"
+ }
+ ],
+ "untrackedSecretScan": {
+ "files": 3,
+ "findings": 0
+ },
+ "documentation": {
+ "files": 12,
+ "relativeLinks": 425,
+ "errors": []
+ },
+ "preservation": {
+ "groups": 57,
+ "statuses": {
+ "PARTIAL": 56,
+ "PASS": 0,
+ "FAIL": 0,
+ "DEFERRED": 1
+ },
+ "originalRoutesPreserved": true,
+ "currentConcreteRoutes": 32
+ },
+ "production": {
+ "main": "27657f31131cd95d42f0e1290853ec9c9b3959a4",
+ "originMain": "27657f31131cd95d42f0e1290853ec9c9b3959a4",
+ "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."
+ ]
+}
diff --git a/docs/redesign/phase-7/p7-05/evidence/discovery-results.json b/docs/redesign/phase-7/p7-05/evidence/discovery-results.json
index bee922e..a13ec53 100644
--- a/docs/redesign/phase-7/p7-05/evidence/discovery-results.json
+++ b/docs/redesign/phase-7/p7-05/evidence/discovery-results.json
@@ -3,7 +3,7 @@
"checkpoint": "P7-05",
"slice": "Discovery",
"recordedUtc": "2026-10-12T22:47:47.672750+00:00",
- "acceptance": "DISCOVERY_IMPLEMENTED_SCOPE_CONDITIONAL_GO_HYGIENE_PASS_NORMAL_PUSH_PENDING_OTHER_DOMAINS_PENDING",
+ "acceptance": "DISCOVERY_IMPLEMENTED_SCOPE_CONDITIONAL_GO_NORMAL_PUSH_VERIFIED_OTHER_DOMAINS_PENDING",
"entryReceipt": {
"checkpoint": "P7-05",
"entry": "CONDITIONAL GO; approved four domain boundaries pending",
@@ -2739,5 +2739,105 @@
"bytes": 3166,
"hashPolicy": "Exact raw bytes",
"qualification": "Four commands0,12docs420links0errors,57groups32routes; final staged recheck follows metadata."
+ },
+ "normalPushReceipt": {
+ "observedUtc": "2026-10-12T22:49:31.085724+00:00",
+ "branch": "codex/ui-redesign-phase4",
+ "head": "34a4d6dc6b4bbe546ef01be91e98bd922b99444f",
+ "originDevelopment": "34a4d6dc6b4bbe546ef01be91e98bd922b99444f",
+ "liveDevelopment": "34a4d6dc6b4bbe546ef01be91e98bd922b99444f",
+ "main": "27657f31131cd95d42f0e1290853ec9c9b3959a4",
+ "originMain": "27657f31131cd95d42f0e1290853ec9c9b3959a4",
+ "liveMain": "27657f31131cd95d42f0e1290853ec9c9b3959a4",
+ "workingTreeClean": true,
+ "pendingPaths": [],
+ "commands": [
+ {
+ "command": [
+ "rtk",
+ "proxy",
+ "git",
+ "branch",
+ "--show-current"
+ ],
+ "exitCode": 0,
+ "stdout": "codex/ui-redesign-phase4\n",
+ "stderr": "[rtk] /!\\ No hook installed — run `rtk init -g` for automatic token savings\n"
+ },
+ {
+ "command": [
+ "rtk",
+ "proxy",
+ "git",
+ "rev-parse",
+ "HEAD"
+ ],
+ "exitCode": 0,
+ "stdout": "34a4d6dc6b4bbe546ef01be91e98bd922b99444f\n",
+ "stderr": "[rtk] /!\\ No hook installed — run `rtk init -g` for automatic token savings\n"
+ },
+ {
+ "command": [
+ "rtk",
+ "proxy",
+ "git",
+ "rev-parse",
+ "origin/codex/ui-redesign-phase4"
+ ],
+ "exitCode": 0,
+ "stdout": "34a4d6dc6b4bbe546ef01be91e98bd922b99444f\n",
+ "stderr": "[rtk] /!\\ No hook installed — run `rtk init -g` for automatic token savings\n"
+ },
+ {
+ "command": [
+ "rtk",
+ "proxy",
+ "git",
+ "rev-parse",
+ "main"
+ ],
+ "exitCode": 0,
+ "stdout": "27657f31131cd95d42f0e1290853ec9c9b3959a4\n",
+ "stderr": "[rtk] /!\\ No hook installed — run `rtk init -g` for automatic token savings\n"
+ },
+ {
+ "command": [
+ "rtk",
+ "proxy",
+ "git",
+ "rev-parse",
+ "origin/main"
+ ],
+ "exitCode": 0,
+ "stdout": "27657f31131cd95d42f0e1290853ec9c9b3959a4\n",
+ "stderr": "[rtk] /!\\ No hook installed — run `rtk init -g` for automatic token savings\n"
+ },
+ {
+ "command": [
+ "rtk",
+ "proxy",
+ "git",
+ "ls-remote",
+ "origin",
+ "refs/heads/codex/ui-redesign-phase4",
+ "refs/heads/main"
+ ],
+ "exitCode": 0,
+ "stdout": "34a4d6dc6b4bbe546ef01be91e98bd922b99444f\trefs/heads/codex/ui-redesign-phase4\n27657f31131cd95d42f0e1290853ec9c9b3959a4\trefs/heads/main\n",
+ "stderr": "[rtk] /!\\ No hook installed — run `rtk init -g` for automatic token savings\n"
+ },
+ {
+ "command": [
+ "rtk",
+ "proxy",
+ "git",
+ "status",
+ "--porcelain=v1"
+ ],
+ "exitCode": 0,
+ "stdout": "",
+ "stderr": "[rtk] /!\\ No hook installed — run `rtk init -g` for automatic token savings\n"
+ }
+ ]
}
}
diff --git a/docs/roadmap/todo.md b/docs/roadmap/todo.md
index 2a243ac..ae3dcbe 100644
--- a/docs/roadmap/todo.md
+++ b/docs/roadmap/todo.md
@@ -87,7 +87,7 @@ Follow the approved [Phase 7 handoff](../redesign/phase-6/p6-05/PHASE7_HANDOFF.m
- [x] P7-02 [onboarding guidance and Career overview/evidence](../redesign/phase-7/p7-02/README.md): entryad7b419 pushed/verified; onboarding implemented-scope CONDITIONAL GO32/13/4+types/lint/build/13 actual PNG inspections/hygiene, pushedd21501aa/direct receipt23:55:12UTC. Separate actual SQLite evidence-list repair91 guards/HTTP1/Release passed and pushed01281f52/direct receipt00:06:47UTC; unfinished Career work preserved/main unchanged. Career affected46/3/native14/fresh preservation2/types/lint/build and22 actual PNG inspections pass; implemented P7-02 CONDITIONAL GO, final Career hygiene PASS (four commands0,9docs347links0errors) and normal push8b4d5c2 directly verified05:51:04UTC, localHEAD=origin=live/clean worktree/main unchanged. Career0/99 axe incompletes/manual gates retained; fullfrontend610/backend1,034 are derived only, latest fresh full573/89 unchanged.
- [x] P7-03 Career structured editor and import/history: Implemented P7-03 scope is **CONDITIONAL GO**. The normal import/history push `5d6e32358c79cc8cfe505013fc79995940079767` was directly verified at 2026-10-12 17:28:54.912933UTC: local HEAD=origin=live development, clean worktree, local/origin/live main unchanged `27657f31131cd95d42f0e1290853ec9c9b3959a4`. Fresh affected76/6, ordinary native35, backend98/seeder24, types/lint/build and staged hygiene pass; 33/54 retained final images were inspected. Import17 scans0/302, editor13 scans0/377 and Career22 scans0/99 remain qualified. RD21 concurrency/partial-write recovery, old orphan records and human/device/provider/output requirements remain open. All57 groups remain56 PARTIAL/C11 DEFERRED,32 routes retained. 648/93 frontend and233 browser are derived only; latest whole573/89 remains inherited. Production NO-GO, P5-13 withholding and Phase8 separation remain unchanged.
- [x] P7-04 CV library and public entry implemented scope CONDITIONAL GO: library fcacae7 pushed/verified; public38/3 affected/native8/fidelity16/36+8/24/types/lint/build PASS,18 actual public images reviewed. Public final hygiene PASS and bfa0ab6 normal push verified; sandbox frame-tested12 and human/device/production qualifications retained.
-- [ ] P7-05 Discovery, Companies, Offers and Reminders: Discovery21/1,new8+existing4native/backend14/types/lint/build pass;18 actual new PNG reviews,81+52 axe incompletes retained. Discovery hygiene PASS/normal push pending; other three domains pending.
+- [ ] P7-05 Discovery, Companies, Offers and Reminders: Discovery21/1,new8+existing4native/backend14/types/lint/build pass;18 actual new PNG reviews,81+52 axe incompletes retained. Discovery hygiene PASS/normal push34a4d6d verified. Companies30/4,new6+old2+named4native/backend2/types/lint/build pass;20actual PNG reviews,91+163incompletes qualified. Companies hygiene PASS/normal push pending; Offers/Reminders next.
- [ ] P7-06 remaining Application forms and global correspondence.
- [ ] P7-06F optional reviewed followMode composer: retain deferral unless its required action/fallback decision is established.
- [ ] P7-07 owner-scoped metadata projection, then Documents interface.
diff --git a/job-tracker-ui/e2e/phase7-companies.spec.ts b/job-tracker-ui/e2e/phase7-companies.spec.ts
new file mode 100644
index 0000000..a5addbd
--- /dev/null
+++ b/job-tracker-ui/e2e/phase7-companies.spec.ts
@@ -0,0 +1,551 @@
+import AxeBuilder from '@axe-core/playwright';
+import { expect, test as base, type APIRequestContext, type APIResponse, type Locator, type Page, type Response, type TestInfo } from '@playwright/test';
+import { translations } from '../src/i18n/translations';
+import type { Company } from '../src/types';
+
+const apiUrl = 'http://localhost:5302/api';
+const companiesUrl = `${apiUrl}/companies`;
+const password = 'E2ePassword123!';
+type Owner = { id: string; headers: Record
};
+type Sample = { language: 'en' | 'nb'; theme: 'light' | 'dark'; width: number; height: number };
+type RequestRecord = { method: string; path: string; query: Record; body: unknown };
+type Session = { owner: Owner; requests: RequestRecord[] };
+type Job = { id: number; jobTitle: string; status: string };
+type Contact = { id: number; jobApplicationId: number; subject: string; date: string; from: string; channel: string; direction: string };
+type Relationship = {
+ company: Company;
+ applications: Array<{ id: number; jobTitle: string; status: string; dateApplied: string | null; followUpAt: string | null; messageCount: number; lastContactAt: string | null }>;
+ recentContacts: Array;
+};
+type Draft = { name: string; location: string; source: string; recruiterName: string; recruiterEmail: string; recruiterLinkedIn: string; pipelineStage: string; lastContactedAt: string; nextContactAt: string };
+const desktop: Sample = { language: 'en', theme: 'light', width: 1440, height: 900 };
+const boundary = 'Company creation is an API fixture: the existing Companies page has no standalone create interface. Company list/detail/nine-field PUT, job creation/soft deletion and manual correspondence use the actual isolated owner HTTP API and disposable SQLite. Route-held requests continue to that API when released; explicitly controlled failures establish presentation and recovery only. No provider/model/external service is invoked. Browser request records contain only Companies method/path/query/JSON payload; direct fixture/readback requests are verified separately and no session or CSRF headers are recorded.';
+
+async function json(result: APIResponse | Response, status = 200): Promise {
+ expect(result.status(), `${result.url()} returns ${status}`).toBe(status);
+ return result.json();
+}
+
+async function login(api: APIRequestContext, email: string): Promise {
+ expect((await api.post(`${apiUrl}/auth/login`, { data: { email, password } })).status()).toBe(200);
+ const me = await json<{ id: string; sessionContext: string }>(await api.get(`${apiUrl}/auth/me`));
+ const csrf = (await api.storageState()).cookies.find(cookie => cookie.name === 'XSRF-TOKEN');
+ expect(csrf).toBeDefined();
+ return { id: me.id, headers: { 'X-CSRF-TOKEN': csrf!.value, 'X-Session-Context': me.sessionContext } };
+}
+
+async function createAccount(admin: APIRequestContext, administrator: Owner) {
+ const email = `p7-companies-${Date.now()}-${Math.random().toString(36).slice(2)}@example.test`;
+ expect((await admin.post(`${apiUrl}/users`, { headers: administrator.headers, data: { email, password, roles: [] } })).status()).toBe(200);
+ return email;
+}
+
+const test = base.extend<{ session: Session }>({
+ session: async ({ page, context, playwright }, provide, info) => {
+ expect(process.env.PLAYWRIGHT_REUSE_SERVERS).not.toBe('true');
+ expect(info.config.metadata.isolatedDataRoot).toEqual(expect.any(String));
+ const admin = await playwright.request.newContext();
+ const requests: RequestRecord[] = [];
+ const blockedExternalRequests: string[] = [];
+ page.on('request', request => {
+ const url = new URL(request.url());
+ if (!/^\/api\/companies(?:\/|$)/.test(url.pathname)) return;
+ requests.push({ method: request.method(), path: url.pathname, query: Object.fromEntries(url.searchParams),
+ body: request.postData() ? request.postDataJSON() : null });
+ });
+ try {
+ const administrator = await login(admin, 'e2e@example.test');
+ const owner = await login(page.request, await createAccount(admin, administrator));
+ await context.route('**/*', route => {
+ const url = new URL(route.request().url());
+ if (!['localhost', '127.0.0.1'].includes(url.hostname) && !['data:', 'blob:', 'about:'].includes(url.protocol)) {
+ blockedExternalRequests.push(url.origin + url.pathname);
+ return route.abort();
+ }
+ return route.continue();
+ });
+ await provide({ owner, requests });
+ } finally {
+ await info.attach('companies-request-boundary', { body: JSON.stringify({ boundary, requests, blockedExternalRequests }), contentType: 'application/json' });
+ await admin.dispose();
+ }
+ },
+});
+
+async function createCompany(page: Page, owner: Owner, name: string, location = 'Oslo', source = 'User') {
+ return json(await page.request.post(companiesUrl, { headers: owner.headers, data: { name, location, source } }), 201);
+}
+async function nativeCompany(page: Page, company: Company) { return json(await page.request.get(`${companiesUrl}/${company.id}`)); }
+async function nativeList(api: APIRequestContext) { return json(await api.get(companiesUrl)); }
+async function createJob(page: Page, owner: Owner, company: Company, title: string, status = 'Saved') {
+ return json(await page.request.post(`${apiUrl}/jobapplications`, { headers: owner.headers, data: { companyId: company.id, jobTitle: title, status } }), 201);
+}
+async function createContact(page: Page, owner: Owner, job: Job, index: number, subject = `Recorded contact ${String(index).padStart(2, '0')}`) {
+ return json(await page.request.post(`${apiUrl}/correspondence`, { headers: owner.headers,
+ data: { jobApplicationId: job.id, from: `Recruiter ${index}`, content: `Owner-recorded correspondence ${index}; no external email delivery.`,
+ subject, channel: 'email', direction: 'inbound', date: new Date(Date.UTC(2026, 10, 1, 10, index)).toISOString() } }), 201);
+}
+
+function edit(page: Page, company: Company, language: Sample['language'] = 'en') {
+ return page.getByRole('button', { name: `${translations[language].jobTableEdit}: ${company.name}`, exact: true });
+}
+function relationshipAction(page: Page, company: Company, language: Sample['language'] = 'en') {
+ return page.getByRole('button', { name: `${translations[language].companiesRelationship}: ${company.name}`, exact: true });
+}
+function editDialog(page: Page, language: Sample['language'] = 'en') {
+ return page.getByRole('dialog', { name: translations[language].companiesEdit, exact: true });
+}
+function editRegion(dialog: Locator, language: Sample['language'] = 'en') {
+ return dialog.getByRole('region', { name: translations[language].companiesEdit, exact: true });
+}
+function relationshipRegion(dialog: Locator, language: Sample['language'] = 'en') {
+ return dialog.getByRole('region', { name: translations[language].companiesRelationship, exact: true });
+}
+function fields(language: Sample['language']) {
+ const t = translations[language];
+ return { name: t.companiesName, location: t.companiesLocation, source: t.companiesSource, recruiterName: t.companiesRecruiterName,
+ recruiterEmail: t.companiesRecruiterEmail, recruiterLinkedIn: t.companiesRecruiterLinkedIn, pipelineStage: t.companiesPipelineStage,
+ lastContactedAt: t.companiesLastContacted, nextContactAt: t.companiesNextContactField };
+}
+async function fillDraft(dialog: Locator, draft: Draft, language: Sample['language'] = 'en') {
+ const labels = fields(language);
+ for (const key of Object.keys(labels) as Array) await dialog.getByLabel(labels[key], { exact: true }).fill(draft[key]);
+}
+async function expectDraft(dialog: Locator, draft: Draft, language: Sample['language'] = 'en') {
+ const labels = fields(language);
+ for (const key of Object.keys(labels) as Array) await expect(dialog.getByLabel(labels[key], { exact: true })).toHaveValue(draft[key]);
+}
+function snapshot(draft: Draft) {
+ return { ...draft, recruiterName: draft.recruiterName.trim() || null, recruiterEmail: draft.recruiterEmail.trim() || null,
+ recruiterLinkedIn: draft.recruiterLinkedIn.trim() || null, pipelineStage: draft.pipelineStage.trim() || null,
+ lastContactedAt: draft.lastContactedAt || null, nextContactAt: draft.nextContactAt || null,
+ location: draft.location || null, source: draft.source || null };
+}
+function draftFor(company: Company, name = `${company.name} reviewed`): Draft {
+ return { name, location: ' Bergen ', source: 'https://www.linkedin.com/', recruiterName: ' Rekrutterer Æ Ø Å ',
+ recruiterEmail: ' reviewed@example.test ', recruiterLinkedIn: ' https://example.test/recruiter ',
+ pipelineStage: ' Interview ', lastContactedAt: '2026-11-21', nextContactAt: '2026-11-23' };
+}
+function hold() { let release!: () => void; const pending = new Promise(resolve => { release = resolve; }); return { pending, release }; }
+function response(page: Page, method: string, url: string) { return page.waitForResponse(result => result.url() === url && result.request().method() === method); }
+function count(requests: RequestRecord[], method: string, path: string) { return requests.filter(request => request.method === method && request.path === path).length; }
+async function keyboardActivate(page: Page, locator: Locator, key = 'Enter') { await locator.focus(); await expect(locator).toBeFocused(); await page.keyboard.press(key); }
+async function recordFocus(page: Page, info: TestInfo, state: string) {
+ await info.attach(`companies-focus-${state}`, { body: JSON.stringify({ state, focused: await page.evaluate(() => {
+ const element = document.activeElement;
+ return { tag: element?.tagName, id: element?.id, role: element?.getAttribute('role'), label: element?.getAttribute('aria-label'), text: element?.textContent?.trim().slice(0, 140) };
+ }), qualification: 'Actual isolated Chromium focus; this is not human screen-reader or physical-device acceptance.' }), contentType: 'application/json' });
+}
+
+async function enter(page: Page, owner: Owner, sample: Sample = desktop, url = '/companies?source=phase7-companies#preserved') {
+ expect((await page.request.put(`${apiUrl}/auth/preferences/language`, { headers: owner.headers,
+ data: { language: sample.language === 'nb' ? 'nb-NO' : 'en' } })).status()).toBe(204);
+ await page.addInitScript(({ language, theme }) => {
+ if (localStorage.getItem('uiLanguage') === null) localStorage.setItem('uiLanguage', language);
+ if (localStorage.getItem('jobtracker.themeMode') === null) localStorage.setItem('jobtracker.themeMode', theme);
+ }, sample);
+ await page.setViewportSize({ width: sample.width, height: sample.height });
+ await page.emulateMedia({ reducedMotion: 'reduce' });
+ await page.goto(url);
+ const stored = await page.evaluate(() => ({ language: localStorage.getItem('uiLanguage'), theme: localStorage.getItem('jobtracker.themeMode') }));
+ if (stored.language !== sample.language || stored.theme !== sample.theme) {
+ await page.evaluate(({ language, theme }) => { localStorage.setItem('uiLanguage', language); localStorage.setItem('jobtracker.themeMode', theme); }, sample);
+ await page.reload();
+ }
+ await expect(page.locator('html')).toHaveAttribute('lang', sample.language === 'nb' ? 'nb-NO' : 'en');
+ await expect(page.locator('html')).toHaveAttribute('data-color-scheme', sample.theme);
+ await expect(page.getByRole('heading', { level: 1 })).toHaveCount(1);
+ await expect(page.getByRole('heading', { level: 1, name: translations[sample.language].companies, exact: true })).toBeVisible();
+}
+
+async function locked(dialog: Locator, language: Sample['language'] = 'en') {
+ const t = translations[language];
+ for (const label of Object.values(fields(language))) await expect(dialog.getByLabel(label, { exact: true })).toBeDisabled();
+ await expect(dialog.getByRole('button', { name: t.cancel, exact: true })).toBeDisabled();
+ await expect(dialog.getByRole('button', { name: t.save, exact: true })).toBeDisabled();
+}
+
+async function capture(page: Page, info: TestInfo, sample: Sample, state: string) {
+ await page.evaluate(async () => { await document.fonts.ready; await new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(() => resolve()))); });
+ const geometry = await page.evaluate(() => ({ width: innerWidth, height: innerHeight,
+ rootOverflow: Math.max(0, document.documentElement.scrollWidth - document.documentElement.clientWidth),
+ visibleHorizontalBounds: [...document.querySelectorAll('body *')]
+ .filter(element => element.getClientRects().length > 0 && getComputedStyle(element).visibility !== 'hidden' && !element.closest('[hidden],[aria-hidden="true"]'))
+ .map(element => { const rect = element.getBoundingClientRect(); return { tag: element.tagName, role: element.getAttribute('role'), label: element.getAttribute('aria-label'), text: element.textContent?.slice(0, 100), left: rect.left, right: rect.right, width: rect.width,
+ insideNamedScrollRegion: element.closest('[role="region"][aria-label]')?.getAttribute('aria-label') ?? null }; })
+ .filter(element => element.width > 0 && (element.left < -1 || element.right > innerWidth + 1)).slice(0, 30),
+ scrollRegions: [...document.querySelectorAll('[role="region"][aria-label]')].map(element => {
+ const rect = element.getBoundingClientRect(); return { label: element.getAttribute('aria-label'), tabIndex: element.tabIndex, clientWidth: element.clientWidth,
+ scrollWidth: element.scrollWidth, clientHeight: element.clientHeight, scrollHeight: element.scrollHeight, left: rect.left, right: rect.right,
+ overflowX: getComputedStyle(element).overflowX, overflowY: getComputedStyle(element).overflowY };
+ }),
+ }));
+ const axe = await new AxeBuilder({ page }).options({ rules: { 'target-size': { enabled: true } } }).analyze();
+ const name = `p7-companies-${sample.language}-${sample.theme}-${sample.width}-${state}`;
+ await info.attach(`${name}-audit`, { body: JSON.stringify({ sample, state, geometry, ariaSnapshot: await page.locator('body').ariaSnapshot(), boundary,
+ axe: { scope: 'All enabled rules plus target-size; all violations/incomplete vectors retained', violations: axe.violations, incomplete: axe.incomplete,
+ passes: axe.passes.map(rule => ({ id: rule.id, nodes: rule.nodes.length })) },
+ qualifications: ['Finite Chromium states, not a Cartesian width/language/theme/state claim.',
+ 'A named bounded desktop company table may legitimately scroll in two dimensions; its geometry is recorded separately from app-root overflow.',
+ 'Axe incomplete findings require contextual triage; automated keyboard/focus checks do not establish human screen-reader acceptance.',
+ 'Real zoom, physical touch/software keyboard, deployed fonts/photos and production acceptance remain unverified.'],
+ }), contentType: 'application/json' });
+ await info.attach(name, { body: await page.screenshot({ fullPage: true, animations: 'disabled' }), contentType: 'image/png' });
+ expect(geometry.rootOverflow, `${name} app chrome reflows`).toBe(0);
+ expect(axe.violations, `${name} complete axe violations`).toEqual([]);
+}
+
+test('[P7-05 Companies] nine-field save persists and owned relationship projects latest20 excluding deleted jobs', async ({ page, session, playwright }, info) => {
+ test.setTimeout(120_000);
+ const company = await createCompany(page, session.owner, 'Recorded company relationship');
+ const first = await createJob(page, session.owner, company, 'Owned application A', 'Applied');
+ const second = await createJob(page, session.owner, company, 'Owned application B', 'Interview');
+ const deleted = await createJob(page, session.owner, company, 'Deleted application must remain absent');
+ const contacts: Contact[] = [];
+ for (let index = 0; index < 22; index++) contacts.push(await createContact(page, session.owner, index % 2 === 0 ? first : second, index));
+ const deletedContact = await createContact(page, session.owner, deleted, 90, 'Deleted-job private contact must remain absent');
+ expect((await page.request.delete(`${apiUrl}/jobapplications/${deleted.id}`, { headers: session.owner.headers })).status()).toBe(204);
+ await enter(page, session.owner);
+ await keyboardActivate(page, edit(page, company));
+ const draft = draftFor(company);
+ await fillDraft(editDialog(page), draft);
+ const savedResponse = response(page, 'PUT', `${companiesUrl}/${company.id}`);
+ await keyboardActivate(page, editDialog(page).getByRole('button', { name: translations.en.save, exact: true }));
+ const saved = await json(await savedResponse);
+ expect(saved).toMatchObject({ id: company.id, name: draft.name, location: 'Bergen', source: 'LinkedIn', recruiterName: 'Rekrutterer Æ Ø Å',
+ recruiterEmail: 'reviewed@example.test', recruiterLinkedIn: 'https://example.test/recruiter', pipelineStage: 'Interview' });
+ expect(saved.lastContactedAt?.slice(0, 10)).toBe(draft.lastContactedAt);
+ expect(saved.nextContactAt?.slice(0, 10)).toBe(draft.nextContactAt);
+ expect(session.requests.filter(request => request.method === 'PUT' && request.path === `/api/companies/${company.id}`)).toEqual([
+ { method: 'PUT', path: `/api/companies/${company.id}`, query: {}, body: snapshot(draft) },
+ ]);
+ expect(await nativeCompany(page, saved)).toEqual(saved);
+ await expect(editDialog(page)).toHaveCount(0);
+ await expect(edit(page, saved)).toBeFocused();
+ await page.reload(); await expect(edit(page, saved)).toBeVisible();
+ expect(await nativeList(page.request)).toEqual([saved]);
+ await capture(page, info, desktop, 'saved-reloaded');
+ const loaded = response(page, 'GET', `${companiesUrl}/${company.id}/relationship`);
+ await keyboardActivate(page, relationshipAction(page, saved));
+ const relationship = await json(await loaded);
+ expect(relationship.company).toEqual(saved);
+ expect(relationship.applications.map(row => row.id).sort((a, b) => a - b)).toEqual([first.id, second.id].sort((a, b) => a - b));
+ for (const job of [first, second]) {
+ const expected = contacts.filter(contact => contact.jobApplicationId === job.id);
+ const application = relationship.applications.find(row => row.id === job.id);
+ expect(application).toMatchObject({ jobTitle: job.jobTitle, status: job.status, messageCount: 11 });
+ // SQLite reloads DateTime without its Kind suffix. Check every recorded UTC
+ // calendar second without inventing a retained JSON-Z formatting contract.
+ expect(application?.lastContactAt?.slice(0, 19)).toBe(expected.at(-1)!.date.slice(0, 19));
+ }
+ expect(relationship.recentContacts.map(contact => ({ ...contact, date: contact.date.slice(0, 19) }))).toEqual(contacts.slice(2).reverse().map(contact => ({ id: contact.id, jobApplicationId: contact.jobApplicationId,
+ jobTitle: contact.jobApplicationId === first.id ? first.jobTitle : second.jobTitle, date: contact.date.slice(0, 19), direction: contact.direction,
+ channel: contact.channel, subject: contact.subject, from: contact.from })));
+ const dialog = page.getByRole('dialog', { name: saved.name, exact: true });
+ for (const contact of contacts.slice(2)) await expect(dialog.getByText(contact.subject, { exact: true })).toBeVisible();
+ for (const excluded of [contacts[0].subject, contacts[1].subject, deleted.jobTitle, deletedContact.subject]) await expect(dialog.getByText(excluded, { exact: true })).toHaveCount(0);
+ await expect(dialog.getByText(translations.en.companiesMessageCount.replace('{count}', '11'), { exact: false })).toHaveCount(2);
+ await capture(page, info, desktop, 'latest20-owned-relationship');
+ await keyboardActivate(page, dialog.getByRole('button', { name: translations.en.close, exact: true }));
+ await expect(relationshipAction(page, saved)).toBeFocused();
+
+ const admin = await playwright.request.newContext(); const foreign = await playwright.request.newContext(); const anonymous = await playwright.request.newContext();
+ const denials: Array<{ operation: string; status: number }> = [];
+ try {
+ const administrator = await login(admin, 'e2e@example.test');
+ const other = await login(foreign, await createAccount(admin, administrator));
+ expect(await nativeList(foreign)).toEqual([]);
+ expect(await json(await foreign.get(`${companiesUrl}?ownerUserId=${session.owner.id}&id=${saved.id}`))).toEqual([]);
+ for (const [operation, method, suffix, data] of [
+ ['foreign detail', 'GET', `/${saved.id}`, undefined],
+ ['foreign relationship', 'GET', `/${saved.id}/relationship`, undefined],
+ ['foreign nine-field update', 'PUT', `/${saved.id}?ownerUserId=${session.owner.id}`, { ...snapshot(draft), name: 'Forbidden rename', ownerUserId: session.owner.id }],
+ ] as const) {
+ const denied = await foreign.fetch(`${companiesUrl}${suffix}`, { method, headers: other.headers, data });
+ denials.push({ operation, status: denied.status() }); expect(denied.status(), operation).toBe(404);
+ }
+ const foreignCompany = await json(await foreign.post(companiesUrl, { headers: other.headers,
+ data: { name: 'Other account company', location: 'Tromsø', source: 'User', ownerUserId: session.owner.id } }), 201);
+ expect(await nativeList(foreign)).toEqual([foreignCompany]);
+ expect((await page.request.get(`${companiesUrl}/${foreignCompany.id}/relationship`)).status()).toBe(404);
+ expect((await page.request.put(`${companiesUrl}/${foreignCompany.id}`, { headers: session.owner.headers, data: snapshot(draft) })).status()).toBe(404);
+ expect((await foreign.post(`${apiUrl}/jobapplications`, { headers: other.headers, data: { companyId: saved.id, jobTitle: 'Forbidden association', status: 'Saved' } })).status()).toBe(400);
+ for (const [operation, method, suffix, data] of [
+ ['anonymous list', 'GET', '', undefined], ['anonymous detail', 'GET', `/${saved.id}`, undefined],
+ ['anonymous relationship', 'GET', `/${saved.id}/relationship`, undefined],
+ ['anonymous create', 'POST', '', { name: 'Unauthorized', location: null, source: null }],
+ ['anonymous update', 'PUT', `/${saved.id}`, snapshot(draft)],
+ ] as const) {
+ const denied = await anonymous.fetch(`${companiesUrl}${suffix}`, { method, data });
+ denials.push({ operation, status: denied.status() }); expect(denied.status(), operation).toBe(401);
+ }
+ expect(await nativeList(page.request)).toEqual([saved]); expect(await nativeCompany(page, saved)).toEqual(saved);
+ expect(await json(await page.request.get(`${companiesUrl}/${saved.id}/relationship`))).toEqual(relationship);
+ await info.attach('companies-native-persistence-and-isolation', { body: JSON.stringify({ company: saved, relationship, deletedJobId: deleted.id,
+ excludedDeletedContactId: deletedContact.id, denials, boundary, qualification: 'Actual native owner/foreign/anonymous HTTP and SQLite readbacks; no intercepted response in these admission/projection checks.' }), contentType: 'application/json' });
+ } finally { await admin.dispose(); await foreign.dispose(); await anonymous.dispose(); }
+});
+
+test('[P7-05 Companies] held save locks one snapshot, retains a failed draft and recovers through its original Save', async ({ page, session }, info) => {
+ test.setTimeout(120_000);
+ const sample: Sample = { language: 'nb', theme: 'dark', width: 360, height: 844 }; const t = translations.nb;
+ const company = await createCompany(page, session.owner, 'Recorded held company'); const draft = draftFor(company);
+ const failure = hold(); const recovery = hold(); let writes = 0;
+ await page.route(`${companiesUrl}/${company.id}`, async route => {
+ if (route.request().method() !== 'PUT') return route.continue();
+ writes++;
+ if (writes === 1) { await failure.pending; return route.fulfill({ status: 503, json: {} }); }
+ await recovery.pending; return route.continue();
+ });
+ await enter(page, session.owner, sample); await keyboardActivate(page, edit(page, company, 'nb'));
+ const dialog = editDialog(page, 'nb'); await fillDraft(dialog, draft, 'nb');
+ const failedResponse = response(page, 'PUT', `${companiesUrl}/${company.id}`);
+ try {
+ await keyboardActivate(page, dialog.getByRole('button', { name: t.save, exact: true }));
+ await expect.poll(() => writes).toBe(1); await locked(dialog, 'nb');
+ await expect(editRegion(dialog, 'nb')).toBeFocused(); await recordFocus(page, info, 'held-save-stable-region');
+ await expect(dialog.getByRole('progressbar', { name: t.loading, exact: true })).toBeVisible();
+ await expect(dialog.getByRole('status')).toHaveText(t.loading); await expect(dialog.getByRole('status')).toHaveAttribute('aria-live', 'polite');
+ await page.keyboard.press('Enter'); await page.keyboard.press('Escape'); await expect(dialog).toBeVisible(); expect(writes).toBe(1);
+ expect(await nativeCompany(page, company)).toEqual(company);
+ await capture(page, info, sample, 'held-save');
+ } finally { failure.release(); }
+ expect((await failedResponse).status()).toBe(503);
+ await expect(dialog.getByRole('alert')).toHaveText(t.companiesUpdateFailed); await expectDraft(dialog, draft, 'nb');
+ await expect(dialog.getByRole('button', { name: t.save, exact: true })).toBeEnabled();
+ await expect(dialog.getByRole('button', { name: t.cancel, exact: true })).toBeEnabled();
+ await expect(dialog.getByRole('button', { name: t.retry, exact: true })).toHaveCount(0);
+ expect(await nativeCompany(page, company)).toEqual(company); await capture(page, info, sample, 'failed-save-durable-draft');
+ const recoveredResponse = response(page, 'PUT', `${companiesUrl}/${company.id}`);
+ try {
+ await keyboardActivate(page, dialog.getByRole('button', { name: t.save, exact: true }));
+ await expect.poll(() => writes).toBe(2); await locked(dialog, 'nb'); await expect(editRegion(dialog, 'nb')).toBeFocused();
+ await page.keyboard.press('Enter'); expect(writes).toBe(2); await capture(page, info, sample, 'recovery-save-held');
+ } finally { recovery.release(); }
+ const saved = await json(await recoveredResponse); await expect(dialog).toHaveCount(0);
+ await expect(edit(page, saved, 'nb')).toBeFocused(); await recordFocus(page, info, 'recovered-save-invoker');
+ expect(session.requests.filter(request => request.method === 'PUT')).toEqual([1, 2].map(() => ({ method: 'PUT', path: `/api/companies/${company.id}`, query: {}, body: snapshot(draft) })));
+ expect(await nativeCompany(page, saved)).toEqual(saved); await page.reload(); await expect(edit(page, saved, 'nb')).toBeVisible();
+ await keyboardActivate(page, edit(page, saved, 'nb')); await page.keyboard.press('Escape');
+ await expect(editDialog(page, 'nb')).toHaveCount(0); await expect(edit(page, saved, 'nb')).toBeFocused(); expect(writes).toBe(2);
+});
+
+test('[P7-05 Companies] late relationship A cannot replace B and Retry reads the same company with stable focus', async ({ page, session }, info) => {
+ test.setTimeout(150_000);
+ const t = translations.en; const sample: Sample = { ...desktop, width: 390, height: 844, theme: 'dark' };
+ const a = await createCompany(page, session.owner, 'Recorded relationship A'); const b = await createCompany(page, session.owner, 'Recorded relationship B');
+ const aJob = await createJob(page, session.owner, a, 'A application remains separate'); const bJob = await createJob(page, session.owner, b, 'B current application', 'Interview');
+ const aUrl = `${companiesUrl}/${a.id}/relationship`; const bUrl = `${companiesUrl}/${b.id}/relationship`;
+ await enter(page, session.owner, sample); await expect(edit(page, a)).toBeVisible(); const listReads = count(session.requests, 'GET', '/api/companies');
+ for (const outcome of ['success', 'error'] as const) {
+ const gate = hold(); let starts = 0;
+ await page.route(aUrl, async route => { starts++; await gate.pending;
+ return outcome === 'success' ? route.continue() : route.fulfill({ status: 503, json: {} }); });
+ const lateResponse = response(page, 'GET', aUrl);
+ try {
+ await keyboardActivate(page, relationshipAction(page, a));
+ const pending = page.getByRole('dialog', { name: a.name, exact: true });
+ await expect.poll(() => starts).toBe(1); await expect(pending.getByRole('progressbar', { name: t.loading, exact: true })).toBeVisible();
+ await expect(pending.getByText(aJob.jobTitle, { exact: true })).toHaveCount(0);
+ await keyboardActivate(page, pending.getByRole('button', { name: t.close, exact: true }));
+ await expect(pending).toHaveCount(0); await expect(relationshipAction(page, a)).toBeFocused();
+ await keyboardActivate(page, relationshipAction(page, b));
+ const current = page.getByRole('dialog', { name: b.name, exact: true });
+ await expect(current.getByText(bJob.jobTitle, { exact: true })).toBeVisible();
+ gate.release(); expect((await lateResponse).status()).toBe(outcome === 'success' ? 200 : 503);
+ await expect(current).toBeVisible(); await expect(current.getByText(bJob.jobTitle, { exact: true })).toBeVisible();
+ await expect(current.getByText(aJob.jobTitle, { exact: true })).toHaveCount(0); await expect(current.getByRole('alert')).toHaveCount(0);
+ await keyboardActivate(page, current.getByRole('button', { name: t.close, exact: true }));
+ await expect(current).toHaveCount(0); await expect(relationshipAction(page, b)).toBeFocused();
+ } finally { gate.release(); await page.unroute(aUrl); }
+ }
+ const retryGate = hold(); let attempts = 0; let forbidden = false;
+ await page.route(bUrl, async route => {
+ attempts++;
+ if (forbidden) return route.fulfill({ status: 403, json: {} });
+ if (attempts === 1) return route.fulfill({ status: 503, json: {} });
+ await retryGate.pending; return route.continue();
+ });
+ await keyboardActivate(page, relationshipAction(page, b)); const dialog = page.getByRole('dialog', { name: b.name, exact: true });
+ await expect(dialog.getByRole('alert')).toContainText(t.companiesRelationshipLoadFailed); await expect(dialog.getByText(bJob.jobTitle, { exact: true })).toHaveCount(0);
+ const retryResponse = response(page, 'GET', bUrl);
+ try {
+ await keyboardActivate(page, dialog.getByRole('button', { name: t.retry, exact: true }));
+ await expect.poll(() => attempts).toBe(2); await expect(relationshipRegion(dialog)).toBeFocused();
+ await recordFocus(page, info, 'relationship-retry-held');
+ await expect(dialog.getByRole('progressbar', { name: t.loading, exact: true })).toBeVisible();
+ await expect(dialog.getByRole('status')).toHaveAttribute('aria-live', 'polite'); await expect(dialog.getByRole('status')).toHaveText(t.loading);
+ await page.keyboard.press('Enter'); expect(attempts).toBe(2); await capture(page, info, sample, 'relationship-retry-held');
+ } finally { retryGate.release(); }
+ expect((await retryResponse).status()).toBe(200); await expect(dialog.getByText(bJob.jobTitle, { exact: true })).toBeVisible();
+ await expect(relationshipRegion(dialog)).toBeFocused(); await expect(dialog.getByRole('button', { name: t.retry, exact: true })).toHaveCount(0);
+ expect(count(session.requests, 'GET', '/api/companies')).toBe(listReads);
+ expect(count(session.requests, 'GET', `/api/companies/${a.id}/relationship`)).toBe(2);
+ expect(count(session.requests, 'GET', `/api/companies/${b.id}/relationship`)).toBe(4);
+ await capture(page, info, sample, 'relationship-recovered');
+ await keyboardActivate(page, dialog.getByRole('button', { name: t.close, exact: true })); await expect(dialog).toHaveCount(0);
+ forbidden = true; await keyboardActivate(page, relationshipAction(page, b));
+ const denied = page.getByRole('dialog', { name: b.name, exact: true });
+ await expect(denied.getByRole('alert')).toContainText(t.companiesRelationshipLoadFailed);
+ await expect(denied.getByRole('button', { name: t.retry, exact: true })).toHaveCount(0);
+ await expect(denied.getByText(bJob.jobTitle, { exact: true })).toHaveCount(0);
+ await info.attach('companies-relationship-read-scope', { body: JSON.stringify({ aId: a.id, bId: b.id, listReads, attempts,
+ qualification: 'A success and B Retry continue to native owner GET; late A503/B503/B403 are explicit presentation fixtures. Real authorization denials are exercised separately.' }), contentType: 'application/json' });
+});
+
+test('[P7-05 Companies] edit deep link preserves query hash state and Back; open drafts survive767/768 transition', async ({ page, session }, info) => {
+ test.setTimeout(90_000);
+ const sample: Sample = { language: 'nb', theme: 'light', width: 767, height: 900 }; const t = translations.nb;
+ const company = await createCompany(page, session.owner, 'Recorded deep-link company');
+ const job = await createJob(page, session.owner, company, 'Recorded relationship navigation');
+ await enter(page, session.owner, sample); await expect(edit(page, company, 'nb')).toBeVisible();
+ await page.goto('/jobs?previous=companies-context#prior'); await expect(page.getByRole('heading', { level: 1 })).toBeVisible();
+ await page.addInitScript(() => {
+ if (location.pathname === '/companies' && new URLSearchParams(location.search).has('edit')) {
+ window.history.replaceState({ ...window.history.state, usr: { source: 'relationship-history', preservedMarker: 'p7-companies' } }, '');
+ }
+ });
+ await page.goto(`/companies?edit=${company.id}&source=relationships&filter=active#preserved-context`);
+ const dialog = editDialog(page, 'nb'); await expect(dialog).toBeVisible();
+ await expect(page).toHaveURL(/\/companies\?source=relationships&filter=active#preserved-context$/);
+ expect(await page.evaluate(() => window.history.state.usr)).toEqual({ source: 'relationship-history', preservedMarker: 'p7-companies' });
+ const reads = count(session.requests, 'GET', '/api/companies');
+ const name = dialog.getByLabel(t.companiesName, { exact: true }); const recruiter = dialog.getByLabel(t.companiesRecruiterName, { exact: true });
+ await name.fill('Unsaved responsive company draft Æ Ø Å'); await recruiter.fill('Unsaved responsive recruiter');
+ await recruiter.focus(); await expect(recruiter).toBeFocused();
+ expect(await dialog.evaluate(element => element.classList.contains('MuiDialog-paperFullScreen'))).toBe(true);
+ await page.setViewportSize({ width: 768, height: 900 });
+ await expect.poll(() => dialog.evaluate(element => element.classList.contains('MuiDialog-paperFullScreen'))).toBe(false);
+ await expect(name).toHaveValue('Unsaved responsive company draft Æ Ø Å'); await expect(recruiter).toHaveValue('Unsaved responsive recruiter');
+ await expect(recruiter).toBeFocused(); expect(count(session.requests, 'GET', '/api/companies')).toBe(reads);
+ await capture(page, info, { ...sample, width: 768 }, 'open-draft-breakpoint-transition');
+ await keyboardActivate(page, dialog.getByRole('button', { name: t.cancel, exact: true })); await expect(dialog).toHaveCount(0);
+ await recordFocus(page, info, 'responsive-cancel-invoker'); await expect(edit(page, company, 'nb')).toBeFocused();
+ expect(count(session.requests, 'PUT', `/api/companies/${company.id}`)).toBe(0); expect(await nativeCompany(page, company)).toEqual(company);
+ // This second opening has an actual focused desktop invoker. Its row is
+ // replaced by the mobile article while the same edit draft stays mounted.
+ await keyboardActivate(page, edit(page, company, 'nb')); await expect(dialog).toBeVisible();
+ await name.fill('Manually opened responsive draft Æ Ø Å'); await recruiter.fill('Manually opened responsive recruiter');
+ await recruiter.focus(); await expect(recruiter).toBeFocused();
+ expect(await dialog.evaluate(element => element.classList.contains('MuiDialog-paperFullScreen'))).toBe(false);
+ await page.setViewportSize({ width: 767, height: 900 });
+ await expect.poll(() => dialog.evaluate(element => element.classList.contains('MuiDialog-paperFullScreen'))).toBe(true);
+ await expect(name).toHaveValue('Manually opened responsive draft Æ Ø Å'); await expect(recruiter).toHaveValue('Manually opened responsive recruiter');
+ await expect(recruiter).toBeFocused(); expect(count(session.requests, 'GET', '/api/companies')).toBe(reads);
+ await capture(page, info, sample, 'manual-open-draft-breakpoint-transition');
+ await keyboardActivate(page, dialog.getByRole('button', { name: t.cancel, exact: true })); await expect(dialog).toHaveCount(0);
+ await recordFocus(page, info, 'manual-responsive-cancel-invoker'); await expect(edit(page, company, 'nb')).toBeFocused();
+ expect(count(session.requests, 'PUT', `/api/companies/${company.id}`)).toBe(0); expect(await nativeCompany(page, company)).toEqual(company);
+ await keyboardActivate(page, relationshipAction(page, company, 'nb'));
+ const historyDialog = page.getByRole('dialog', { name: company.name, exact: true }); await expect(historyDialog.getByText(job.jobTitle, { exact: true })).toBeVisible();
+ await keyboardActivate(page, historyDialog.getByRole('button', { name: t.notificationsOpen, exact: true }));
+ await expect(page).toHaveURL(new RegExp(`/jobs/${job.id}$`));
+ await page.goBack(); await expect(edit(page, company, 'nb')).toBeVisible(); await expect(editDialog(page, 'nb')).toHaveCount(0);
+ await expect(page).toHaveURL(/\/companies\?source=relationships&filter=active#preserved-context$/);
+ expect(await page.evaluate(() => window.history.state.usr)).toEqual({ source: 'relationship-history', preservedMarker: 'p7-companies' });
+ await page.goBack(); await expect(page).toHaveURL(/\/jobs\?previous=companies-context#prior$/);
+ await info.attach('companies-route-context', { body: JSON.stringify({ source: 'BrowserRouter history.state.usr set before the deep-link mount to represent an existing router entry.',
+ companyId: company.id, jobId: job.id, qualification: 'Real browser edit-parameter replacement, retained query/hash/state and relationship-to-job Back journey; no new navigation is introduced.' }), contentType: 'application/json' });
+});
+
+test('[P7-05 Companies] live locale changes translate failures and relationship status without refetching or losing drafts', async ({ page, context, session }, info) => {
+ test.setTimeout(90_000);
+ const en = translations.en; const nb = translations.nb;
+ const company = await createCompany(page, session.owner, 'Recorded language-independent company');
+ const job = await createJob(page, session.owner, company, 'Recorded bilingual relationship', 'Interview');
+ await enter(page, session.owner); await expect(edit(page, company)).toBeVisible();
+ const reads = count(session.requests, 'GET', '/api/companies');
+ const preference = response(page, 'PUT', `${apiUrl}/auth/preferences/language`);
+ await keyboardActivate(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(edit(page, company, 'nb')).toBeVisible(); expect(count(session.requests, 'GET', '/api/companies')).toBe(reads);
+ await keyboardActivate(page, relationshipAction(page, company, 'nb'));
+ const relationship = page.getByRole('dialog', { name: company.name, exact: true });
+ await expect(relationship.getByText(job.jobTitle, { exact: true })).toBeVisible();
+ await expect(relationship.getByText(new RegExp(`^${nb.statusInterview} · `))).toBeVisible();
+ await keyboardActivate(page, relationship.getByRole('button', { name: nb.close, exact: true })); await expect(relationship).toHaveCount(0);
+ await page.route(`${companiesUrl}/${company.id}`, route => route.request().method() === 'PUT' ? route.fulfill({ status: 503, json: {} }) : route.continue());
+ await keyboardActivate(page, edit(page, company, 'nb')); const dialog = editDialog(page, 'nb');
+ const draft = draftFor(company, 'Retained locale draft Æ Ø Å'); await fillDraft(dialog, draft, 'nb');
+ await keyboardActivate(page, dialog.getByRole('button', { name: nb.save, exact: true })); await expect(dialog.getByRole('alert')).toHaveText(nb.companiesUpdateFailed);
+ const before = [...session.requests];
+ const otherTab = await context.newPage();
+ try {
+ await otherTab.goto('/companies'); await expect(otherTab.getByRole('heading', { level: 1, name: nb.companies, exact: true })).toBeVisible();
+ await otherTab.evaluate(() => localStorage.setItem('uiLanguage', 'en'));
+ await expect(page.locator('html')).toHaveAttribute('lang', 'en');
+ const translated = editDialog(page);
+ await expect(translated.getByRole('alert')).toHaveText(en.companiesUpdateFailed); await expectDraft(translated, draft);
+ await expect(translated.getByRole('button', { name: en.save, exact: true })).toBeEnabled();
+ expect(session.requests).toEqual(before); expect(count(session.requests, 'GET', '/api/companies')).toBe(reads);
+ await capture(page, info, desktop, 'live-cross-tab-locale-retained-failure');
+ await keyboardActivate(page, translated.getByRole('button', { name: en.cancel, exact: true })); await expect(translated).toHaveCount(0);
+ await expect(edit(page, company)).toBeFocused(); expect(await nativeCompany(page, company)).toEqual(company);
+ } finally { await otherTab.close(); }
+ await info.attach('companies-locale-boundary', { body: JSON.stringify({ initialHeaderChange: 'Actual named Norsk header control and native language-preference204.',
+ modalChange: 'Actual same-origin second browser tab changes localStorage, producing a storage event while the primary modal stays mounted.',
+ recordedCompaniesReads: reads, qualification: 'The primary page request log proves no Companies refetch or duplicate PUT during its locale transition. The second tab has its own independent initial read; no hidden modal-background control was forced.' }), contentType: 'application/json' });
+});
+
+test('[P7-05 Companies] eight finite language/theme widths reflow with named table scrolling and keyboard dialogs', async ({ page, session }, info) => {
+ test.setTimeout(270_000);
+ const company = await createCompany(page, session.owner, 'Samfunnskritiske systemer og rekruttering for nordnorske virksomheter Æ Ø Å — langsiktig samarbeid',
+ 'Tromsø og internasjonalt samarbeid med flere steder i Nord-Norge');
+ const draft: Draft = { name: company.name, location: company.location!, source: 'LinkedIn',
+ recruiterName: 'Rekrutterer med et langt meningsfullt navn Æ Ø Å', recruiterEmail: 'lang-rekruttererkontakt-for-virksomheten@example.test',
+ recruiterLinkedIn: 'https://example.test/recruiter/long-recorded-contact-context', pipelineStage: 'Oppfølging av langsiktig relasjon',
+ lastContactedAt: '2026-11-01', nextContactAt: '2026-12-01' };
+ const saved = await json(await page.request.put(`${companiesUrl}/${company.id}`, { headers: session.owner.headers, data: snapshot(draft) }));
+ const job = await createJob(page, session.owner, saved, 'Senior utvikler for samfunnskritiske systemer med ansvar for kvalitet Æ Ø Å', 'Interview');
+ await createContact(page, session.owner, job, 1, 'Oppfølging av en langsiktig og meningsfull søknadsprosess med et langt emne Æ Ø Å');
+ const samples: Sample[] = [
+ { language: 'en', theme: 'light', width: 320, height: 740 }, { language: 'nb', theme: 'dark', width: 360, height: 844 },
+ { language: 'en', theme: 'dark', width: 390, height: 844 }, { language: 'nb', theme: 'light', width: 767, height: 900 },
+ { language: 'en', theme: 'dark', width: 768, height: 1024 }, { language: 'nb', theme: 'light', width: 1024, height: 320 },
+ { language: 'en', theme: 'light', width: 1440, height: 900 }, { language: 'nb', theme: 'dark', width: 1920, height: 1080 },
+ ];
+ for (const sample of samples) {
+ const t = translations[sample.language]; await enter(page, session.owner, sample); await expect(edit(page, saved, sample.language)).toBeVisible();
+ if (sample.width < 768) {
+ const article = page.getByRole('article', { name: saved.name, exact: true }); await expect(article).toBeVisible();
+ await expect(article.getByRole('heading', { level: 2, name: saved.name, exact: true })).toBeVisible(); await expect(page.getByRole('table', { name: t.companies, exact: true })).toHaveCount(0);
+ } else {
+ const table = page.getByRole('table', { name: t.companies, exact: true }); const region = page.getByRole('region', { name: t.companies, exact: true });
+ await expect(table).toBeVisible(); await expect(region).toHaveAttribute('tabindex', '0');
+ const geometry = await region.evaluate(element => ({ width: element.clientWidth, scrollWidth: element.scrollWidth, left: element.getBoundingClientRect().left, right: element.getBoundingClientRect().right, viewport: innerWidth }));
+ expect(geometry.left).toBeGreaterThanOrEqual(0); expect(geometry.right).toBeLessThanOrEqual(geometry.viewport);
+ await region.focus(); await expect(region).toBeFocused();
+ if (geometry.scrollWidth > geometry.width + 1) {
+ await page.keyboard.press('ArrowRight'); await expect.poll(() => region.evaluate(element => element.scrollLeft)).toBeGreaterThan(0);
+ await region.evaluate(element => { element.scrollLeft = 0; });
+ }
+ }
+ await capture(page, info, sample, 'loaded-finite-sample');
+ if (sample.width === 320) {
+ await keyboardActivate(page, edit(page, saved, sample.language)); const dialog = editDialog(page, sample.language);
+ const save = dialog.getByRole('button', { name: t.save, exact: true }); const cancel = dialog.getByRole('button', { name: t.cancel, exact: true });
+ await save.focus(); await page.keyboard.press('Tab'); await expect(dialog.getByLabel(t.companiesName, { exact: true })).toBeFocused();
+ await page.keyboard.press('Shift+Tab'); await expect(save).toBeFocused();
+ await capture(page, info, sample, 'mobile-edit-keyboard-trap');
+ await keyboardActivate(page, cancel); await expect(dialog).toHaveCount(0); await expect(edit(page, saved, sample.language)).toBeFocused();
+ }
+ if (sample.width === 1024) {
+ await keyboardActivate(page, relationshipAction(page, saved, sample.language)); const dialog = page.getByRole('dialog', { name: saved.name, exact: true });
+ await expect(dialog.getByText(job.jobTitle, { exact: true })).toBeVisible(); const region = relationshipRegion(dialog, sample.language);
+ const scroll = await region.evaluate(element => ({ clientHeight: element.clientHeight, scrollHeight: element.scrollHeight }));
+ expect(scroll.scrollHeight).toBeGreaterThan(scroll.clientHeight);
+ await region.focus(); await page.keyboard.press('PageDown'); await expect.poll(() => region.evaluate(element => element.scrollTop)).toBeGreaterThan(0);
+ const close = dialog.getByRole('button', { name: t.close, exact: true }); await close.focus(); await page.keyboard.press('Tab');
+ await expect(dialog.getByRole('button', { name: t.notificationsOpen, exact: true })).toBeFocused();
+ await page.keyboard.press('Shift+Tab'); await expect(close).toBeFocused();
+ await capture(page, info, sample, 'landscape-relationship-scroll-trap');
+ await keyboardActivate(page, close); await expect(dialog).toHaveCount(0);
+ await recordFocus(page, info, 'landscape-relationship-close');
+ await expect(relationshipAction(page, saved, sample.language)).toBeFocused();
+ }
+ expect(await nativeCompany(page, saved)).toEqual(saved);
+ }
+ expect(count(session.requests, 'PUT', `/api/companies/${saved.id}`)).toBe(0);
+ await info.attach('companies-finite-samples', { body: JSON.stringify({ samples, boundary,
+ qualification: 'Exactly eight balanced English/Bokmål and light/dark samples including767/768 and1024×320 landscape, plus two keyboard modal states. Named table geometry/keyboard horizontal scrolling is measured when it overflows. No Cartesian, real zoom, human AT or physical-device claim.' }), contentType: 'application/json' });
+});
diff --git a/job-tracker-ui/src/companies-relationship.test.tsx b/job-tracker-ui/src/companies-relationship.test.tsx
index 6bbf881..e410e6d 100644
--- a/job-tracker-ui/src/companies-relationship.test.tsx
+++ b/job-tracker-ui/src/companies-relationship.test.tsx
@@ -1,7 +1,7 @@
import React from "react";
import "@testing-library/jest-dom";
-import { fireEvent, render, screen, waitFor, within } from "@testing-library/react";
-import { MemoryRouter } from "react-router-dom";
+import { act, fireEvent, render, screen, waitFor, within } from "@testing-library/react";
+import { MemoryRouter, useLocation, useNavigate } from "react-router-dom";
import { api } from "./api";
import CompaniesTable from "./components/CompaniesTable";
import { I18nProvider } from "./i18n/I18nProvider";
@@ -22,7 +22,7 @@ beforeEach(() => {
test("opens one company relationship view with applications and recent contact", async () => {
const company = { id: 3, name: "Acme", recruiterName: "Rita", recruiterEmail: "rita@example.test" };
mockedApi.get.mockImplementation((url) => Promise.resolve({ data: String(url).endsWith("/relationship") ? {
- company,
+ company: { ...company, name: "Acme refreshed" },
applications: [{ id: 9, jobTitle: "Backend Developer", status: "Interview", messageCount: 1 }],
recentContacts: [{ id: 12, jobApplicationId: 9, jobTitle: "Backend Developer", date: "2026-08-10T10:00:00Z", channel: "email", subject: "Interview", from: "Rita" }],
} : [company] } as any));
@@ -34,16 +34,18 @@ test("opens one company relationship view with applications and recent contact",
expect(screen.getAllByText("Backend Developer").length).toBeGreaterThan(0);
expect(screen.getByText(/Interview · 1 messages/)).toBeInTheDocument();
expect(mockedApi.get).toHaveBeenCalledWith("/companies/3/relationship");
+ expect(await screen.findByRole("dialog", { name: "Acme refreshed" })).toBeInTheDocument();
});
test.each(["en", "nb"])("company contact dates follow the %s UI locale", async (language) => {
window.localStorage.setItem("uiLanguage", language);
const nextContactAt = "2026-11-23T12:00:00Z";
- mockedApi.get.mockResolvedValue({ data: [{ id: 3, name: "Acme", nextContactAt }] });
+ mockedApi.get.mockResolvedValue({ data: [{ id: 3, name: "Acme", nextContactAt, pipelineStage: "Interview" }] });
render();
const expected = new Date(nextContactAt).toLocaleDateString(language === "nb" ? "nb-NO" : "en");
await screen.findByRole("button", { name: `${language === "nb" ? "Rediger" : "Edit"}: Acme` });
expect(screen.getByRole("row", { name: /Acme/ })).toHaveTextContent(expected);
+ expect(screen.getByRole("row", { name: /Acme/ })).toHaveTextContent("Interview");
});
test.each([
@@ -72,3 +74,158 @@ test.each([
expect(await screen.findByRole("button", { name: `${language === "en" ? "Edit" : "Rediger"}: Acme updated` })).toBeVisible();
expect(mockedApi.put).toHaveBeenNthCalledWith(2, "/companies/3", expect.objectContaining({ name: "Acme updated" }));
});
+
+const companyA = { id: 3, name: "Acme", location: "Oslo", source: "User", recruiterName: "Rita", recruiterEmail: "rita@example.test" };
+const companyB = { id: 4, name: "Northstar", location: "Bergen", source: "User" };
+
+function relationship(company: { id: number; name: string } = companyA) {
+ return { data: { company, applications: [{ id: company.id + 10, jobTitle: `${company.name} role`, status: "Interview", messageCount: 1 }], recentContacts: [] } };
+}
+
+function RouteContext() {
+ const location = useLocation();
+ const navigate = useNavigate();
+ return <>>;
+}
+
+function renderPage(initialEntries: any[] = ["/companies"]) {
+ return render();
+}
+
+function deferred() {
+ let resolve!: (value: T) => void;
+ let reject!: (reason: unknown) => void;
+ const promise = new Promise((yes, no) => { resolve = yes; reject = no; });
+ return { promise, resolve, reject };
+}
+
+test("a held company save locks all nine fields and cancellation, submits one exact snapshot, and retains a failed draft", async () => {
+ const write = deferred();
+ mockedApi.get.mockResolvedValue({ data: [companyA] } as any);
+ mockedApi.put.mockImplementationOnce(() => write.promise);
+ renderPage();
+ fireEvent.click(await screen.findByRole("button", { name: "Edit: Acme" }));
+ const dialog = await screen.findByRole("dialog", { name: "Edit Company" });
+ const draft = {
+ Name: "Acme updated", Location: "Bergen", Source: "User", "Pipeline stage": " Interview ",
+ "Recruiter name": " Updated recruiter ", "Recruiter email": " updated@example.test ",
+ "Recruiter LinkedIn": " https://example.test/recruiter ", "Last contacted": "2026-11-21", "Next contact": "2026-11-23",
+ };
+ Object.entries(draft).forEach(([label, value]) => fireEvent.change(within(dialog).getByLabelText(label), { target: { value } }));
+ const save = within(dialog).getByRole("button", { name: "Save" });
+ fireEvent.click(save);
+ expect(mockedApi.put).toHaveBeenCalledWith("/companies/3", {
+ name: "Acme updated", location: "Bergen", source: "User", recruiterName: "Updated recruiter",
+ recruiterEmail: "updated@example.test", recruiterLinkedIn: "https://example.test/recruiter",
+ pipelineStage: "Interview", lastContactedAt: "2026-11-21", nextContactAt: "2026-11-23",
+ });
+ expect(save).toBeDisabled();
+ Object.keys(draft).forEach((label) => expect(within(dialog).getByLabelText(label)).toBeDisabled());
+ expect(within(dialog).getByRole("button", { name: "Cancel" })).toBeDisabled();
+ fireEvent.click(save);
+ fireEvent.click(within(dialog).getByRole("button", { name: "Cancel" }));
+ expect(mockedApi.put).toHaveBeenCalledTimes(1);
+ expect(dialog).toBeVisible();
+ await act(async () => { write.reject(new Error("synthetic save failure")); });
+ expect(await within(dialog).findByRole("alert")).toHaveTextContent("Failed to update company.");
+ Object.entries(draft).forEach(([label, value]) => expect(within(dialog).getByLabelText(label)).toHaveValue(value));
+ expect(save).toBeEnabled();
+ expect(within(dialog).getByRole("button", { name: "Cancel" })).toBeEnabled();
+ expect(mockedApi.put).toHaveBeenCalledTimes(1);
+});
+
+test("a locale change translates an existing company-save failure without refetching or replacing its draft", async () => {
+ mockedApi.get.mockResolvedValue({ data: [companyA] } as any);
+ mockedApi.put.mockRejectedValueOnce(new Error("synthetic save failure"));
+ renderPage();
+ fireEvent.click(await screen.findByRole("button", { name: "Edit: Acme" }));
+ const dialog = await screen.findByRole("dialog");
+ fireEvent.change(within(dialog).getByRole("textbox", { name: "Name" }), { target: { value: "Acme unsaved" } });
+ fireEvent.change(within(dialog).getByRole("textbox", { name: "Recruiter name" }), { target: { value: "Unsaved recruiter" } });
+ fireEvent.click(within(dialog).getByRole("button", { name: "Save" }));
+ expect(await within(dialog).findByRole("alert")).toHaveTextContent("Failed to update company.");
+ const reads = mockedApi.get.mock.calls.length;
+ act(() => { window.dispatchEvent(new StorageEvent("storage", { key: "uiLanguage", newValue: "nb" })); });
+ expect(within(dialog).getByRole("alert")).toHaveTextContent("Kunne ikke oppdatere selskap.");
+ expect(within(dialog).getByRole("textbox", { name: "Navn" })).toHaveValue("Acme unsaved");
+ expect(within(dialog).getByRole("textbox", { name: "Navn på rekrutterer" })).toHaveValue("Unsaved recruiter");
+ expect(mockedApi.get).toHaveBeenCalledTimes(reads);
+ expect(mockedApi.put).toHaveBeenCalledTimes(1);
+});
+
+test("retrying a failed relationship read loads the same company without reloading the company list", async () => {
+ const retry = deferred();
+ let reads = 0;
+ mockedApi.get.mockImplementation((url) => url === "/companies/3/relationship"
+ ? ++reads === 1 ? Promise.reject(new Error("synthetic relationship failure")) : retry.promise
+ : Promise.resolve({ data: [companyA] }) as any);
+ renderPage();
+ fireEvent.click(await screen.findByRole("button", { name: "Relationship history: Acme" }));
+ const dialog = await screen.findByRole("dialog");
+ expect(await within(dialog).findByRole("alert")).toHaveTextContent("Could not load the relationship history.");
+ fireEvent.click(within(dialog).getByRole("button", { name: "Retry" }));
+ expect(reads).toBe(2);
+ expect(mockedApi.get.mock.calls.filter(([url]) => url === "/companies")).toHaveLength(1);
+ expect(within(dialog).queryByText("Acme role")).not.toBeInTheDocument();
+ await act(async () => { retry.resolve(relationship()); });
+ expect(await within(dialog).findByText("Acme role")).toBeInTheDocument();
+ expect(within(dialog).queryByRole("alert")).not.toBeInTheDocument();
+ expect(reads).toBe(2);
+});
+
+test.each(["success", "failure"])("a late %s for relationship A cannot replace the opened relationship B", async (outcome) => {
+ const previous = deferred();
+ mockedApi.get.mockImplementation((url) => url === "/companies/3/relationship" ? previous.promise
+ : Promise.resolve(url === "/companies/4/relationship" ? relationship(companyB) : { data: [companyA, companyB] }) as any);
+ renderPage();
+ fireEvent.click(await screen.findByRole("button", { name: "Relationship history: Acme" }));
+ fireEvent.click(await screen.findByRole("button", { name: "Close" }));
+ await waitFor(() => expect(screen.queryByRole("dialog")).not.toBeInTheDocument());
+ fireEvent.click(screen.getByRole("button", { name: "Relationship history: Northstar" }));
+ const dialog = await screen.findByRole("dialog");
+ expect(await within(dialog).findByText("Northstar role")).toBeInTheDocument();
+ await act(async () => {
+ if (outcome === "success") previous.resolve(relationship());
+ else previous.reject(new Error("late A relationship failure"));
+ });
+ expect(within(dialog).getByText("Northstar role")).toBeInTheDocument();
+ expect(within(dialog).queryByText("Acme role")).not.toBeInTheDocument();
+ expect(within(dialog).queryByRole("alert")).not.toBeInTheDocument();
+ expect(mockedApi.get.mock.calls.filter(([url]) => String(url).endsWith("/relationship"))).toHaveLength(2);
+});
+
+test("a relationship response received after closing stays closed and does not populate another held relationship", async () => {
+ const previous = deferred();
+ const next = deferred();
+ mockedApi.get.mockImplementation((url) => url === "/companies/3/relationship" ? previous.promise
+ : url === "/companies/4/relationship" ? next.promise : Promise.resolve({ data: [companyA, companyB] }) as any);
+ renderPage();
+ fireEvent.click(await screen.findByRole("button", { name: "Relationship history: Acme" }));
+ fireEvent.click(await screen.findByRole("button", { name: "Close" }));
+ await waitFor(() => expect(screen.queryByRole("dialog")).not.toBeInTheDocument());
+ await act(async () => { previous.resolve(relationship()); });
+ expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
+ fireEvent.click(screen.getByRole("button", { name: "Relationship history: Northstar" }));
+ const dialog = await screen.findByRole("dialog");
+ expect(within(dialog).queryByText("Acme role")).not.toBeInTheDocument();
+ expect(within(dialog).queryByText("Northstar role")).not.toBeInTheDocument();
+ await act(async () => { next.resolve(relationship(companyB)); });
+ expect(await within(dialog).findByText("Northstar role")).toBeInTheDocument();
+});
+
+test("the company edit deep link removes only edit while preserving query, hash, state and replace navigation", async () => {
+ mockedApi.get.mockResolvedValue({ data: [companyA] } as any);
+ renderPage([
+ { pathname: "/jobs/9", search: "?keep=true", hash: "#source", state: { previous: true } },
+ { pathname: "/companies", search: "?edit=3&return=jobs%2F9", hash: "#activity", state: { returnTo: "/jobs/9" } },
+ ]);
+ const route = screen.getByLabelText("Route context");
+ expect(await screen.findByRole("dialog", { name: "Edit Company" })).toBeInTheDocument();
+ await waitFor(() => expect(JSON.parse(route.textContent!).search).toBe("?return=jobs%2F9"));
+ expect(JSON.parse(route.textContent!)).toEqual({ pathname: "/companies", search: "?return=jobs%2F9", hash: "#activity", state: { returnTo: "/jobs/9" } });
+ fireEvent.click(screen.getByRole("button", { name: "Cancel" }));
+ await waitFor(() => expect(screen.queryByRole("dialog")).not.toBeInTheDocument());
+ fireEvent.click(screen.getByRole("button", { name: "Previous route" }));
+ expect(JSON.parse(route.textContent!)).toEqual({ pathname: "/jobs/9", search: "?keep=true", hash: "#source", state: { previous: true } });
+ expect(mockedApi.get).toHaveBeenCalledTimes(1);
+});
diff --git a/job-tracker-ui/src/components/CompaniesTable.tsx b/job-tracker-ui/src/components/CompaniesTable.tsx
index dabf22a..e7b3967 100644
--- a/job-tracker-ui/src/components/CompaniesTable.tsx
+++ b/job-tracker-ui/src/components/CompaniesTable.tsx
@@ -1,4 +1,4 @@
-import React, { useEffect, useMemo, useState } from "react";
+import React, { useEffect, useMemo, useRef, useState } from "react";
import { useLocation, useNavigate } from "react-router-dom";
import {
@@ -30,7 +30,8 @@ import EditOutlinedIcon from "@mui/icons-material/EditOutlined";
import PeopleAltOutlinedIcon from "@mui/icons-material/PeopleAltOutlined";
import { useToast } from "../toast";
import { useI18n } from "../i18n/I18nProvider";
-import { useViewResource } from "../hooks/useViewResource";
+import { useViewResource, type ViewResourceError } from "../hooks/useViewResource";
+import { statusLabel } from "../pipeline";
export default function CompaniesTable() {
const isMobile = useMediaQuery("(max-width:767.95px)");
@@ -39,12 +40,22 @@ export default function CompaniesTable() {
const location = useLocation();
const navigate = useNavigate();
const [editOpen, setEditOpen] = useState(false);
- const [editError, setEditError] = useState("");
+ const [editError, setEditError] = useState(null);
const [editing, setEditing] = useState(null);
+ const [saving, setSaving] = useState(false);
+ const savePending = useRef(false);
+ const editContentRef = useRef(null);
+ const editCompanyIdRef = useRef(null);
+ const containerRef = useRef(null);
+ const [focusReturn, setFocusReturn] = useState<{ action: "edit" | "relationship"; id?: number | null } | null>(null);
const [relationshipOpen, setRelationshipOpen] = useState(false);
+ const [relationshipCompany, setRelationshipCompany] = useState(null);
const [relationshipLoading, setRelationshipLoading] = useState(false);
- const [relationshipError, setRelationshipError] = useState("");
+ const [relationshipError, setRelationshipError] = useState(null);
const [relationship, setRelationship] = useState(null);
+ const relationshipSequence = useRef(0);
+ const relationshipPending = useRef(false);
+ const relationshipContentRef = useRef(null);
const [recruiterName, setRecruiterName] = useState("");
const [recruiterEmail, setRecruiterEmail] = useState("");
@@ -60,14 +71,14 @@ export default function CompaniesTable() {
},
{
initialData: [],
- errorMessage: t("companiesLoadFailed"),
- deps: [t],
+ errorMessage: "",
},
);
const companies = companiesResource.data;
useEffect(() => {
+ if (savePending.current) return;
const params = new URLSearchParams(location.search);
const editId = Number(params.get("edit") || 0);
if (!editId || companies.length === 0) return;
@@ -75,11 +86,22 @@ export default function CompaniesTable() {
if (!company) return;
openEdit(company);
params.delete("edit");
- navigate({ pathname: location.pathname, search: params.toString() ? `?${params.toString()}` : "" }, { replace: true });
- }, [companies, location.pathname, location.search, navigate]);
+ navigate({ pathname: location.pathname, search: params.toString() ? `?${params.toString()}` : "", hash: location.hash }, { replace: true, state: location.state });
+ }, [companies, location.pathname, location.search, location.hash, location.state, navigate, saving]);
+
+ useEffect(() => () => { relationshipSequence.current += 1; }, []);
+
+ useEffect(() => {
+ // Restore after MUI has removed the exiting modal.
+ if (focusReturn?.id && document.activeElement === document.body) {
+ containerRef.current?.querySelector(`[data-company-${focusReturn.action}="${focusReturn.id}"]`)?.focus();
+ }
+ }, [focusReturn]);
const openEdit = (c: Company) => {
- setEditError("");
+ if (savePending.current) return;
+ editCompanyIdRef.current = c.id;
+ setEditError(null);
setEditing(c);
setRecruiterName(c.recruiterName ?? "");
setRecruiterEmail(c.recruiterEmail ?? "");
@@ -91,22 +113,40 @@ export default function CompaniesTable() {
};
const openRelationship = async (company: Company) => {
- setRelationshipOpen(true); setRelationshipLoading(true); setRelationshipError(""); setRelationship(null);
+ const request = ++relationshipSequence.current;
+ relationshipPending.current = true;
+ setRelationshipCompany(company);
+ setRelationshipOpen(true); setRelationshipLoading(true); setRelationshipError(null); setRelationship(null);
try {
const response = await api.get(`/companies/${company.id}/relationship`);
- setRelationship(response.data);
+ if (request === relationshipSequence.current) setRelationship(response.data);
} catch (error) {
- setRelationshipError(getApiErrorMessage(error, t("companiesRelationshipLoadFailed")));
+ if (request !== relationshipSequence.current) return;
+ const status = (error as { response?: { status?: number } })?.response?.status;
+ const unauthorized = status === 401 || status === 403;
+ setRelationshipError({ kind: unauthorized ? "unauthorized" : "unavailable", message: getApiErrorMessage(error, ""), retryable: !unauthorized, status });
} finally {
- setRelationshipLoading(false);
+ if (request === relationshipSequence.current) {
+ relationshipPending.current = false;
+ setRelationshipLoading(false);
+ }
}
};
+ const closeRelationship = () => {
+ relationshipSequence.current += 1;
+ relationshipPending.current = false;
+ setRelationshipOpen(false);
+ };
+
const canSave = useMemo(() => !!editing?.id, [editing]);
const save = async () => {
- if (!editing?.id) return;
- setEditError("");
+ if (!editing?.id || savePending.current) return;
+ savePending.current = true;
+ editContentRef.current?.focus();
+ setSaving(true);
+ setEditError(null);
try {
const res = await api.put(`/companies/${editing.id}`, {
name: editing.name,
@@ -125,7 +165,10 @@ export default function CompaniesTable() {
setEditOpen(false);
setEditing(null);
} catch (error) {
- setEditError(getApiErrorMessage(error, t("companiesUpdateFailed")));
+ setEditError(getApiErrorMessage(error, ""));
+ } finally {
+ savePending.current = false;
+ setSaving(false);
}
};
@@ -137,10 +180,10 @@ export default function CompaniesTable() {
);
return (
-
+
{companies.map((c) => (
-
+
-
- {c.name}
+
+ {c.name}
{c.location || t("companiesLocation")}
- openEdit(c)}>
+ openEdit(c)}>
- void openRelationship(c)}>
+ void openRelationship(c)}>
@@ -181,8 +224,8 @@ export default function CompaniesTable() {
) : null}
) : (
-
-
+
+
{t("companiesName")}
@@ -191,26 +234,26 @@ export default function CompaniesTable() {
{t("companiesPipeline")}
{t("companiesRecruiter")}
{t("companiesNextContact")}
-
+ {t("jobTableActions")}
{companies.map((c) => (
- {c.name}
+ {c.name}
{c.location ?? ""}
- {c.source ?? ""}
- {c.pipelineStage ?? ""}
+ {c.source ?? ""}
+ {c.pipelineStage ?? ""}
{c.recruiterName ?? ""}
{c.recruiterEmail ? ` (${c.recruiterEmail})` : ""}
- {c.nextContactAt ? new Date(c.nextContactAt).toLocaleDateString(language === "nb" ? "nb-NO" : "en") : ""}
-
- void openRelationship(c)}>
+ {c.nextContactAt ? new Date(c.nextContactAt).toLocaleDateString(language === "nb" ? "nb-NO" : "en") : ""}
+
+ void openRelationship(c)}>
- openEdit(c)}>
+ openEdit(c)}>
@@ -231,45 +274,54 @@ export default function CompaniesTable() {
)
) : null}
-