Use this section as the current workspace truth when older notes elsewhere in this file describe earlier failures.
### Main Acceptance Path
- The supported browser-level gate remains `cd frontend/admin && npm.cmd run e2e:full:win`.
- That gate was re-run green on 2026-04-23 after fixes in device pagination flow, backend-response envelope decoding, settings-service adapter alignment, and Playwright CDP selector and suite-retry stability.
### Recovery Notes That Matter
-`DevicesPage` must keep the request cursor separate from the response `next_cursor`; otherwise the initial load can auto-chain into extra `/admin/devices` requests and trigger rate limiting.
- Frontend services must decode backend envelopes by their actual fields and by the shared HTTP client contract. The recovered cases in this round were `list`, `deliveries`, `accounts`, and `/admin/settings` direct `data`.
- Late-stage E2E scenarios are more stable when assertions target route, heading, and role-based locators instead of broad page text matches.
- If suite retry reuses the same backend process, one-time preconditions such as `admin-bootstrap` must be refreshed from live backend capabilities before the next attempt starts.
### Boundary
- This snapshot proves browser-level real E2E closure in the current workspace.
- It does not by itself prove the full backend matrix, OS-level automation, or live third-party provider verification.
## 2026-04-23 Late-Suite E2E Triage Order
Use this order before blaming the browser wrapper when `cd frontend/admin && npm.cmd run e2e:full:win` fails in later admin scenarios.
1. Check whether the failing page consumes an API whose response envelope or field names changed.
2. Check whether the page state machine, pagination flow, or derived state issued unexpected follow-up requests.
3. Check whether the failing assertion uses a broad text locator where route, heading, role, or labeled-control matching would be more precise.
4. Only after the first three checks stay clean, investigate CDP session lifecycle, page/context closure, or local browser startup instability.
## 2026-04-23 Password Reset And CDP Recovery Notes
### Root Cause
- The password-reset browser gap came from a backend contract omission: `/api/v1/auth/capabilities` returned `password_reset=false` even when `passwordResetHandler` was mounted and the reset routes were live.
### Minimal Fix
-`AuthHandler` now carries the password-reset capability bit and fills `caps.PasswordReset` in `GetAuthCapabilities()`.
- Router assembly now synchronizes that bit from the same `passwordResetHandler != nil` condition that mounts the reset routes.
### Browser Flow Proof
- The supported browser suite now proves the real password-reset chain end to end:
- admin creates a real user
- login surface exposes the forgot-password entry
-`/api/v1/auth/forgot-password` emits a real SMTP-captured reset link
-`/api/v1/auth/password/validate` and `/api/v1/auth/reset-password` complete through the browser
- the user logs in with the new password
### Stability Rule
- When headless-shell closes the last live target late in the suite, reconnect the CDP browser connection and reacquire the persistent page before declaring the whole run failed.
## 2026-04-23 Permissions CRUD And Full Matrix Technical Snapshot
Use this section as the newest technical snapshot when earlier 2026-04-23 notes describe only the 19-scenario gate.
### Main Acceptance Path
- The supported browser-level gate remains `cd frontend/admin && npm.cmd run e2e:full:win`.
- That gate was re-run green on 2026-04-23 after adding `permissions-management-crud`, fixing permissions payload compatibility, fixing auth-header selection under concurrent refresh state, and stabilizing CDP observation for proxied permission calls.
- The same branch state also re-ran `go test ./... -count=1`, `go vet ./...`, `go build ./cmd/server`, `cd frontend/admin && npm.cmd run test:run`, `cd frontend/admin && npm.cmd run lint`, and `cd frontend/admin && npm.cmd run build` successfully.
### Recovery Notes That Matter
- The permissions frontend adapter must accept raw numeric backend `type` values, normalize them to the frontend string enum, and serialize writes back to the backend numeric form.
- The permissions backend handler must continue accepting menu `type=0` and status payloads delivered as either numeric or string values, because real browser flows and clients can send both forms during incremental rollout.
- A valid non-expired access token must still be attached to requests even when a different refresh flow is already in flight. Refresh state alone is not evidence that the current request should lose authentication.
- In the permissions CRUD scenario, the page and backend were healthy even when Playwright CDP request and response observers missed the proxied `/api/v1/permissions` call. The reliable proof path was the in-page fetch diagnostic log plus the post-submit UI refresh.
- Ant modal leave animations can keep intercepting clicks after the dialog is visually closing. Scenario code should wait for the modal to stop blocking interaction before the next action.
- Vite 8 on Windows with `--configLoader native` can fail the supported build path if project root resolution is implicit. The stable fix is an explicit `root` in `vite.config.js`.
### Boundary
- This snapshot proves browser-level real E2E closure with `20` supported scenarios in the current workspace.
- It does not by itself prove OS-level automation, live third-party provider verification, or remote-repository publication status.
## 2026-04-24 Profile Security Contract Recovery
### Root Cause
- The profile password form used the UI model (`current_password`, `confirm_password`) all the way through the service layer, but the real backend `PUT /users/:id/password` handler binds `old_password` and `new_password` only.
### Minimal Fix
-`frontend/admin/src/services/profile.ts` now maps the UI request to the real backend payload shape before calling the shared HTTP client.
-`frontend/admin/scripts/run-playwright-cdp-e2e.mjs` now couples password and TOTP fetch waits to the submit action that triggers them, so a later locator failure does not leave an orphaned background waiter that hides the real error.
### Browser Flow Proof
- The targeted profile page and service regression set is green.
- The supported browser-level gate `cd frontend/admin && npm.cmd run e2e:full:win` is green with `20` scenarios, including `profile-and-security`.
### Stability Rule
- When a scenario uses asynchronous fetch diagnostics for proof, create the waiter in the same control flow as the triggering action and tear it down implicitly with that action path. A background waiter that survives a failed action is a runner bug because it can replace the primary failure with misleading page-closed noise.
- The supported browser-level gate remains `cd frontend/admin && npm.cmd run e2e:full:win`.
- On 2026-04-24 that gate was re-run green after changing `frontend/admin/scripts/run-playwright-auth-e2e.ps1` to keep one backend and one frontend session alive while launching a fresh browser process for each selected Playwright scenario.
-`frontend/admin/scripts/run-playwright-cdp-e2e.mjs` now supports a lightweight `E2E_LIST_SCENARIOS=1` mode so the wrapper and the runner derive the scenario order from the same source of truth.
### Current Green Evidence
- The current full-gate green evidence is `21` isolated scenario runs in one end-to-end environment:
-`admin-bootstrap`
-`public-registration`
-`email-activation`
-`password-reset`
-`login-surface`
-`auth-workflow`
-`responsive-login`
-`desktop-mobile-navigation`
-`user-management-crud`
-`user-management-batch`
-`role-management-crud`
-`permissions-management-crud`
-`device-management`
-`login-logs`
-`operation-logs`
-`webhook-management`
-`import-export`
-`profile-management`
-`profile-and-security`
-`settings`
-`dashboard-stats`
### Operational Knobs
-`E2E_SCENARIO_ISOLATION=0` keeps the legacy whole-suite browser mode available for diagnostics.
-`E2E_SCENARIO_ATTEMPTS` overrides the per-scenario retry count; otherwise the wrapper falls back to `E2E_SUITE_ATTEMPTS`.
- For owner-scoped resources, split authorization across two layers:
- the handler extracts the current actor identity and admin bit from the authenticated request context;
- the service loads the target resource and re-checks `owner-or-admin` before returning or mutating it.
- This prevents future handlers, background callers, or admin-route reuse from silently bypassing ownership checks by passing a raw resource ID straight into repository operations.
### Minimum Regression Pattern
- Add a targeted red-green regression set for each resource family that covers:
- cross-user read forbidden;
- cross-user update forbidden;
- cross-user delete forbidden;
- cross-user state toggle forbidden;
- admin positive access when the contract allows it.