Files
crumbs/tests
Frederic CabassutandClaude Opus 5 ffde151b75 feat(wu-003): add admin reset-password control to Settings > Users
Closes the UI gap in issue #77. The endpoint, resetPassword() and the
notification email have all existed since the feature was built; only
the control was missing, which is why the docs promised something the
screen did not offer.

The control renders on EVERY row, the admin's own included. It is
enabled only for accounts that sign in with a password and that are not
the signed-in admin; otherwise it is presented as unavailable WITH A
VISIBLE REASON. A control that simply vanishes is the confusion this
replaces, so silence was not an option.

No new control uses the `disabled` attribute. Unavailable is expressed
with aria-disabled, the element stays focusable, and the handler refuses
activation itself — a disabled button leaves the tab order, so a
keyboard or TalkBack user loses the element they just activated, and a
title tooltip explains nothing on a touch screen. The reporter is on
Android.

Two rules protect the credential, and both are enforced by the shape of
the state rather than by convention:

- An unconfirmed outcome REVEALS the password. If the request left the
  device and no answer came back, the server may well have stored it,
  making the revealed string the only copy in existence. Discarding it
  could leave an account holding a credential nobody knows.
- `password` and `message` are independent fields, so an error has
  nowhere to write that would replace a reveal. Revealed rows are also
  marked unavailable, so a second reset cannot be issued until Dismiss.

Offline is a definite state, not a lost response: the service worker
caches only GET /api/*, so a PATCH never leaves the browser. It is
checked before anything is generated, and reported as the certainty it
is.

The live region is rendered empty on first paint and only its text is
replaced, because a region inserted together with its content is not
reliably announced. It announces that a password appeared and where —
never the secret itself, which a polite region would speak aloud once,
unrepeatably, to whoever is in earshot.

DoD items verified (22):
- [x] 1-2. control on every row; enabled iff eligible and not self,
      else aria-disabled with a visible, aria-describedby reason
- [x] 3-4. PATCHes newPassword; "Resetting…" and no double-submit
- [x] 5-6. revealed as selectable text, announced; stays unavailable
- [x] 7. Copy confirms success; failure offers a manual-copy hint
- [x] 8-10. 4xx reveals nothing; offline definite; unknown REVEALS
- [x] 11-12. errors never replace a reveal; both announced
- [x] 13-14. 404 drops the row and its plaintext; rows isolated
- [x] 15-17. Dismiss clears and restores focus; 44px targets;
      activating an aria-disabled control fires no request
- [x] 18-19. victim login from a separate context, old password 401
      last; plaintext in no storage, no IndexedDB, no console
- [x] 20-22. CSS variables only, no hover-only; untruncated at 360px;
      declarative Gherkin with distinct victim emails

Verified independently by the orchestrator: pnpm check 0 errors /
14 pre-existing warnings; pnpm test:unit 306 passed / 26 files;
admin-users.spec.ts 28 passed; full e2e suite 126 passed.

The reviewer additionally mutation-tested the security core, each
mutation rebuilt and caught with clean attribution: revealing null on an
unconfirmed outcome, forcing the offline pre-flight false (which falls
into the unknown path and would reveal a password that never left the
browser), dropping the state merge in patchReset, swapping aria-disabled
for disabled (which breaks focus return), and deleting the activation
guard — the last proving the force-click assertions test the component
refusing rather than Playwright declining to click.

Reviewed-by: adversarial-review (PASS)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RbqJcFofZSbhbGwUwZP3wU
2026-08-18 12:31:12 +02:00
..