From 8a355b637f236baa5d45daa472b9f1954d0f5227 Mon Sep 17 00:00:00 2001 From: Tyler Hallada Date: Sun, 20 Sep 2026 21:08:32 +0000 Subject: [PATCH] Give every dashboard table cell a content class Co-Authored-By: Claude Fable 5.1 --- .../dashboard-tables/handoff-templates.md | 46 +++++++++++++++++++ src/web/templates/_candidate_row.html | 10 ++-- src/web/templates/dashboard/article.html | 22 ++++----- src/web/templates/dashboard/articles.html | 8 ++-- src/web/templates/dashboard/feeds.html | 2 +- src/web/templates/dashboard/interests.html | 2 +- src/web/templates/dashboard/jobs.html | 8 ++-- src/web/templates/dashboard/profile.html | 6 +-- src/web/templates/dashboard/ratings.html | 22 ++++----- src/web/templates/dashboard/run.html | 4 +- src/web/templates/dashboard/runs.html | 2 +- .../templates/dashboard/settings_history.html | 2 +- src/web/templates/dashboard/stats.html | 2 +- 13 files changed, 91 insertions(+), 45 deletions(-) create mode 100644 docs/plans/briefs/dashboard-tables/handoff-templates.md diff --git a/docs/plans/briefs/dashboard-tables/handoff-templates.md b/docs/plans/briefs/dashboard-tables/handoff-templates.md new file mode 100644 index 0000000..ea03fa8 --- /dev/null +++ b/docs/plans/briefs/dashboard-tables/handoff-templates.md @@ -0,0 +1,46 @@ +# Dashboard tables template handoff + +Implemented `02-templates.md` against `00-shared.md` and +`docs/dashboard-tables.md`. No columns, content, hooks, or attributes were +changed. The existing page-level `.scroll-x` wrappers already had the required +direct-child topology, so none moved. + +## Cell audit by template + +| Template | Cells that lost `cell-tight` | Cells that gained `cell-wrap` | Clamp added | +| --- | --- | --- | --- | +| `_candidate_row.html` | Feed, stage, reason, admitted by, flags | Flags (badge list) | None | +| `_signals_table.html` | None | None | None | +| `dashboard/article.html` | Run history: stage, reason, admitted by; nearest articles: feed, rating; rating events: label, kind, source, user | Top-interests interest name | Rating-event note: inner `line-clamp-3` with the full note in `title` | +| `dashboard/articles.html` | Feed, last stage (badge plus run date), reason, rating | None | None | +| `dashboard/feeds.html` | None | None (Feed and Why already had it) | None (existing Feed/Why inner clamps retained) | +| `dashboard/interests.html` | Category | None | None | +| `dashboard/jobs.html` | Job name, requested by, status | None (message already had it) | Message: inner `line-clamp-3` with the full message in `title` | +| `dashboard/profile.html` | Saved by, restore action | Preview | None | +| `dashboard/ratings.html` | Imports: verdict, status; events: verdict, source, by; current: when/by, feed credit, used last run | Current feed credit | Import message, event note, and current note: inner `line-clamp-3` with full text in `title` | +| `dashboard/run.html` | Funnel stage; near misses: feed, stage | None | None | +| `dashboard/runs.html` | Status (badge plus dry-run text) | None | None | +| `dashboard/settings_history.html` | Who | None | None | +| `dashboard/stats.html` | Runs status | None | None | +| `dashboard/users.html` | None | None | None | + +`dashboard/feeds.html` also lost page-level `table-fixed`; its existing header +width hints and inner Feed/Why clamps remain. The card-local `table-fixed` feeds +table in `dashboard/run.html` remains as specified. + +## Deviations + +None. Templates whose cells were already compliant were left unchanged. + +## Verification + +- `cargo test --lib web::`: 135 passed; two Miniflux mock-server tests failed + only because sandbox `bind()` returned `PermissionDenied` at + `src/web/dashboard/feeds.rs:497`, as permitted by the brief. +- `cargo fmt`: passed. +- `cargo clippy --all-targets -- -D warnings`: passed. +- `git diff --check`: passed. + +The first clippy attempt exhausted the filesystem while writing generated Rust +metadata. Removing the disposable `target/debug/incremental` cache freed enough +space; the exact clippy command then completed successfully. diff --git a/src/web/templates/_candidate_row.html b/src/web/templates/_candidate_row.html index fe15115..4208353 100644 --- a/src/web/templates/_candidate_row.html +++ b/src/web/templates/_candidate_row.html @@ -1,15 +1,15 @@
{{ candidate.title }}{% let signals = candidate.signals %}{% include "_signals_table.html" %}
-{{ candidate.feed }} +{{ candidate.feed }} {{ candidate.words }} -{{ candidate.stage }} -{% if let Some(reason) = candidate.reason %}{{ reason }}{% endif %} -{% if let Some(first) = candidate.admitted_first %}{{ first }}{% if !candidate.admitted_rest.is_empty() %} ({{ candidate.admitted_rest }}){% endif %}{% endif %} +{{ candidate.stage }} +{% if let Some(reason) = candidate.reason %}{{ reason }}{% endif %} +{% if let Some(first) = candidate.admitted_first %}{{ first }}{% if !candidate.admitted_rest.is_empty() %} ({{ candidate.admitted_rest }}){% endif %}{% endif %} {{ candidate.utility }} {{ candidate.rank }} {{ candidate.cluster }} {{ candidate.triage }} {{ candidate.quality }} {{ candidate.fit }} -{% if candidate.exploration %}exploration{% endif %}{% if candidate.auto_include %}auto{% endif %} +{% if candidate.exploration %}exploration{% endif %}{% if candidate.auto_include %}auto{% endif %} diff --git a/src/web/templates/dashboard/article.html b/src/web/templates/dashboard/article.html index e525d61..f53f0a2 100644 --- a/src/web/templates/dashboard/article.html +++ b/src/web/templates/dashboard/article.html @@ -47,9 +47,9 @@ {% for row in history %}
run {{ row.run_id }} {{ row.status }}{% let signals = row.signals %}{% include "_signals_table.html" %}
{{ row.date }} -{{ row.stage }} -{% if let Some(reason) = row.reason %}{{ reason }}{% endif %} -{% if let Some(first) = row.admitted_first %}{{ first }}{% if !row.admitted_rest.is_empty() %} ({{ row.admitted_rest }}){% endif %}{% endif %} +{{ row.stage }} +{% if let Some(reason) = row.reason %}{{ reason }}{% endif %} +{% if let Some(first) = row.admitted_first %}{{ first }}{% if !row.admitted_rest.is_empty() %} ({{ row.admitted_rest }}){% endif %}{% endif %} {{ row.utility }} {{ row.rank }} {{ row.cluster }} @@ -57,7 +57,7 @@

Neighbours and interests

{% if let Some(signals) = latest_signals %}{% if signals.top_interests.is_empty() && signals.neighbours.is_empty() %}

The latest run recorded no interests or neighbours for this article.

{% else %}
-

Top interests

{% if signals.top_interests.is_empty() %}

None.

{% else %}
{% for interest in signals.top_interests %}{% endfor %}
interestzcos
{{ interest.name }}{{ interest.z }}{{ interest.cos }}
{% endif %}
+

Top interests

{% if signals.top_interests.is_empty() %}

None.

{% else %}
{% for interest in signals.top_interests %}{% endfor %}
interestzcos
{{ interest.name }}{{ interest.z }}{{ interest.cos }}
{% endif %}

Nearest rated neighbours

{% if signals.neighbours.is_empty() %}

None.

{% else %}
{% for neighbour in signals.neighbours %}{% endfor %}
labelcosarticle
{{ neighbour.label }}{{ neighbour.cos }}{{ neighbour.title }}
{% endif %}
{% endif %}{% else %}

No signals recorded.

{% endif %} @@ -67,9 +67,9 @@ {% for article in articles %} {{ article.cosine }} {{ article.title }} -{{ article.feed }} +{{ article.feed }} {{ article.first_seen }} -{% if let Some(rating) = article.rating %}{{ rating }}{% else %}unrated{% endif %} +{% if let Some(rating) = article.rating %}{{ rating }}{% else %}unrated{% endif %} {% endfor %}{% endif %}{% else %}

No embedding stored — run features-backfill.

{% endif %}

Embedding

@@ -80,12 +80,12 @@ whenlabelvaluekindsourceuserissuenote {% for event in events %} {{ event.event_at }} -{{ event.label }} +{{ event.label }} {{ event.value }} -{{ event.kind }} -{{ event.source }} -{% if let Some(user) = event.user %}{{ user }}{% else %}—{% endif %} +{{ event.kind }} +{{ event.source }} +{% if let Some(user) = event.user %}{{ user }}{% else %}—{% endif %} {% if let Some(date) = event.issue_date %}{{ date }}{% endif %} -{% if let Some(note) = event.note %}{{ note }}{% endif %} +{% if let Some(note) = event.note %}
{{ note }}
{% endif %} {% endfor %}{% endif %} {% endblock %} diff --git a/src/web/templates/dashboard/articles.html b/src/web/templates/dashboard/articles.html index 72f210d..d19d744 100644 --- a/src/web/templates/dashboard/articles.html +++ b/src/web/templates/dashboard/articles.html @@ -23,16 +23,16 @@ {% for article in articles %} {{ article.first_seen }} {{ article.title }} -{% if let Some(feed_id) = article.feed_id %}{{ article.feed }}{% else %}{{ article.feed }}{% endif %} +{% if let Some(feed_id) = article.feed_id %}{{ article.feed }}{% else %}{{ article.feed }}{% endif %} {% for interest in article.interests %}{{ interest.name }}{% if !loop.last %} {% endif %}{% endfor %} {{ article.words }} -{% if let Some(stage) = article.stage %}{{ stage }}{% if let Some(run_id) = article.run_id %} {% if let Some(date) = article.run_date %}{{ date }}{% else %}run {{ run_id }}{% endif %}{% endif %}{% else %}never considered{% endif %} -{% if let Some(reason) = article.reason %}{{ reason }}{% endif %} +{% if let Some(stage) = article.stage %}{{ stage }}{% if let Some(run_id) = article.run_id %} {% if let Some(date) = article.run_date %}{{ date }}{% else %}run {{ run_id }}{% endif %}{% endif %}{% else %}never considered{% endif %} +{% if let Some(reason) = article.reason %}{{ reason }}{% endif %} {{ article.utility }} {{ article.triage }} {{ article.quality }} {{ article.fit }} -{% if let Some(rating) = article.rating %}{{ rating }}{% endif %} +{% if let Some(rating) = article.rating %}{{ rating }}{% endif %} {% if let Some(date) = article.published %}{{ date }}{% endif %} {% endfor %}{% if articles.is_empty() %}No articles match this filter.{% endif %} {% include "dashboard/_pager.html" %} diff --git a/src/web/templates/dashboard/feeds.html b/src/web/templates/dashboard/feeds.html index 4255ab1..54f37a8 100644 --- a/src/web/templates/dashboard/feeds.html +++ b/src/web/templates/dashboard/feeds.html @@ -6,7 +6,7 @@ {% if let Some(error) = categories_error %}

{{ error }}

{% endif %} {% include "dashboard/_pager.html" %} -
+
{% for row in rows %} diff --git a/src/web/templates/dashboard/interests.html b/src/web/templates/dashboard/interests.html index 806cd86..8a45968 100644 --- a/src/web/templates/dashboard/interests.html +++ b/src/web/templates/dashboard/interests.html @@ -20,7 +20,7 @@ {% for row in rows %} - + diff --git a/src/web/templates/dashboard/jobs.html b/src/web/templates/dashboard/jobs.html index 01d394b..9dba4d4 100644 --- a/src/web/templates/dashboard/jobs.html +++ b/src/web/templates/dashboard/jobs.html @@ -21,14 +21,14 @@ {% for job in jobs %} - - + + - - + +{% endfor %}
ScoreFeedWhyArticlesSeenActions
{{ row.score }}
interestcategoryweightupdownrated matchesmatched articlesaddedactions
{{ row.name }}{% if let Some(category) = row.category %}{{ category }}{% else %}uncategorized{% endif %}{% if let Some(category) = row.category %}{{ category }}{% else %}uncategorized{% endif %} {{ row.weight }} {{ row.up }} {{ row.down }}
jobnamerequested byrequestedstartedfinisheddurationstatusmessagerun
{{ job.id }}{{ job.name }}{{ job.requested_by }}{{ job.name }}{{ job.requested_by }} {{ job.requested }} {{ job.started }} {{ job.finished }} {{ job.duration }}{{ job.status }}{% if let Some(message) = job.message %}{{ message }}{% endif %}{{ job.status }}{% if let Some(message) = job.message %}
{{ message }}
{% endif %}
{% if let Some(run_id) = job.run_id %}{{ run_id }}{% endif %}
{% endif %} {% endblock %} diff --git a/src/web/templates/dashboard/profile.html b/src/web/templates/dashboard/profile.html index d57660c..da2df92 100644 --- a/src/web/templates/dashboard/profile.html +++ b/src/web/templates/dashboard/profile.html @@ -30,10 +30,10 @@ {% for version in versions %} #{{ version.id }} {{ version.saved_at }} -{{ version.saved_by }} +{{ version.saved_by }} {{ version.bytes }} bytes -
{{ version.preview }}
-
+
{{ version.preview }}
+
{% endfor %} {% endif %} diff --git a/src/web/templates/dashboard/ratings.html b/src/web/templates/dashboard/ratings.html index 71f186e..3ad5014 100644 --- a/src/web/templates/dashboard/ratings.html +++ b/src/web/templates/dashboard/ratings.html @@ -19,9 +19,9 @@ {% for row in imports %} {{ row.requested }} {{ row.url }} -{{ row.verdict }} -{{ row.status }} -{{ row.message }} +{{ row.verdict }} +{{ row.status }} +
{{ row.message }}
{% if let Some(article_id) = row.article_id %}{{ article_id }}{% endif %} {% endfor %} {% if imports.is_empty() %}No rating imports yet.{% endif %} @@ -60,14 +60,14 @@ {% for event in events %} #{{ event.id }}{% if event.kind != "explicit" %} {{ event.kind }}{% endif %}{% if event.superseded %} superseded{% endif %} -{{ event.verdict }} +{{ event.verdict }} {{ event.value }} {{ event.title }} {% if !event.issue_date.is_empty() %}{{ event.issue_date }}{% endif %} {{ event.when }} -{{ event.source }} -{{ event.username }} -{{ event.note }} +{{ event.source }} +{{ event.username }} +
{{ event.note }}
{% endfor %} {% if events.is_empty() %}No rating events match.{% endif %} @@ -89,14 +89,14 @@ {% for row in current %} {% let widget = row.widget %}{% include "_rating_widget.html" %} {{ row.title }}
{{ row.feed_title }}{% if !row.issue_date.is_empty() %} · {{ row.issue_date }}{% endif %} -{{ row.when }}
{{ row.source }} · {{ row.username }} -{{ row.note }} +{{ row.when }}
{{ row.source }} · {{ row.username }} +
{{ row.note }}
{{ row.age_days }} d → {{ row.decay }} {% if row.has_embedding && !row.neighbour_weight.is_empty() %}{{ row.neighbour_weight }}{% if row.beyond_lookback %} (beyond lookback){% endif %}{% else if row.badge == "cleared" %}—{% else %}no embedding{% endif %} -{% for credit in row.feed_credits %}{{ credit.title }} {{ credit.credit }}{% if !loop.last %}
{% endif %}{% endfor %} +{% for credit in row.feed_credits %}{{ credit.title }} {{ credit.credit }}{% if !loop.last %}
{% endif %}{% endfor %} {% if row.in_prompt %}✓{% endif %} {% if row.in_rebuild %}✓{% endif %} -{% if row.used_total > 0 %}{{ row.used_total }} ({{ row.used_selected }} selected){% endif %} +{% if row.used_total > 0 %}{{ row.used_total }} ({{ row.used_selected }} selected){% endif %} {% endfor %} {% if current.is_empty() %}No current verdicts match.{% endif %} diff --git a/src/web/templates/dashboard/run.html b/src/web/templates/dashboard/run.html index a86dd5d..311aedf 100644 --- a/src/web/templates/dashboard/run.html +++ b/src/web/templates/dashboard/run.html @@ -17,7 +17,7 @@
{% for stage in funnel %} - + @@ -45,7 +45,7 @@

Near misses

{% if near_misses.is_empty() %}

None recorded.

{% else %}
stagesharereachedstopped herewhy
{{ stage.stage }}{{ stage.stage }}
{{ stage.reached }} {{ stage.stopped }}
-{% for miss in near_misses %}{% endfor %}
#scoretitlefeedqualityfitstagereason
{{ loop.index }}{{ miss.score }}{{ miss.title }}{{ miss.feed }}{{ miss.quality }}{{ miss.fit }}{{ miss.stage }}{% if let Some(reason) = miss.reason %}{{ reason }}{% endif %}
{% endif %} +{% for miss in near_misses %}{{ loop.index }}{{ miss.score }}{{ miss.title }}{{ miss.feed }}{{ miss.quality }}{{ miss.fit }}{{ miss.stage }}{% if let Some(reason) = miss.reason %}{{ reason }}{% endif %}{% endfor %}{% endif %}

Candidates

diff --git a/src/web/templates/dashboard/runs.html b/src/web/templates/dashboard/runs.html index b410d74..8795599 100644 --- a/src/web/templates/dashboard/runs.html +++ b/src/web/templates/dashboard/runs.html @@ -12,7 +12,7 @@ {% for run in runs %} {{ run.id }} {{ run.date }} -{{ run.status }}{% if run.dry_run %} dry{% endif %} +{{ run.status }}{% if run.dry_run %} dry{% endif %} {{ run.started }} {{ run.duration }} {{ run.funnel }} diff --git a/src/web/templates/dashboard/settings_history.html b/src/web/templates/dashboard/settings_history.html index 3b1057e..503f3e4 100644 --- a/src/web/templates/dashboard/settings_history.html +++ b/src/web/templates/dashboard/settings_history.html @@ -7,7 +7,7 @@ WhenWhoKeyBeforeAfter {% for change in changes %} {{ change.changed_at }} -{% match change.username %}{% when Some with (name) %}{{ name }}{% when None %}removed user{% endmatch %} +{% match change.username %}{% when Some with (name) %}{{ name }}{% when None %}removed user{% endmatch %} {{ change.key }} {% match change.old_value %}{% when Some with (value) %}
{{ value }}
{% when None %}absent{% endmatch %} {% match change.new_value %}{% when Some with (value) %}
{{ value }}
{% when None %}removed{% endmatch %} diff --git a/src/web/templates/dashboard/stats.html b/src/web/templates/dashboard/stats.html index 5bd2131..f564709 100644 --- a/src/web/templates/dashboard/stats.html +++ b/src/web/templates/dashboard/stats.html @@ -34,7 +34,7 @@

Runs in the window

{% if runs.is_empty() %}

No finished runs in the window.

{% else %}{% if runs.len() > 1 %}{% endif %}
-{% for run in runs %}{% endfor %}
rundatestatuscostselectedduration
{{ run.run_id }}{{ run.date }}{{ run.status }}{{ run.cost }}{{ run.selected }}{{ run.duration }}
{% endif %} +{% for run in runs %}{{ run.run_id }}{{ run.date }}{{ run.status }}{{ run.cost }}{{ run.selected }}{{ run.duration }}{% endfor %}{% endif %}
As text (daily-epub stats --days {{ days }})
{{ text }}
{% endblock %}