UI pass: rating widget, issue section headers, ratings verdict column

Finishes the Codex partial (0cdc302) after seeing it rendered.

Rating widget
- Inactive segments go text-ink-2 (ink on hover) so the group recedes
  beside serif body copy; the per-verdict tint stays the only colour.
- Hit area: the ::after overlay is positioned against the padding box,
  so -inset-y-1/-1.5 only gave 37/41 px. Now -inset-y-1.5/-2 -> 41 px
  desktop, 45 px phone.
- focus-visible hugs the segment (z-20, ring-offset-0); the inherited
  ring-offset-2 painted a detached box over the neighbouring buttons.
- The note field is a quiet bottom-hairline input everywhere (rule at
  rest, ink on focus) instead of a box on reader pages and an invisible
  border in tables.

Issue section headers
- The Brief, every section name and Colophon now use one serif
  uppercase, letter-spaced mark on a double rule. A sentence-case
  text-2xl header was screenshotted first and rejected: it reads as
  another headline, which is the problem being fixed.

Ratings table verdict column
- New .rating-cell block: a one-column 15rem grid, segmented group with
  clear/cleared flush right, note field beneath on the same width, group
  top-aligned with the article title. 13rem cannot hold the labels plus
  the wider "cleared" state, so rows wrapped and overlapped the Article
  column.
- Drop the dead CurrentRow::verdict field left over from removing the
  badge (clippy -D warnings).

Also: reader-page on world/behind so the text-wrap: balance polish item
covers those chapters.

Verified on the dev server at 127.0.0.1:3602 in light and dark, desktop
and 390 px: rating clicks toggle .active/aria-pressed with no navigation
and no console output; note text round-trips; cargo fmt, clippy
--all-targets -D warnings, cargo test (445 lib + integration) and
npm run css:check are clean. See docs/plans/briefs/ui-pass/handoff-rating.md.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MD4VWGq6mGcd8Bg67qyx9k
This commit is contained in:
2026-09-04 17:42:21 +00:00
co-authored by Claude Fable 5.1
parent bcbe1583af
commit 4248e48a8b
6 changed files with 187 additions and 18 deletions
+1 -3
View File
@@ -344,7 +344,6 @@ struct CurrentRow {
feed_title: String,
issue_date: String,
badge: String,
verdict: String,
widget: RatingWidget,
when: String,
source: String,
@@ -678,7 +677,7 @@ async fn build_current_rows(
.or_insert_with(|| feed_title_for(article.as_ref(), *feed_id));
}
let (badge, verdict) = widget_label(&rating.label);
let (badge, _) = widget_label(&rating.label);
if filters.label.as_deref().is_some_and(|label| label != badge) {
continue;
}
@@ -715,7 +714,6 @@ async fn build_current_rows(
feed_title: rating.feed_title.clone(),
issue_date: issue_date.clone(),
badge: badge.to_string(),
verdict: verdict.to_string(),
widget: RatingWidget {
article_id: rating.article_id,
issue_date,
File diff suppressed because one or more lines are too long
+16 -12
View File
@@ -157,15 +157,16 @@
.prose-body li + li { @apply mt-2; }
.reader-page :where(h1, h2, h3, h4), .reader-section-heading { text-wrap:balance; }
.reader-section-heading { @apply border-t-2 border-rule-strong pt-3 font-serif text-xl font-semibold leading-[1.1] tracking-[-0.01em] text-ink; }
.reader-section-heading { @apply border-t-[3px] border-double border-rule-strong pt-3 font-serif text-[1.05rem] font-semibold uppercase leading-[1.2] tracking-[0.15em] text-ink; }
.rating { @apply my-5 flex flex-wrap items-center gap-x-3 font-sans text-xs; }
.rating-prompt { @apply font-serif text-[0.8rem] italic text-muted; }
.rating-actions { @apply flex min-h-10 items-center whitespace-nowrap; }
.rating button[data-label] { @apply relative h-8 text-xs duration-150; transition-property:transform, background-color, color, border-color; }
.rating button[data-label]::after { content:""; @apply absolute -inset-y-1 inset-x-0; }
.rating button[data-label]::after { content:""; @apply absolute -inset-y-1.5 inset-x-0; }
.rating button[data-label]:active { transform:scale(0.96); }
.rating button[data-label]:not(.clear) { @apply -ml-px rounded-none border-rule bg-transparent px-2 py-1 text-ink first:ml-0 first:rounded-l-sm last:rounded-r-sm hover:z-10 hover:border-ink hover:bg-paper-2; }
.rating button[data-label]:focus-visible { @apply z-20 ring-offset-0; }
.rating button[data-label]:not(.clear) { @apply -ml-px rounded-none border-rule bg-transparent px-2 py-1 text-ink-2 first:ml-0 first:rounded-l-sm last:rounded-r-sm hover:z-10 hover:border-ink hover:bg-paper-2 hover:text-ink; }
.rating button.active[data-label="loved"] { @apply z-10 border-loved text-loved; background:color-mix(in oklab, var(--loved) 14%, transparent); }
.rating button.active[data-label="good"] { @apply z-10 border-good text-good; background:color-mix(in oklab, var(--good) 14%, transparent); }
.rating button.active[data-label="down"] { @apply z-10 border-down text-down; background:color-mix(in oklab, var(--down) 14%, transparent); }
@@ -176,7 +177,7 @@
.rating button.clear.active .rating-clear-label { @apply hidden; }
.rating button.clear.active .rating-cleared-label { @apply inline; }
.rating-note { @apply mt-2 flex w-full max-w-md basis-full items-center gap-2 text-[0.72rem] text-muted; }
.rating-note input { @apply min-w-0 flex-1; }
.rating-note input { @apply min-w-0 flex-1 rounded-none border-x-0 border-t-0 border-b border-rule bg-transparent px-0 py-1 shadow-none focus:border-ink; }
.flash { @apply my-4 border-l-4 border-accent bg-paper-2 px-4 py-3 font-sans text-sm; }
.error { @apply border-l-4 border-down bg-paper-2 px-4 py-3 font-sans text-sm text-down; }
@@ -244,16 +245,19 @@
tr.superseded > td { @apply text-muted; }
tr.superseded a { @apply text-muted; }
/* the rating widget has to fit inside a table cell */
td .rating, .picks .rating { @apply my-0 max-w-[15rem] gap-y-0 text-xs; }
/* the rating widget has to fit inside a table cell or a dense list */
td .rating, .picks .rating { @apply my-0 max-w-none gap-y-1 text-xs; }
.picks .rating { @apply mt-2; }
td .rating-prompt, .picks .rating-prompt { @apply hidden; }
td .rating button[data-label]:not(.clear), .picks .rating button[data-label]:not(.clear) { @apply px-2 py-1 text-xs; }
td .rating button[data-label]:not(.clear), .picks .rating button[data-label]:not(.clear) { @apply px-1.5 py-1 text-xs; }
td .rating button.clear, .picks .rating button.clear { @apply ml-1.5 px-0 text-xs; }
td .rating-note { @apply mt-1 max-w-[13rem] gap-0 text-[0.7rem]; }
td .rating-note { @apply mt-0 max-w-none gap-1.5 text-[0.7rem]; }
td .rating-note-label { @apply sr-only; }
td .rating-note input { @apply rounded-none border-x-0 border-t-0 border-b border-transparent bg-transparent px-0 py-1 text-xs shadow-none focus:border-ink focus:ring-0; }
.rating-cell .rating { @apply w-[13rem]; }
.picks .rating { @apply max-w-none; }
td .rating-note input { @apply text-xs; }
/* the ratings table's verdict cell: one column, buttons then note, nothing wraps */
.rating-cell .rating { @apply grid w-[15rem] grid-cols-1 gap-y-1.5; }
.rating-cell .rating-actions { @apply flex w-full min-h-0 items-center; }
.rating-cell .rating button.clear { @apply ml-auto; }
pre.block, pre.preview, pre.journal { @apply overflow-auto whitespace-pre-wrap rounded-sm bg-paper-2 p-3 font-mono text-[0.8125rem] leading-relaxed; overflow-wrap:anywhere; }
pre.block { @apply max-h-64; }
@@ -333,7 +337,7 @@
.kv { @apply block; } .kv dt { @apply mt-2; }
.rating { @apply items-stretch; } .rating-prompt, .rating-note { @apply basis-full; } .rating-prompt { @apply mb-1; }
.rating-actions { @apply min-h-11; }
.rating button[data-label]::after { @apply -inset-y-1.5; }
.rating button[data-label]::after { @apply -inset-y-2; }
.filters label { @apply w-full; }
.filters input, .filters select { @apply w-full; }
}
+1 -1
View File
@@ -1,3 +1,3 @@
{% extends "layout.html" %}{% block ears %}<span class="sm:hidden">{{ toc.short_date }}</span><span class="hidden sm:inline">{{ toc.display_date }} · No. {{ toc.issue_number }}</span>{% endblock %}{% block content %}<div class="mx-auto max-w-7xl lg:grid lg:grid-cols-[16rem_minmax(0,1fr)] lg:gap-x-10 lg:px-6 xl:grid-cols-[18rem_minmax(0,1fr)] xl:gap-x-14">{% include "_toc.html" %}
<article class="mx-auto mt-10 w-full max-w-[68ch] px-4 sm:px-6 lg:mt-12 lg:px-0"><header class="mb-9"><p class="font-sans text-[0.72rem] uppercase tracking-[0.12em] text-muted">Edition notes</p><h1 class="mt-2 text-4xl font-semibold leading-[1.1] tracking-[-0.01em] sm:text-5xl">Behind the paper</h1></header><section class="border-y border-rule py-6"><p>{{ summary_line }}</p><p class="mt-3">{{ admitted_line }}</p><p class="mt-3">{{ learned_line }}</p></section><section class="mt-10"><h2 class="border-t border-rule pt-2 font-sans text-[0.72rem] font-semibold uppercase tracking-[0.12em] text-muted">Near misses</h2><p class="mt-5 italic text-ink-2">Highest utility not selected.</p>{% if near_misses.is_empty() %}<p class="mt-4 text-muted">None recorded for this run.</p>{% else %}<ol class="mt-5 list-decimal space-y-3 pl-6">{% for near_miss in near_misses %}<li>{% if page.is_admin() %}<a class="text-ink decoration-rule hover:decoration-accent" href="/dashboard/articles/{{ near_miss.article_id }}">{{ near_miss.line }}</a>{% else %}{{ near_miss.line }}{% endif %}</li>{% endfor %}</ol>{% endif %}</section><p class="mt-10 border-l-2 border-rule pl-4 italic text-ink-2">{{ models_line }}</p><p class="mt-10 border-t border-rule pt-5 font-sans text-sm"><a href="{{ issue_href }}">← Back to this issue</a></p></article>
<article class="reader-page mx-auto mt-10 w-full max-w-[68ch] px-4 sm:px-6 lg:mt-12 lg:px-0"><header class="mb-9"><p class="font-sans text-[0.72rem] uppercase tracking-[0.12em] text-muted">Edition notes</p><h1 class="mt-2 text-4xl font-semibold leading-[1.1] tracking-[-0.01em] sm:text-5xl">Behind the paper</h1></header><section class="border-y border-rule py-6"><p>{{ summary_line }}</p><p class="mt-3">{{ admitted_line }}</p><p class="mt-3">{{ learned_line }}</p></section><section class="mt-10"><h2 class="border-t border-rule pt-2 font-sans text-[0.72rem] font-semibold uppercase tracking-[0.12em] text-muted">Near misses</h2><p class="mt-5 italic text-ink-2">Highest utility not selected.</p>{% if near_misses.is_empty() %}<p class="mt-4 text-muted">None recorded for this run.</p>{% else %}<ol class="mt-5 list-decimal space-y-3 pl-6">{% for near_miss in near_misses %}<li>{% if page.is_admin() %}<a class="text-ink decoration-rule hover:decoration-accent" href="/dashboard/articles/{{ near_miss.article_id }}">{{ near_miss.line }}</a>{% else %}{{ near_miss.line }}{% endif %}</li>{% endfor %}</ol>{% endif %}</section><p class="mt-10 border-l-2 border-rule pl-4 italic text-ink-2">{{ models_line }}</p><p class="mt-10 border-t border-rule pt-5 font-sans text-sm"><a href="{{ issue_href }}">← Back to this issue</a></p></article>
</div>{% endblock %}
+1 -1
View File
@@ -1,3 +1,3 @@
{% extends "layout.html" %}{% block ears %}<span class="sm:hidden">{{ toc.short_date }}</span><span class="hidden sm:inline">{{ toc.display_date }} · No. {{ toc.issue_number }}</span>{% endblock %}{% block content %}<div class="mx-auto max-w-7xl lg:grid lg:grid-cols-[16rem_minmax(0,1fr)] lg:gap-x-10 lg:px-6 xl:grid-cols-[18rem_minmax(0,1fr)] xl:gap-x-14">{% include "_toc.html" %}
<article class="mx-auto mt-10 w-full max-w-[68ch] px-4 sm:px-6 lg:mt-12 lg:px-0"><header class="mb-9">{% match display_date %}{% when Some with (date) %}<p class="font-sans text-[0.72rem] uppercase tracking-[0.12em] text-muted">{{ date }}</p>{% when None %}{% endmatch %}<h1 class="mt-2 text-4xl font-semibold leading-[1.1] tracking-[-0.01em] sm:text-5xl">World Briefing</h1></header><div class="prose-body border-t border-rule pt-7">{{ body_html|safe }}</div><p class="mt-10 border-t border-rule pt-5 font-sans text-sm"><a href="{{ issue_href }}">← Back to this issue</a></p></article>
<article class="reader-page mx-auto mt-10 w-full max-w-[68ch] px-4 sm:px-6 lg:mt-12 lg:px-0"><header class="mb-9">{% match display_date %}{% when Some with (date) %}<p class="font-sans text-[0.72rem] uppercase tracking-[0.12em] text-muted">{{ date }}</p>{% when None %}{% endmatch %}<h1 class="mt-2 text-4xl font-semibold leading-[1.1] tracking-[-0.01em] sm:text-5xl">World Briefing</h1></header><div class="prose-body border-t border-rule pt-7">{{ body_html|safe }}</div><p class="mt-10 border-t border-rule pt-5 font-sans text-sm"><a href="{{ issue_href }}">← Back to this issue</a></p></article>
</div>{% endblock %}