Compare commits

..

3 Commits

Author SHA1 Message Date
Ben Stull 551d240967 Merge pull request 'Release v0.31.4: fix invisible light-surface btn-link + harmonize RFC breadcrumb action bar' (#5) from release/v0.31.4-button-ux into main 2026-06-01 15:20:33 +00:00
Ben Stull 76c82a5e96 v0.31.4: fix invisible light-surface btn-link + harmonize RFC breadcrumb action bar
`.btn-link` was a dark-header utility (white text on translucent white)
reused on light surfaces — the RFC breadcrumb action bar, PR diff toggle,
modals, discussion panel — where it rendered white-on-white and looked
like missing buttons (reported: "Metadata"/"Claim ownership"/"Invitations"
absent on a super-draft header). Root-cause fix:

- Base .btn-link is now a light-surface secondary button (white fill,
  hairline border, dark label); the dark-header "Sign out" keeps the
  translucent-on-dark treatment via an .app-header .btn-link scope.
- .breadcrumb-actions normalizes the toggle, filled CTAs, and secondary
  buttons to one height/radius/type as a single control group, and
  flex-wraps instead of clipping off the right edge.
- Diff-mode active toggle now reads as clearly selected (filled ink).

CSS-only; patch release, plain frontend rebuild applies it. No upgrade
steps. Driver session 0059.0.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-01 07:22:19 -07:00
Ben Stull e8e555d8a4 docs(invites): correct stale last_seen_at NULL claim
The provisioning docstring claimed the invitee row gets
`last_seen_at = NULL` as the "not yet arrived" discriminator. It does
not: the column is NOT NULL and the INSERT omits it, so it defaults to
datetime('now') — the longer note below already explained this, but the
bullet contradicted it. Rewrite the bullet to state the real behavior
(both timestamps default to now; the pending-invite state lives in the
unclaimed user_invite_tokens row) and note that consumers must treat a
pending-invite row as never-seen. Comment-only; no runtime change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-30 04:04:11 -07:00
5 changed files with 100 additions and 13 deletions
+44
View File
@@ -23,6 +23,50 @@ 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.4 — 2026-06-01
**Patch — bug fix + UI polish: secondary buttons that were invisible on
light surfaces now render legibly, and the RFC view's breadcrumb action
bar is harmonized into one coherent control group. CSS-only
(`frontend/src/App.css`); no schema, API, config, overlay, or secret
change — a plain frontend rebuild applies it. No upgrade steps. Shipped
from driver session 0059.0.**
`.btn-link` was authored as a *dark-header* utility — white text on a
translucent-white fill (`rgba(255,255,255,0.15)`), the established
on-dark pattern for the app header's "Sign out". But the same class is
reused on **light** surfaces: the RFC breadcrumb action bar
(`RFCView.jsx`), the PR view's diff-mode toggle and "Edit title" control
(`PRView.jsx`), the invitations and inbox modals, and the discussion
panel. On those near-white backgrounds the buttons were white-on-white —
present in the DOM, fully functional, but visually invisible. The
reported symptom: on a super-draft's header, "Metadata", "Claim
ownership", and "Invitations" looked *missing*, while the filled CTAs
("Start Contributing", "Graduate to RFC repo") rendered fine because
their fill carried them.
The fix is root-cause, not a per-site patch:
- The base `.btn-link` rule is now a proper light-surface secondary
button (white fill, hairline `--c-gray-300` border, `--c-gray-700`
label, hover darkens both). This corrects every light-surface reuse at
once.
- The original translucent-on-dark treatment is preserved for the one
legitimate dark-surface use via an `.app-header .btn-link` scope, so
the header "Sign out" is unchanged.
- The breadcrumb action bar (`.breadcrumb-actions`) normalizes every
action — the discuss/contribute toggle, the filled CTAs, and the
secondary buttons — to one height, radius, and type scale, so the row
reads as a single intentional control group. The bar now `flex-wrap`s
instead of clipping buttons off the right edge when the set is wide.
- The diff-mode toggle's active option now reads as clearly selected
(filled ink) rather than relying on a weight change alone.
Smooth hover transitions and the keyboard focus ring were already
provided globally by the v0.21.0 interaction-polish layer, so this
change adds no new motion or focus rules — it only corrects resting-state
color/contrast and harmonizes sizing.
## 0.31.3 — 2026-05-30
**Patch — admin Users tab: "Last seen" reads "Never" for unclaimed
+1 -1
View File
@@ -1 +1 @@
0.31.3
0.31.4
+10 -4
View File
@@ -144,10 +144,16 @@ def create_invite(
The invitee `users` row is provisioned with:
* `permission_state='granted'` the admin's hand is the grant;
the v0.8.0 self-serve `pending` queue is for the other path.
* `last_seen_at = NULL` the discriminator for "invited but
not yet arrived" per the §16 / roadmap design. Every sign-in
path stamps `last_seen_at` to now, so a NULL value means the
invited user has not clicked through yet.
* `created_at` / `last_seen_at` NOT set here, so both fall
through to the column default `datetime('now')` (the column is
`NOT NULL`; see `migrations/001_users_and_audit.sql` and the
longer note below). The "invited but not yet arrived" state is
therefore NOT carried on the user row it is the existence of
an unclaimed `user_invite_tokens` row, surfaced as the listing's
`pending_invite` field. Consumers that want a truthful
last-seen MUST treat a pending-invite row as never-seen rather
than trusting `last_seen_at` (every real sign-in path stamps it
to now, but an unclaimed invite has never hit one).
* `gitea_id = NULL`, `gitea_login = NULL` same as a v0.7.0
OTC-provisioned user; the OAuth identity is grandfathered if
the user ever lands through that path.
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "rfc-app-frontend",
"private": true,
"version": "0.31.3",
"version": "0.31.4",
"type": "module",
"scripts": {
"dev": "vite",
+44 -7
View File
@@ -34,13 +34,31 @@
.role-owner { background: var(--c-warning-accent); }
.role-admin { background: var(--c-accent-strong); }
/* The default surface for .btn-link is LIGHT (breadcrumb bar, PR view,
* modals, discussion panel, inbox). It renders as a quiet secondary
* button: white fill, hairline border, dark label. The dark app-header
* reuse ("Sign out") opts back into the translucent-on-dark treatment
* via the .app-header scope below. (Before v0.31.4 the base rule WAS the
* dark-header style, so every light-surface .btn-link was white-on-near-
* white and effectively invisible.) */
.btn-link {
color: var(--c-white); text-decoration: none;
background: var(--color-on-dark-soft);
display: inline-flex; align-items: center;
color: var(--c-gray-700); text-decoration: none;
background: var(--c-white);
border: 1px solid var(--c-gray-300);
border-radius: var(--radius-md); padding: 4px 10px;
font-size: var(--text-base);
font-size: var(--text-base); cursor: pointer;
}
.btn-link:hover {
background: var(--c-gray-50); border-color: var(--c-gray-400); color: var(--c-ink);
}
/* Dark header reuse: restore the original translucent-white treatment. */
.app-header .btn-link {
color: var(--c-white); background: var(--color-on-dark-soft); border-color: transparent;
}
.app-header .btn-link:hover {
color: var(--c-white); background: var(--color-on-dark-hover); border-color: transparent;
}
.btn-link:hover { background: var(--color-on-dark-hover); }
.btn-signin-header {
color: var(--c-white); text-decoration: none;
@@ -599,7 +617,7 @@
}
.rfc-breadcrumb {
display: flex; align-items: center; gap: 8px;
display: flex; align-items: center; flex-wrap: wrap; gap: 8px;
padding: 10px 16px;
border-bottom: 1px solid var(--c-gray-200);
background: var(--c-gray-50);
@@ -612,7 +630,25 @@
}
.breadcrumb-sep { color: var(--c-gray-300); }
.breadcrumb-meta { color: var(--c-gray-500); font-size: var(--text-sm); }
.breadcrumb-actions { margin-left: auto; display: flex; gap: 8px; align-items: center; }
.breadcrumb-actions {
margin-left: auto;
display: flex; flex-wrap: wrap; justify-content: flex-end;
gap: 8px; align-items: center; min-width: 0;
}
/* Normalize every action in the bar to one height + shape so the mode
* toggle, the filled CTAs (Start Contributing / Open PR / Graduate) and
* the secondary buttons (Metadata / Claim ownership / Invitations / )
* line up as a single, intentional control group. Higher specificity
* than the per-variant rules, so it harmonizes their size/radius/type
* without disturbing each variant's fill colors. */
.breadcrumb-actions > button,
.breadcrumb-actions > a {
display: inline-flex; align-items: center;
height: 30px; padding: 0 12px;
border-radius: var(--radius-md);
font-size: var(--text-sm); font-weight: 600;
white-space: nowrap;
}
.btn-mode-toggle {
font-size: var(--text-sm); font-weight: 600;
@@ -1444,7 +1480,8 @@
font-size: var(--text-sm);
}
.diff-mode-toolbar .btn-link.active {
font-weight: 600; color: var(--c-ink);
font-weight: 600; color: var(--c-white);
background: var(--c-ink); border-color: var(--c-ink);
}
.pr-diff-accent {
margin-left: auto;