From 7e595b6e5e734d8a7a8c868386da0d291c43783c Mon Sep 17 00:00:00 2001 From: Ben Stull Date: Sat, 30 May 2026 04:00:31 -0700 Subject: [PATCH] v0.31.3: admin Users "Last seen" = Never for unclaimed invites An admin-created invite row showed a Last-seen timestamp identical to Signed-up, implying the invitee had visited. users.last_seen_at is NOT NULL DEFAULT (datetime('now')) and the invite INSERT sets neither timestamp, so both default to row-creation time; last_seen_at only advances on real authentication. An unclaimed invite has provably never authenticated (the unclaimed state drives the PENDING INVITE badge), so the Users tab now renders "Never" for the Last-seen cell of a pending row. Signed-up (invite-created date) unchanged. Co-Authored-By: Claude Opus 4.8 --- CHANGELOG.md | 20 ++++++++++++++++++++ VERSION | 2 +- frontend/package.json | 2 +- frontend/src/components/Admin.jsx | 13 ++++++++++--- 4 files changed, 32 insertions(+), 5 deletions(-) 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 (