Compare commits

...

2 Commits

Author SHA1 Message Date
Ben Stull 7e595b6e5e 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 <noreply@anthropic.com>
2026-05-30 04:00:31 -07:00
Ben Stull 1d716d0cb8 v0.31.2: landing welcome panel spacing
Visual-only patch. The "/" welcome read-view was jammed against the
catalog divider with no top offset and loose paragraph rhythm: the
.main-pane §8 override (padding:0; display:flex) shadows the padded
read-view rule, so the pane gives no padding, and .welcome (max-width
only) never compensated. The welcome surface now owns its breathing
room — 56px top / 48px side gutters, a 680px measure, a text-3xl hero,
and even --space-8 paragraph spacing at --leading-relaxed. Covers both
the signed-out and signed-in welcome.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-29 22:54:29 -07:00
5 changed files with 71 additions and 8 deletions
+38
View File
@@ -23,6 +23,44 @@ 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
`App.css` (`.welcome`). A plain frontend rebuild applies it.**
The welcome read-view was jammed against the catalog divider with no
top offset and loose, uneven paragraph spacing. Root cause: `.main-pane`
carries a bare `.main-pane { padding: 0; display: flex }` override (the
§8 three-column RFC shell) that shadows the earlier padded read-view
rule, so the pane provides no padding — and `.welcome` (just
`max-width`) never compensated. The welcome surface now owns its own
breathing room: 56px top / 48px side gutters, a capped 680px measure,
a stronger `text-3xl` "Welcome." hero, and even `--space-8` paragraph
rhythm at `--leading-relaxed`. Applies to both the signed-out and
signed-in welcome (same `.welcome` class).
Upgrade steps: none. **SHOULD** deploy as a normal code deploy.
## 0.31.1 — 2026-05-29
**Patch — admin Users tab + header UX polish. Visual only: CSS plus
+1 -1
View File
@@ -1 +1 @@
0.31.1
0.31.3
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "rfc-app-frontend",
"private": true,
"version": "0.31.1",
"version": "0.31.3",
"type": "module",
"scripts": {
"dev": "vite",
+21 -3
View File
@@ -189,9 +189,27 @@
padding: 32px 48px;
}
.welcome { max-width: 640px; }
.welcome h1 { font-size: var(--text-2xl); font-weight: 600; margin: 0 0 16px; }
.welcome p { line-height: 1.7; color: var(--c-gray-600); }
/* `.main-pane` is the §8 flex shell and carries no padding (the bare
`.main-pane` override below shadows the padded read-view rule), so the
welcome surface owns its own breathing room top offset, comfortable
side gutters, and a capped measure for readable line length. */
.welcome {
max-width: 680px;
padding: 56px 48px 64px;
}
.welcome h1 {
font-size: var(--text-3xl); font-weight: 600;
letter-spacing: -0.01em;
margin: 0 0 var(--space-8);
}
.welcome p {
font-size: var(--text-lg);
line-height: var(--leading-relaxed);
color: var(--c-gray-600);
margin: 0 0 var(--space-8);
}
.welcome p:last-child { margin-bottom: 0; }
.welcome strong { color: var(--c-gray-800); }
/* --- RFC / Proposal view (read-only for slice 1) --- */
+10 -3
View File
@@ -326,7 +326,14 @@ function UserRow({ user: u, busy, onChangeRole, onToggleMute, onFlipPermission }
)}
</td>
<TimeCell value={u.created_at} />
<TimeCell value={u.last_seen_at} />
{/* 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. */}
<TimeCell
value={pendingInvite ? null : u.last_seen_at}
emptyLabel={pendingInvite ? 'Never' : '—'}
/>
</tr>
{state === 'pending' && u.beta_request_reason ? (
<tr className="user-row-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 <td className="muted"></td>
function TimeCell({ value, emptyLabel = '—' }) {
if (!value) return <td className="muted">{emptyLabel}</td>
const [date, ...rest] = String(value).split(' ')
const time = rest.join(' ')
return (