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:
@@ -0,0 +1,132 @@
|
||||
# Step 3 handoff — dashboard restyle
|
||||
|
||||
Branch `v2-dashboard`, worktree `/home/thallada/workspace/the-daily-epub-v2c`, one commit
|
||||
on top of the merged `v2-frontend` work (step 1 + step 2).
|
||||
|
||||
## Landed
|
||||
|
||||
Every template under `src/web/templates/dashboard/` plus the shared partials
|
||||
`_candidate_row.html`, `_signals_table.html` and `_pagination.html` is restyled with
|
||||
Tailwind utilities and the component layer step 2 built. `src/web/static/app.css` is
|
||||
rebuilt and committed; `npm run css:check` is clean.
|
||||
|
||||
### New/changed pieces in `src/web/tailwind.css`
|
||||
|
||||
`@layer base` gained: a disabled state for every control, `summary { cursor:pointer }`,
|
||||
and a styled `<meter>` (webkit + moz pseudo-elements, `--rule` track, `--good` value,
|
||||
`--down` when `.over`) for the overview budget bars.
|
||||
|
||||
`@layer components` gained the dashboard vocabulary the templates now use:
|
||||
|
||||
| class | what it is |
|
||||
| --- | --- |
|
||||
| `.page-head`, `.page-desc`, `.page-actions`, `.page-eyebrow` | the page header on all 13 pages: eyebrow/breadcrumb, `h1`, one-line description, right-aligned actions, hairline under it |
|
||||
| `.tiles`, `.tile`, `.tile-label`, `.tile-num`, `.tile-delta` | stat tiles (overview, stats, run detail) |
|
||||
| `.cell-wrap`, `.cell-tight` | long text cells (`message`, `editor_why`, notes, reasons) wrap with `overflow-wrap:anywhere` and a max width; dates/badges stay on one line |
|
||||
| `.scroll-x.tall` | opt-in `max-h-[70vh] overflow-y-auto`, which is what actually makes the base layer's `thead { sticky top-0 bg-paper }` do something. Applied to the long tables (runs, candidates, articles, ratings, jobs history, users, settings history, stats) and deliberately not to the small tables inside cards |
|
||||
| `.filters` / `.filter-actions` / `.form-inline` | one inline filter form per page: rule-bounded row, tiny uppercase labels above each control, buttons grouped at the end |
|
||||
| `.pager`, `.tabs`, `.pagination` | pagination row and the ratings tab strip (accent underline on `.active`) |
|
||||
| `.setting`, `.setting-label`, `.setting-input`, `.help`, `.default`, `.secret`, `button.reset`, `.save-bar` | settings: two-column rows collapsing to one on phones, monospace keys, disabled env-locked fields with a `locked` badge and a lock note, sticky save bar with a soft top shadow |
|
||||
| `.disclosure` (`details.disclosure` + `.disclosure-body`) | the framed `<details>` blocks (ratings "How ratings enter the algorithm", stats "As text", article "Explain") |
|
||||
| `.sparklines`, `.spark-figure`, `.spark-axis`, `.spark-legend` | sparkline cards; the SVG classes (`.spark`, `.s0`…`.s5`, `.line`) are untouched |
|
||||
| `.funnel`, `.funnel-cell` | the funnel bar gets a `bg-paper-2` track; `class="funnel"` is unchanged because a test asserts it verbatim |
|
||||
| `td .rating` / `.picks .rating` overrides | the rating widget compacted for table cells and the overview picks list: prompt hidden, `min-h-8` buttons, small note field, capped width |
|
||||
| `tr.superseded` | muted row (the class stays exactly `superseded`; a test counts it) |
|
||||
| `.badge` default | bare `<span class="badge">` now has a neutral tint, plus `.badge.restart` / `.badge.warn` |
|
||||
|
||||
`.dashboard` gained a vertical rhythm (`> * + * { mt-4 }`), section headings as tracked
|
||||
uppercase labels on a rule (`.dashboard > h2`), muted underlined table links, and
|
||||
`code` chips on `bg-paper-2`.
|
||||
|
||||
### Per page
|
||||
|
||||
- **overview** — five stat tiles (last run, warnings, verdicts this week, unrated picks,
|
||||
active jobs) above the existing cards; budget rows are now provider + spend + a meter bar;
|
||||
jobs/picks lists are hairline-separated with ink links.
|
||||
- **runs** — page header with Stats/Jobs actions, inline status filter with a Reset that
|
||||
only appears when a filter is set, numeric columns right-aligned, empty-state row.
|
||||
- **run** — breadcrumb + status badge header, prev/issue/next as buttons in the header,
|
||||
four tiles (date, duration, cost, warnings), funnel with a track, cards for the report,
|
||||
empty-state row for candidates.
|
||||
- **articles / article** — filter form, wrapped title cells; the article page splits its
|
||||
facts into "Article" and "Provenance" cards, gives the rating widget its own
|
||||
"Your verdict" section and keeps everything else as tables.
|
||||
- **ratings** — description as the page header, tab strip, per-tab filter form, the
|
||||
in-cell rating widget, muted superseded rows.
|
||||
- **profile** — editor/preview split (`lg:grid-cols-2`, stacked on phones), monospace
|
||||
textarea, previews as capped `pre.preview` blocks, history table, `.kv` theme list.
|
||||
- **stats** — window buttons in the header, `summary` rendered as six stat tiles,
|
||||
sparkline cards, cards, two filterable tables, text output in a disclosure.
|
||||
- **jobs / job** — job cards now carry a status badge and a "last started" line linking
|
||||
to that job (see the one backend change below); the job page is breadcrumb + two cards
|
||||
+ a scrolling monospace journal.
|
||||
- **settings / settings history / users** — as described in the table above.
|
||||
|
||||
### Backend changes (three, all small)
|
||||
|
||||
1. `JobCard` gained `last_status` / `last_requested` / `last_id`, filled by a new
|
||||
`attach_last_runs` from the job rows the page already loads (matching `name` or
|
||||
`name-<date>` for the dated `generate` card). No extra query. Unit-tested.
|
||||
2. `stats::stats` passed `"dashboard"` as its nav key, so the admin nav highlighted
|
||||
*Overview* on the Stats page; it now passes `"stats"`.
|
||||
3. `settings.html` dropped the `! ` prefix in front of the "config.toml on disk does not
|
||||
load" banner (the `.error` bar already says it); the assertion in
|
||||
`settings_pages_render_save_and_show_hand_edits` was updated with it. That is the only
|
||||
test touched.
|
||||
|
||||
## Deviations
|
||||
|
||||
- **`class="inline"` was a latent bug**, the same shape as step 2's `.block` collision:
|
||||
Tailwind emits an `.inline { display:inline }` utility, which beats `form.inline`'s
|
||||
`@apply flex` in the components layer, so those forms were laid out inline. The class
|
||||
is renamed to `form-inline` in `jobs.html`, `profile.html` and `settings.html` and in
|
||||
`tailwind.css`. Nothing asserted on it.
|
||||
- **Sticky table heads need a scroll box.** `thead { sticky top-0 }` from step 2 is inert
|
||||
inside a plain `overflow-x:auto` container, so the long tables opt into
|
||||
`.scroll-x.tall` (`max-h-[70vh]`). Small tables inside cards keep the plain `.scroll-x`.
|
||||
- The brief asks for "last run" on the job cards; the data was not in `JobCard`, so it was
|
||||
added (item 1 above) rather than skipped.
|
||||
- `_rating_widget.html` itself is untouched — two tests assert its exact attribute order
|
||||
(`value="loved" data-label="loved" class="active"`), so the table-cell treatment is done
|
||||
purely with `td .rating …` rules.
|
||||
- `.dashboard > h2` renders section headings as small tracked uppercase labels on a rule
|
||||
rather than as large sans headings; `.card h2`/`h3` stay normal-weight `text-base`.
|
||||
|
||||
## Hooks and asserted classes (all preserved)
|
||||
|
||||
`form.rating`, `button[data-label]`, `.active` + `aria-pressed`, `form[data-confirm]`,
|
||||
`details[id]` (`ratings-how`, `profile-prompt`, plus new `stats-text` and `article-explain`),
|
||||
`table[data-filter]`, `.table-filter`, `button[data-reset]`/`data-default`, `[data-refresh]`,
|
||||
`.scroll-x`, `.spark`/`.s0`–`.s5`/`.line`, `class="funnel"`, `class="superseded"`,
|
||||
`badge <state>`, `<h1>Users</h1>`, every form `action`/`name`/`id` and every `aria-*`.
|
||||
|
||||
## Verification
|
||||
|
||||
- `npm run css` + `npm run css:check` — clean.
|
||||
- `cargo fmt`, `cargo clippy --all-targets -- -D warnings` — clean.
|
||||
- `cargo test` — **477 passed, 0 failed** (441 lib + 9 + 2 + 3 + 4 + 7 + 9 + 2 integration,
|
||||
one binary with 0 tests). Run outside the Codex sandbox, so the socket-binding tests ran.
|
||||
- Dev server on `127.0.0.1:3601` (port 3599/3602 belong to other agents, 3499 to
|
||||
production). Every dashboard route returned 200, with **no console errors and no asset
|
||||
404s** in the server log.
|
||||
|
||||
### Screenshots
|
||||
|
||||
`/tmp/claude-1000/-home-thallada-workspace-the-daily-epub/e963db53-510f-4312-ac87-460291a92781/scratchpad/step3-shots/`
|
||||
— 58 PNGs, all signed in as `admin`: `/dashboard`, `/dashboard/runs`, `/dashboard/runs/2`,
|
||||
`/dashboard/articles`, `/dashboard/articles/1`, `/dashboard/ratings`,
|
||||
`/dashboard/ratings?tab=events`, `/dashboard/profile`, `/dashboard/stats`,
|
||||
`/dashboard/jobs`, `/dashboard/jobs/1`, `/dashboard/settings`,
|
||||
`/dashboard/settings/history`, `/dashboard/users` × {light, dark} × {1280px desktop,
|
||||
390px mobile}, full page, plus viewport shots of the settings save bar
|
||||
(`*-top.png`, desktop light/dark and mobile).
|
||||
|
||||
The throwaway `./dev` database was extended by hand for the shoot (four `jobs` rows, eight
|
||||
`rating_events`, four `config_changes`) so the jobs, ratings and settings-history pages had
|
||||
content; `dev/` is git-ignored and `examples/seed_dev_db.rs` was **not** changed. A future
|
||||
design pass may want those rows in the seeder.
|
||||
|
||||
## Left for later
|
||||
|
||||
- Step 4 adds the collapsing table-of-contents sidebar to the signed-in issue pages.
|
||||
- `examples/seed_dev_db.rs` still seeds no jobs, ratings or config changes.
|
||||
Reference in New Issue
Block a user