diff --git a/docs/redesign/phase-7/p7-12/ADMIN_HELP.md b/docs/redesign/phase-7/p7-12/ADMIN_HELP.md new file mode 100644 index 0000000..38d16e8 --- /dev/null +++ b/docs/redesign/phase-7/p7-12/ADMIN_HELP.md @@ -0,0 +1,11 @@ +# Last-administrator help semantics + +The combined fixture review retained28axe scans with0violations and2155incomplete node observations. Most are overlapping/obscured Audit-table contrast contexts at the full fixture's larger row count; ten prohibited-name observations include roleless disabled-action Tooltip wrappers and MUI DataGrid Sort spans. These are triaged contexts, not a claim of human acceptance or2155confirmed defects. + +The confirmed owner correction applies the existing `describeChild` pattern to the four last-administrator help Tooltips on mobile cards and the desktop grid. A roleless wrapper no longer receives an accessible name; the disabled Remove admin/Delete buttons retain their own names, and their unchanged help remains available. No permission, disabled condition, confirmation, API payload or data operation changes. + +The existing final-administrator component case now asserts that the wrapper has no `aria-label` and that hovering still reveals the same help text. It fails before the correction (9PASS/1FAIL) and passes afterward (10/10). No case is added or assertion removed. Full frontend inventory remains899/101. + +[Portable evidence](evidence/admin-help.json) records the10component cases, all3native Users cases with zero skips/flaky/retries, and7all-enabled axe scans with0violations.21contrast incomplete nodes remain in overlap/obscured/short-badge contexts; four prohibited-name observations are MUI DataGrid Sort spans. The final-administrator wrapper names are no longer observed. Their raw contexts remain retained; no rule or assertion is disabled. + +Fresh production build, application/browser TypeScript and lint pass. The native run includes actual final-admin409, ordinary-user403, persisted role/session changes, disabled deletion503, GET-only recovery and bilingual mobile/desktop widths. Native viewport/keyboard evidence is not human screen-reader, physical touch, real zoom or production acceptance. Whole integrated successor execution remains P7-12's final gate. PRES-A01/E08/E09 and all57group dispositions remain; no Phase8 implementation. diff --git a/docs/redesign/phase-7/p7-12/evidence/admin-help-static-checks.json b/docs/redesign/phase-7/p7-12/evidence/admin-help-static-checks.json new file mode 100644 index 0000000..b40cf05 --- /dev/null +++ b/docs/redesign/phase-7/p7-12/evidence/admin-help-static-checks.json @@ -0,0 +1,104 @@ +{ + "schemaVersion": 1, + "checkpoint": "p7-12", + "status": "PASS", + "checkedUtc": "2026-10-21T05:22:54.393478+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-21T05:22:50.908124+00:00", + "finishedUtc": "2026-10-21T05:22:53.599465+00:00", + "exitCode": 0, + "log": ".artifacts/phase7/current/static/20261021T052250Z/secret-scan.log", + "logSha256Raw": "be5119e6057287d397e87759f855c91e634e3bfac61c8be7584d05592ba2dfcd" + }, + { + "name": "working-whitespace", + "command": [ + "rtk", + "proxy", + "git", + "diff", + "--check" + ], + "startedUtc": "2026-10-21T05:22:53.599948+00:00", + "finishedUtc": "2026-10-21T05:22:53.686928+00:00", + "exitCode": 0, + "log": ".artifacts/phase7/current/static/20261021T052250Z/working-whitespace.log", + "logSha256Raw": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "name": "staged-whitespace", + "command": [ + "rtk", + "proxy", + "git", + "diff", + "--cached", + "--check" + ], + "startedUtc": "2026-10-21T05:22:53.687422+00:00", + "finishedUtc": "2026-10-21T05:22:53.776484+00:00", + "exitCode": 0, + "log": ".artifacts/phase7/current/static/20261021T052250Z/staged-whitespace.log", + "logSha256Raw": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "name": "phase7-whitespace", + "command": [ + "rtk", + "proxy", + "git", + "diff", + "--check", + "b5319981ad3d1bd635daff861453d23c0de5320b" + ], + "startedUtc": "2026-10-21T05:22:53.776984+00:00", + "finishedUtc": "2026-10-21T05:22:53.975095+00:00", + "exitCode": 0, + "log": ".artifacts/phase7/current/static/20261021T052250Z/phase7-whitespace.log", + "logSha256Raw": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + } + ], + "untrackedSecretScan": { + "files": 0, + "findings": 0 + }, + "documentation": { + "files": 43, + "relativeLinks": 669, + "errors": [] + }, + "preservation": { + "groups": 57, + "statuses": { + "PARTIAL": 56, + "PASS": 0, + "FAIL": 0, + "DEFERRED": 1 + }, + "originalRoutesPreserved": true, + "currentConcreteRoutes": 33 + }, + "production": { + "main": "8699ae4f421bb1d049b937e735e40a4797a4b719", + "originMain": "8699ae4f421bb1d049b937e735e40a4797a4b719", + "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-12/evidence/admin-help.json b/docs/redesign/phase-7/p7-12/evidence/admin-help.json new file mode 100644 index 0000000..bbbe99a --- /dev/null +++ b/docs/redesign/phase-7/p7-12/evidence/admin-help.json @@ -0,0 +1,108 @@ +{ + "observedUtc": "2026-10-21T05:22:23.564754+00:00", + "scope": "Existing last-administrator disabled-action Tooltip wrappers on mobile cards and desktop grid use describeChild. Labels, disabled state, help text, confirmation, server admission and writes are unchanged. Existing component case strengthened; zero added cases.", + "unit": { + "p712-admin-tooltip-red": { + "path": ".artifacts/phase7/current/unit/p712-admin-tooltip-red-20261021T051907Z-command.json", + "sha256": "77ce3fccbfabece87bfe68d7404af3caa5728a5f542ecaedf942a6493c476ec5", + "command": [ + "rtk", + "proxy", + "npm.cmd", + "test", + "--", + "--runInBand", + "--watchAll=false", + "--json", + "--outputFile=E:\\Storage\\Projects\\Active\\Job tracker\\.artifacts\\phase7\\current\\unit\\p712-admin-tooltip-red-20261021T051907Z.json", + "src/admin-users-page.test.tsx" + ], + "startedUtc": "2026-10-21T05:19:07.132180+00:00", + "finishedUtc": "2026-10-21T05:19:14.112556+00:00", + "exitCode": 1, + "counts": { + "numPassedTests": 9, + "numFailedTests": 1, + "numPendingTests": 0, + "numTodoTests": 0, + "numPassedTestSuites": 0, + "numFailedTestSuites": 1 + } + }, + "p712-admin-tooltip-green": { + "path": ".artifacts/phase7/current/unit/p712-admin-tooltip-green-20261021T051928Z-command.json", + "sha256": "e8863431c5dcec50c1ea1085a97753b98d3384ebd1abea45eb3690505d3b97fb", + "command": [ + "rtk", + "proxy", + "npm.cmd", + "test", + "--", + "--runInBand", + "--watchAll=false", + "--json", + "--outputFile=E:\\Storage\\Projects\\Active\\Job tracker\\.artifacts\\phase7\\current\\unit\\p712-admin-tooltip-green-20261021T051928Z.json", + "src/admin-users-page.test.tsx" + ], + "startedUtc": "2026-10-21T05:19:28.259530+00:00", + "finishedUtc": "2026-10-21T05:19:33.800673+00:00", + "exitCode": 0, + "counts": { + "numPassedTests": 10, + "numFailedTests": 0, + "numPendingTests": 0, + "numTodoTests": 0, + "numPassedTestSuites": 1, + "numFailedTestSuites": 0 + } + } + }, + "native": { + "stats": { + "startTime": "2026-10-21T05:20:10.074Z", + "duration": 25335.5, + "expected": 3, + "skipped": 0, + "unexpected": 0, + "flaky": 0 + }, + "path": ".artifacts/phase7/current/ui/admin-users.json", + "sha256": "e711f9b03ec133d48b3ec9b33cc86f9bf507fc0477475bf34e6bd0ead25fc963", + "command": { + "name": "admin-users", + "command": [ + "rtk", + "proxy", + "node", + "node_modules/@playwright/test/cli.js", + "test", + "e2e/phase7-admin-users.spec.ts", + "--retries=0", + "--reporter=list,json", + "--output=E:\\Storage\\Projects\\Active\\Job tracker\\.artifacts\\phase7\\current\\ui\\admin-users-results" + ], + "startedUtc": "2026-10-21T05:20:09.392563+00:00", + "finishedUtc": "2026-10-21T05:20:35.451870+00:00", + "exitCode": 0 + } + }, + "sources": { + "job-tracker-ui/src/views/AdminUsersPage.tsx": "c015f87184fb9ab7e66303d9c293214d5fe249013954030ca64da48d657a8bd2", + "job-tracker-ui/src/admin-users-page.test.tsx": "a148723fe696cea93dcd4cf5c59cd5f7654d94c34cfdd25f7e30b2915ad8ef1a" + }, + "axe": { + "scans": 7, + "violations": 0, + "incompleteNodes": { + "color-contrast": 21, + "aria-prohibited-attr": 4 + }, + "remainingContexts": [ + "", + "", + "", + "" + ] + }, + "qualification": "7 all-enabled axe scans have0violations;21 contrast incomplete nodes are obscured/overlap/short badge contexts and4 prohibited-name observations are MUI DataGrid Sort spans. Raw contexts remain for settled/manual triage. Final-administrator wrapper prohibited names are no longer observed. No human reader/device acceptance or new visual styling claim." +} diff --git a/job-tracker-ui/src/admin-users-page.test.tsx b/job-tracker-ui/src/admin-users-page.test.tsx index 6be5621..47b3c6b 100644 --- a/job-tracker-ui/src/admin-users-page.test.tsx +++ b/job-tracker-ui/src/admin-users-page.test.tsx @@ -115,7 +115,11 @@ test("warns before demoting another administrator", async () => { test("disables demotion and deletion for the final administrator", async () => { renderPage([{ id: "me", email: "me@example.com", userName: "me", roles: ["Admin"], emailConfirmed: true, isCurrentUser: true, canRemoveAdmin: false }]); - expect(await screen.findByRole("button", { name: "Remove admin" })).toBeDisabled(); + const demote = await screen.findByRole("button", { name: "Remove admin" }); + expect(demote).toBeDisabled(); + expect(demote.parentElement).not.toHaveAttribute("aria-label"); + fireEvent.mouseOver(demote.parentElement!); + expect(await screen.findByRole("tooltip")).toHaveTextContent("The final administrator cannot be demoted or deleted."); expect(screen.getByRole("button", { name: "Delete" })).toBeDisabled(); }); diff --git a/job-tracker-ui/src/views/AdminUsersPage.tsx b/job-tracker-ui/src/views/AdminUsersPage.tsx index 7534b17..f18e88d 100644 --- a/job-tracker-ui/src/views/AdminUsersPage.tsx +++ b/job-tracker-ui/src/views/AdminUsersPage.tsx @@ -200,7 +200,7 @@ export default function AdminUsersPage() { const isAdmin = (user.roles || []).includes("Admin"); return ( - + - + @@ -275,13 +275,13 @@ export default function AdminUsersPage() { - + - +