Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NHyYupFdBiR4VfoUM7NjSM
6.2 KiB
Step 2 — Tailwind v4 design system, theme toggle, reader pages
Worktree: /home/thallada/workspace/the-daily-epub-v2b (branch v2-frontend).
Read 00-shared.md first, especially the design language — it is the spec.
You own the design system and the reader-facing templates. Do not restyle
src/web/templates/dashboard/* (step 3 does that, on top of your system); they
may look plain until then, which is expected. Do not implement the sidebar
table of contents (step 4).
1. Tailwind toolchain (no Node at build or run time)
package.jsonat the repo root withdevDependenciestailwindcssand@tailwindcss/clipinned to4.3.3, scriptscss(tailwindcss -i src/web/tailwind.css -o src/web/static/app.css --minify),css:watch, andcss:check(build to a temp file anddiffagainst the committedapp.css, non-zero on drift).package-lock.jsoncommitted;node_modules/anddev/added to.gitignore.- The compiled
src/web/static/app.cssis committed and embedded withinclude_str!exactly as today, socargo buildand the server never need Node. Document the workflow (edit templates/tailwind.css→npm run css→ commit both) inREADME.mdand indocs/runbooks/web-dashboard-rollout.md. - Fonts:
src/web/static/fonts/Newsreader.woff2,Newsreader-italic.woff2andOFL.txtare already in your worktree. Serve them fromweb::static_asset(include_bytes!,font/woff2, same ETag/304 logic, long cache) — extend the route so/static/fonts/Newsreader.woff2or/static/Newsreader.woff2works (pick one, keepsafematching: a fixed match arm per file, no path joins).@font-faceintailwind.csswithfont-weight: 200 800; font-stretch: normal; font-display: swap;and theunicode-rangefor latin from Google's CSS (U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD). Add a<link rel="preload" as="font" type="font/woff2" crossorigin>for the regular face in the layout.
2. Theme toggle (item 2)
src/web/static/theme.js(new, served likeapp.js,include_str!): a handful of lines run synchronously in<head>before paint: readlocalStorage.theme("light"/"dark", anything else = system), setdocument.documentElement.dataset.themeaccordingly (remove the attribute for system). Wrap storage access in try/catch. No inline script (CSP).- The toggle button in the layout's "ears" row:
<button type="button" data-theme-toggle aria-label="Theme: system">with an inline SVG icon (sun / moon / auto) and a short visible label.app.jshandles clicks: cycle system → light → dark → system, persist (removeItemfor system), update the attribute, icon and label, and re-evaluate onmatchMedia("(prefers-color-scheme: dark)")changes while in system mode. The three icons can all be in the markup withhiddentoggled — remember[hidden]must win over anydisplayutility. - Everything themed goes through the semantic tokens;
color-schemefollows the theme so native controls and scrollbars match. Check both themes in screenshots: contrast of muted text on paper, accent on dark paper, rating buttons, flash, focus rings, images in dark mode.
3. Design system + layout
src/web/tailwind.css per the shared brief (tokens, dark variant, fonts,
@layer base for body/typography defaults, @layer components for .btn,
.btn-primary, .badge + states (selected loved good down excluded shortlisted admitted eligible cleared assessed triaged reason running requested ok degraded failed dry_run), .kv, .scroll-x, .prose-body, base tables
and form controls — step 3 will consume these). Rewrite layout.html: ears
row, masthead, primary nav with aria-current="page" derived from
page.active_nav (check what values handlers set), the admin nav row, flash,
<main>, footer, scripts. Add an accessible "skip to content" link.
4. Reader templates (restyle in place; keep every handler field)
issue_public.html, issue_full.html, issue_list.html, article.html,
world.html, behind.html, login.html, account.html, error.html,
_rating_widget.html. The public and full issue pages are the showcase — set
them like a front page: dateline, stats line, The Brief with its drop cap,
download buttons, sections as tracked labels on rules, entries per the spec,
lead story larger, the chapter links (World Briefing · Behind the paper) as a
centred rule-bounded row, the colophon as a two-column .kv in small sans.
Note: the backend step adds summary/why to the public entries and it will
merge after you; leave a clear place for them in issue_public.html (the same
summary/why treatment as the full issue) guarded with {% if let Some(...) %}
only if the field exists in your tree — otherwise just leave a comment
{# step 1 adds summary/why here #} so the merge is trivial.
Article page: h1 → source link, byline/meta, "Why it's here" callout, social
line, the summary as standfirst, the body in .prose-body, discussion with
nested comment styling (blockquote.reply indent, .comment-meta small
sans), rating control, "Read online ↗" button, prev/next cards, back link.
Login/account: a narrow centred card-free form, labelled inputs, primary button.
Archive: months as tracked labels, issues as a hairline list with date, number,
article count.
Phones (390px): masthead scales down, nav wraps as a centred row, everything
single-column, tap targets ≥ 40px, tables scroll inside .scroll-x.
5. Verify
Run the seeded dev server (shared brief), screenshot /, /issues,
/issues/2026-09-02, /issues/2026-09-02/articles/1, /issues/2026-09-02/world,
/issues/2026-09-02/behind, /login, /account as anonymous, reader and
admin, in light and dark, desktop and mobile, and look at them. Fix what
looks off. Make sure npm run css:check passes on the committed CSS, cargo fmt, clippy -D warnings and cargo test are green (minus the sandbox-only
failures), then write handoff-step2.md and commit once on v2-frontend:
Web dashboard v2 step 2: Tailwind design system, theme toggle, reader pages.