diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c09392..af643de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,26 @@ skip versions are the composition of each intervening adjacent release's steps in order — no A-to-B path is pre-computed beyond that. +## 0.31.3 — 2026-05-30 + +**Patch — admin Users tab: "Last seen" reads "Never" for unclaimed +invites. Visual/logic only in `Admin.jsx`. A plain frontend rebuild +applies it.** + +An admin-created invite row showed a real-looking "Last seen" timestamp +identical to "Signed up," implying the invitee had visited when they +hadn't. Cause: `users.last_seen_at` is `NOT NULL DEFAULT (datetime('now'))` +(`migrations/001_users_and_audit.sql`) and the invite INSERT +(`invites.py`) sets neither timestamp, so both default to the +row-creation instant; `last_seen_at` only advances on a real +authentication. Since an unclaimed invite has provably never +authenticated (that unclaimed state is exactly what drives the +"PENDING INVITE" badge), the Users tab now renders **"Never"** for the +Last-seen cell of a pending-invite row instead of the misleading +default. Signed-up (the invite-created date) is unchanged. + +Upgrade steps: none. **SHOULD** deploy as a normal code deploy. + ## 0.31.2 — 2026-05-29 **Patch — landing (`/`) welcome panel spacing. Visual only: CSS in diff --git a/VERSION b/VERSION index c415e1c..8239f42 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.31.2 +0.31.3 diff --git a/frontend/package.json b/frontend/package.json index 654316d..d9085ba 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,7 +1,7 @@ { "name": "rfc-app-frontend", "private": true, - "version": "0.31.2", + "version": "0.31.3", "type": "module", "scripts": { "dev": "vite", diff --git a/frontend/src/components/Admin.jsx b/frontend/src/components/Admin.jsx index f47ddf7..ca424e2 100644 --- a/frontend/src/components/Admin.jsx +++ b/frontend/src/components/Admin.jsx @@ -326,7 +326,14 @@ function UserRow({ user: u, busy, onChangeRole, onToggleMute, onFlipPermission } )} - + {/* An unclaimed admin invite has provably never authenticated, so + last_seen_at is just the row-creation default (it equals + created_at). Render the truth — "Never" — rather than a + timestamp that reads like a real visit. */} + {state === 'pending' && u.beta_request_reason ? ( @@ -345,8 +352,8 @@ function UserRow({ user: u, busy, onChangeRole, onToggleMute, onFlipPermission } // Render a "YYYY-MM-DD HH:MM:SS" timestamp as an intentional date-over-time // stack (date prominent, time quiet below) rather than letting a narrow // column wrap the value mid-string. Falls back to an em-dash when absent. -function TimeCell({ value }) { - if (!value) return — +function TimeCell({ value, emptyLabel = '—' }) { + if (!value) return {emptyLabel} const [date, ...rest] = String(value).split(' ') const time = rest.join(' ') return (