Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7e595b6e5e | |||
| 1d716d0cb8 |
@@ -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,7 +1,7 @@
|
||||
{
|
||||
"name": "rfc-app-frontend",
|
||||
"private": true,
|
||||
"version": "0.31.1",
|
||||
"version": "0.31.3",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
+21
-3
@@ -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) --- */
|
||||
|
||||
|
||||
@@ -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 (
|
||||
|
||||
Reference in New Issue
Block a user