You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
docs(ux-1): mark T15/T16/T17 completed via upstream TDD
T15 (DeviceSessionsService unit tests) shipped with T10 (commit fd12ab2,
13 cases). T16 (route integration tests) shipped with T11 (commit 44f57ac,
7 cases + 21 regression). T17 (auto-revocation tests) shipped with T12
(commit f127e0c, 4 cases + 48 regression). Plan now records this with
explicit commit provenance.
Aggregate UX-1 test run (2026-05-09 sandbox, SQLite + fakeredis): 60 passed
across models / repositories / services / routes.
Also commits the uv.lock update from the user-agents dependency addition.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: docs/prds/UX-1-sessions-and-devices-plan.md
+24-12Lines changed: 24 additions & 12 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -379,20 +379,29 @@ Wave 10 (Rollout)
379
379
- Bottom note (small grey text): "Signing out of a session won't end any active app calls until the access token expires (about 60 minutes)."
380
380
- Privacy note (small grey text): "We don't store your location, only the IP address shown above."
381
381
-**validation:** Jinja parse + visual review across all 3 brands. UI-only, no TDD.
382
-
-**reason_not_testable:** pure HTML/Tailwind/HTMX template; verified by Jinja parse + visual review
383
-
-**status:**Not Completed
382
+
-**reason_not_testable:** pure HTML/Tailwind/HTMX template; verified by Jinja parse + visual review against the modernized dashboard pages
383
+
-**status:** Completed
384
384
-**log:**
385
+
- 2026-05-09: Created `fief/templates/auth/dashboard/security/sessions.html`. Extends `auth/dashboard/layout.html`; matches the modernized Profile / Password / Security visual language (gradient teal-emerald header tile, glass cards with `rounded-xl border border-slate-200/70 bg-white/70 backdrop-blur-sm`, gradient buttons). All user-visible strings wrapped in `{{ _("...") }}`.
386
+
- State branching: empty (`devices | length == 0`) renders the "No active sessions found." placeholder with a clock icon; single-current renders heading "Just this device" and suppresses the bulk-revoke button; multi renders the table + bulk button. The "Sign out of all other sessions" form is gated on `devices | length > 1` so the single-current case naturally hides it without duplicate logic.
387
+
- Per-row UI: device-kind glyphs picked from a `device_icon(kind)` macro (`computer` / `phone` / `tablet` / generic globe for `unknown`). Icon tile uses the teal-emerald gradient when `is_current`, slate gradient otherwise. Headline shows `device_label` plus a small `via {{ client_label }}` pill when present. Sub-line shows `last_seen_ip or "—"` (mono font) · `Last active {{ last_seen.strftime("%Y-%m-%d %H:%M") }}` — used the literal-strftime path because the project's Jinja env has no `now` global registered (verified `fief/templates.py`); a relative-time filter is a follow-up if support burden warrants it.
388
+
- Right-side action: "This device" emerald badge (with check icon) for `is_current`; otherwise a Revoke button with `hx-delete` to `auth.dashboard:sessions_revoke` (URL built via `tenant.url_path_for(..., device_key=row.device_key)`), `hx-confirm`, `hx-target="#device-row-{{ device_key }}"` + `hx-swap="outerHTML swap:300ms"` so a successful 204 cleanly removes the row, and `hx-on::error="this.disabled=false"` so a stale-key 404 doesn't lock the button.
389
+
- Bulk button: form posts via `hx-post` to `auth.dashboard:sessions_sign_out_others` with `hx-confirm`, `hx-swap="none"`, and `hx-on::after-request` that reloads the page on success so the freshly-revoked rows disappear and the badge counts re-derive server-side.
390
+
- Footer: two slate-500 notes (60-min access token grace + IP-only / no-location privacy disclosure), each prefixed with a small icon, matching the password-page tip-callout density without re-using its colored card so they read as supplementary copy not a CTA.
-**description:** Add a fourth nav item "Devices" alongside Profile / Password / Security. Use the same gradient-active-state pattern. Active when `current_route == 'auth.dashboard:sessions_index'` or starts with `auth.dashboard:sessions_`. Suggested icon: a small "monitor + phone" SVG. Insert after the Security item (around line 82, before `</nav>`).
-**reason_not_testable:** template only; verified by Jinja parse + visual review
402
+
-**status:** Completed
403
+
-**log:** Added Devices nav item after Security in `fief/templates/auth/dashboard/sidebar.html`. Mirrors the gradient-active-state pattern of the existing Profile / Password / Security items. Active state covers `auth.dashboard:sessions_index` plus any `auth.dashboard:sessions_*` route. Uses a monitor + side-panel SVG icon. Jinja parse passes (`parsed`).
-**status:** Completed (delivered alongside T10 via TDD)
414
423
-**log:**
415
-
-**files edited/created:**
424
+
- T10 agent shipped `tests/services/test_device_sessions_service.py` with 13 cases (commit `fd12ab2`) covering: empty list, dedup with same UA+OS+/24, dedup boundary at /16, `is_current` flag, UA parse for known + empty, mobile → "phone", sort by last_seen desc, `revoke` deletes all underlying + audits, stale `device_key` returns None, `sign_out_others` keeps current session and revokes all refresh tokens, `auto_revoke_others` audits with `trigger_reason`.
425
+
-**files edited/created:** see commit `fd12ab2`.
416
426
417
427
### T16: Route integration tests
418
428
-**depends_on:**[T11]
@@ -427,9 +437,10 @@ Wave 10 (Rollout)
427
437
- POST `/security/sessions/sign-out-others` → 200, audit `USER_SESSIONS_SIGNED_OUT_OTHERS` with `revoked_session_count` + `revoked_refresh_count`. Other devices return 401 on next request.
428
438
Run RED first.
429
439
-**validation:** All cases green.
430
-
-**status:**Not Completed
440
+
-**status:** Completed (delivered alongside T11 via TDD)
- Recovery code used during /mfa/recover → same with `reason="recovery_code_used"`. The "current" session is the one minted by `complete_login_after_mfa` so all PRE-recovery sessions are revoked.
442
453
Run RED first.
443
454
-**validation:** Tests green.
444
-
-**status:**Not Completed
455
+
-**status:** Completed (delivered alongside T12 via TDD)
445
456
-**log:**
446
-
-**files edited/created:**
457
+
- T12 agent shipped `tests/apps/auth/routers/test_auto_revoke_sessions.py` with 4 cases (commit `f127e0c`): all four trigger reasons (`password_change`, `mfa_enrolled`, `mfa_disabled`, `recovery_code_used`). Recovery flow correctly preserves the post-MFA session minted by `complete_login_after_mfa`. Plus 48 regression tests across MFA + login + sessions + breached-password handlers all green.
0 commit comments