phase: 101_tokens_page_overhaul
All gates verified green — no defects found, nothing to fix. Final report:
## Phase 101 — final verification pass (all tasks already complete; verified, not re-implemented)
**Verified against the working tree** (task files 01–04 present in `complete/`):
- Active/revoked table split (Status column gone, section hidden while 0 revoked, per-table search with query state surviving re-renders) in `frontend/index.html` + `tokens.js`
- `generateToken()` refuses blank/whitespace names (`Give the token a name first.`, no request); `|| "token"` fallback deleted
- Atomic rotation: `regenerate_token` service + `POST /api/tokens/{id}/regenerate` (201 new-plaintext-once / 404 / 409 / router-wide 403) + `TokenListItem.revoked_at` (D5)
- Regenerate two-step confirm UI + CSS (`.token-regenerate`, neutral hover, no new hue); A4 pins intact
**Test / lint / coverage results:**
- `uv run pytest` → **2065 passed**
- `uv run pytest --cov=app --cov-report=term-missing` → **TOTAL 99%** (>90% ✓)
- `uv run ruff check . && uv run pyright` → clean (0 errors)
- `uv run pytest tests/e2e/test_tokens_page.py -v --no-cov` → **4 passed** (isolation, DB up)
- Regression, each in isolation: `test_api_tokens.py` **9 passed**, `test_admin_auth.py` **6 passed**, `test_shared_header.py` **6 passed**, `test_theme_semantic_completion.py` **8 passed** (its revoked-pill pin was correctly re-scoped to the revoked table in this phase)
**Completion criteria:** 1 ✓ split+search (E2E 1–2) · 2 ✓ required name (E2E 3 + source pin) · 3 ✓ rotation end-to-end, old token refused at gate (E2E 4 + API 404/409 pinned) · 4 ✓ A4 holds (list carries no plaintext/hashes) · 5 ✓ suite/coverage/lint green · 6 ✓ E2E + regressions green in isolation · 7 commit left to the harness per executor rules (all changes uncommitted in the working tree)
**Deviations:** none. Next pending phase: `98_sync_summary_visibility`.
This commit is contained in:
+82
-22
@@ -913,21 +913,22 @@
|
||||
lines, nothing else. -->
|
||||
<span class="tokens-status" id="tokens-status" role="status" aria-live="polite"></span>
|
||||
|
||||
<!-- The create row: label (1–120 — the server re-validates; a
|
||||
BLANK label sends "token", the placeholder documents the
|
||||
fallback) + Generate. SHIPS hidden (anonymous-safe — no
|
||||
dead control beside the gate; tokens.js reveals it for
|
||||
admin only). The button is type="button" (no form submit)
|
||||
and runs the §7.4 never-stale lifecycle ("Generating…"
|
||||
while the POST is in flight, re-enabled on success AND
|
||||
failure). -->
|
||||
<!-- The create row: name (1–120 — the server re-validates; a
|
||||
BLANK name is REFUSED client-side — tokens.js announces
|
||||
"Give the token a name first." and sends NOTHING, phase
|
||||
101 D3; the server's 422 is defense in depth) +
|
||||
Generate. SHIPS hidden (anonymous-safe — no dead control
|
||||
beside the gate; tokens.js reveals it for admin only).
|
||||
The button is type="button" (no form submit) and runs the
|
||||
§7.4 never-stale lifecycle ("Generating…" while the POST
|
||||
is in flight, re-enabled on success AND failure). -->
|
||||
<div class="token-create" id="token-create" hidden>
|
||||
<input
|
||||
id="token-label"
|
||||
type="text"
|
||||
maxlength="120"
|
||||
placeholder="e.g. alice"
|
||||
aria-label="Token label"
|
||||
placeholder="e.g. alice — required"
|
||||
aria-label="Token name"
|
||||
autocomplete="off"
|
||||
>
|
||||
<button type="button" class="token-generate" id="token-generate">Generate</button>
|
||||
@@ -959,16 +960,35 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- The full-width table (AGENTS.md rule 5 — no skinny list):
|
||||
Label | Created (locale date+time, full ISO on hover) |
|
||||
Last used (locale or "never") | Status (Active em-dash vs
|
||||
the rose Revoked pill — the stale-pill language) | Actions
|
||||
(Revoke — the inline two-step confirm; Revoked rows carry
|
||||
NO action — nothing left to revoke). tokens.js fills
|
||||
#tokens-tbody; #tokens-empty-row ships hidden and is
|
||||
revealed by a 0-row fetch (and a failed load). The Actions
|
||||
column header is visually-hidden — the row buttons carry
|
||||
their own aria-labels (the history-table convention). -->
|
||||
<!-- Phase 101 (task 02, D4): the ACTIVE table's live label
|
||||
search — a client-side, case-insensitive SUBSTRING filter
|
||||
over the rendered rows (no fetch). SHIPS hidden with the
|
||||
create row (tokens.js reveals it in the admin branch);
|
||||
the query lives in module state and is RE-APPLIED after
|
||||
every loadTokens() — a re-render never loses it. -->
|
||||
<input
|
||||
type="search"
|
||||
id="token-search-active"
|
||||
aria-label="Search active tokens"
|
||||
placeholder="Search active tokens…"
|
||||
class="token-search"
|
||||
hidden
|
||||
>
|
||||
|
||||
<!-- The full-width ACTIVE table (AGENTS.md rule 5 — no skinny
|
||||
list): Label | Created (locale date+time, full ISO on
|
||||
hover) | Last used (locale or "never") | Actions (Revoke
|
||||
— the inline two-step confirm; phase 101 task 03 adds
|
||||
Regenerate before it). The Status column is GONE (phase
|
||||
101 D1 — a table that is all-active needs no status; the
|
||||
table's position IS the status). tokens.js fills
|
||||
#tokens-tbody with the revoked === false rows only;
|
||||
#tokens-empty-row ships hidden and is revealed by a
|
||||
0-active-row fetch (and a failed load); #tokens-no-match-row
|
||||
(its text JS-filled) is the D4 search no-match state. The
|
||||
Actions column header is visually-hidden — the row
|
||||
buttons carry their own aria-labels (the history-table
|
||||
convention). -->
|
||||
<div class="table-wrap tokens-table-wrap" id="tokens-table-wrap" role="region" aria-label="Access tokens" tabindex="0">
|
||||
<table class="tokens-table" id="tokens-table">
|
||||
<caption class="visually-hidden">Access tokens issued to people who use the app — newest first</caption>
|
||||
@@ -977,13 +997,53 @@
|
||||
<th scope="col">Label</th>
|
||||
<th scope="col">Created</th>
|
||||
<th scope="col">Last used</th>
|
||||
<th scope="col">Status</th>
|
||||
<th scope="col"><span class="visually-hidden">Actions</span></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="tokens-tbody">
|
||||
<tr class="tokens-empty-row" id="tokens-empty-row" hidden>
|
||||
<td colspan="5">No tokens yet — generate one above and hand it out.</td>
|
||||
<td colspan="4">No tokens yet — generate one above and hand it out.</td>
|
||||
</tr>
|
||||
<tr class="tokens-empty-row" id="tokens-no-match-row" hidden>
|
||||
<td colspan="4"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Phase 101 (task 02, D1 + D4): the REVOKED section — BELOW
|
||||
the active table: a visible sub-heading + its own live
|
||||
label search + its own full-width table (Label | Created
|
||||
| Last used | Revoked — the revoked_at date, locale
|
||||
date+time, full ISO on hover). SHIPS hidden — tokens.js
|
||||
shows it when ≥ 1 token is revoked (an empty table is
|
||||
noise). The table IS the status: the em-dash /
|
||||
.stale-pill column is gone from BOTH tables. Revoked rows
|
||||
carry NO actions (nothing left to revoke or rotate —
|
||||
task 03's Regenerate lives in the active table only). -->
|
||||
<h2 id="tokens-revoked-heading" class="tokens-revoked-heading" hidden>Revoked tokens</h2>
|
||||
<input
|
||||
type="search"
|
||||
id="token-search-revoked"
|
||||
aria-label="Search revoked tokens"
|
||||
placeholder="Search revoked tokens…"
|
||||
class="token-search"
|
||||
hidden
|
||||
>
|
||||
<div class="table-wrap tokens-table-wrap" id="tokens-revoked-wrap" role="region" aria-label="Revoked tokens" tabindex="0" hidden>
|
||||
<table class="tokens-table" id="tokens-revoked-table">
|
||||
<caption class="visually-hidden">Revoked tokens — newest first</caption>
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Label</th>
|
||||
<th scope="col">Created</th>
|
||||
<th scope="col">Last used</th>
|
||||
<th scope="col">Revoked</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="tokens-revoked-tbody">
|
||||
<tr class="tokens-empty-row" id="tokens-revoked-no-match-row" hidden>
|
||||
<td colspan="4"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
Reference in New Issue
Block a user