Web dashboard v2 step 3: dashboard restyle

Restyle every template under src/web/templates/dashboard/ plus the shared
partials _candidate_row.html, _signals_table.html and _pagination.html with
Tailwind utilities and the design system step 2 built, so the dashboard reads
as the sans-serif instrument-panel half of the same publication as the reader
pages: a page header on every page, stat tiles, compact tables with sticky
heads inside .scroll-x, tinted badges, one inline filter form per page,
settings groups with a sticky save bar, job cards, a scrolling journal block
and a rating widget that fits inside a table cell.

tailwind.css gains the dashboard vocabulary (.page-head/.page-desc/
.page-actions, .tiles/.tile*, .cell-wrap, .scroll-x.tall, .filters,
.pager/.tabs, .setting*/.save-bar, .disclosure, .sparklines/.spark-figure,
td .rating overrides, tr.superseded) plus a styled <meter> and a disabled
control state. Renames class="inline" to form-inline: Tailwind emits an
.inline display utility that was beating form.inline's layout.

Three small backend changes: job cards show the status and time of their last
run (attach_last_runs, no extra query, unit-tested); the Stats page passes
"stats" as its nav key so the admin nav no longer highlights Overview; and the
settings load-error banner drops its "! " prefix, with its assertion updated.

cargo test: 477 passed. npm run css:check clean.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NHyYupFdBiR4VfoUM7NjSM
This commit is contained in:
2026-09-03 21:30:09 +00:00
co-authored by Claude Fable 5.1
parent 29c66da865
commit a4c338a9f9
24 changed files with 665 additions and 259 deletions
+7 -7
View File
@@ -1,16 +1,16 @@
<tr>
<td><details class="signals"><summary><a href="{{ candidate.href }}">{{ candidate.title }}</a></summary>{% let signals = candidate.signals %}{% include "_signals_table.html" %}</details></td>
<td>{{ candidate.feed }}</td>
<td class="cell-wrap"><details class="signals"><summary><a href="{{ candidate.href }}">{{ candidate.title }}</a></summary>{% let signals = candidate.signals %}{% include "_signals_table.html" %}</details></td>
<td class="cell-tight text-muted">{{ candidate.feed }}</td>
<td class="num">{{ candidate.words }}</td>
<td><span class="badge {{ candidate.stage }}">{{ candidate.stage }}</span></td>
<td>{% if let Some(reason) = candidate.reason %}<span class="badge reason">{{ reason }}</span>{% endif %}</td>
<td>{% if let Some(first) = candidate.admitted_first %}{{ first }}{% if !candidate.admitted_rest.is_empty() %} <span class="muted">({{ candidate.admitted_rest }})</span>{% endif %}{% endif %}</td>
<td class="cell-tight"><span class="badge {{ candidate.stage }}">{{ candidate.stage }}</span></td>
<td class="cell-tight">{% if let Some(reason) = candidate.reason %}<span class="badge reason">{{ reason }}</span>{% endif %}</td>
<td class="cell-tight text-muted">{% if let Some(first) = candidate.admitted_first %}{{ first }}{% if !candidate.admitted_rest.is_empty() %} <span class="muted text-xs">({{ candidate.admitted_rest }})</span>{% endif %}{% endif %}</td>
<td class="num">{{ candidate.utility }}</td>
<td class="num">{{ candidate.rank }}</td>
<td class="num">{{ candidate.cluster }}</td>
<td class="num">{{ candidate.triage }}</td>
<td class="num">{{ candidate.quality }}</td>
<td class="num">{{ candidate.fit }}</td>
<td>{% if candidate.exploration %}<span class="badge">exploration</span>{% endif %}{% if candidate.auto_include %}<span class="badge">auto</span>{% endif %}</td>
<td>{% if let Some(why) = candidate.editor_why %}{{ why }}{% endif %}</td>
<td class="cell-tight">{% if candidate.exploration %}<span class="badge">exploration</span>{% endif %}{% if candidate.auto_include %}<span class="badge">auto</span>{% endif %}</td>
<td class="cell-wrap text-muted">{% if let Some(why) = candidate.editor_why %}{{ why }}{% endif %}</td>
</tr>