Firefox users saw a flash on the first navigation after an idle spell, and
Chrome occasionally flashed white. Nothing in the network path explained it
(assets are immutable, the stylesheet is render-blocking, a cold Firefox
load paints fully styled), so this removes every remaining way a page could
look different between its first paint and its final state:
- Both Newsreader faces are embedded in the served stylesheet as data: URIs
(CSP gains `font-src 'self' data:`; the preloads go away). A font fetched
by URL is applied after first paint whenever the browser has to bring it
back from disk, which is exactly the "first click after a while" case.
- The cross-document view transition is gone; the operator wants snappy.
- A color-scheme meta, kept in step with the saved theme, so the canvas the
browser paints before the stylesheet is the right shade.
- The theme toggle's icon and label are chosen by CSS from html[data-theme]
(set pre-paint by theme.js) instead of being rewritten by app.js.
- Dashboard table filters are rendered by the templates (shown under
`.has-js`) rather than inserted by app.js, so tables no longer jump.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MD4VWGq6mGcd8Bg67qyx9k
- Issue pages highlight Archive instead of Latest unless the issue is the
newest one (IssueView::is_latest), with a router test.
- The ears row is empty by default and says "Dashboard" on dashboard pages;
"Morning edition" only survives on the empty-state hero.
- Run page: the Feeds card table is fixed-layout and truncates long feed
names with the full title on hover instead of pushing the entries column
out of the card.
- Static assets are content-hashed, so serve them immutable for a year;
preload both Newsreader faces and use font-display: block so a cache
revalidation never paints the fallback serif first. Persisted details
state is restored pre-paint from theme.js.
- Cross-document view transitions (140 ms fade, masthead held still) and
Speculation Rules (prerender reader pages, prefetch dashboard pages on
hover) served from /static/speculation.json via the Speculation-Rules
header, which the CSP would otherwise block inline. No client router.
- Dashboard nav gets the same accent underline as the site nav; 40 px
targets for the theme toggle, account link and dashboard nav.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MD4VWGq6mGcd8Bg67qyx9k
Signed-in issue pages (issue, article, world, behind) get a chapter list
that marks where the reader is. One `issue_toc` helper builds it for all
four handlers: the picks in issue order numbered across sections, then
World Briefing, Behind the paper and a colophon anchor, with
position/total over the navigable chapters.
At `lg` and up it is a sticky left column with its own sticky header
(issue number, "Chapter N of M", a 2px progress bar); below `lg` the same
`<nav>` collapses behind a sticky hamburger bar and drops down as a
panel that closes on link tap, Escape and outside tap. Without JS the
panel is simply visible; `theme.js` marks the document scripted before
paint so it never flashes open. Article pages advance the progress bar
with scroll position through `requestAnimationFrame`, and the bar is a
`<progress>` element because a percentage width would need an inline
style the CSP forbids.
Also fixes the phone "ears" row from the step 2 review: the row no
longer wraps at 390px, the dateline has a short form below `sm:`, and
the toggle and account link stay on one line.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NHyYupFdBiR4VfoUM7NjSM
Replaces the hand-written stylesheet with a Tailwind CSS v4 source
(src/web/tailwind.css) whose minified build is committed as
src/web/static/app.css and still embedded with include_str!, so the binary
and the test suite never need Node. package.json pins tailwindcss and
@tailwindcss/cli to 4.3.3 and adds css, css:watch and css:check.
Colours are semantic CSS variables swapped per theme and exposed to Tailwind
through @theme inline, so templates write bg-paper/text-ink/border-rule once
and both themes work. theme.js runs synchronously in <head> to set
data-theme before paint; app.js cycles the toggle system -> light -> dark and
persists the choice. Newsreader is self-hosted and served from
web::static_asset with the same ETag/304 logic as the other assets.
Every reader template is restyled: ears row, centred masthead with its rules,
tracked section labels, the drop cap on The Brief, "Why it's here" with its
accent bar, the segmented rating control, prev/next cards, and the colophon
as a two-column definition grid. Dashboard templates are untouched (step 3).
Also fixes a .block collision with Tailwind's display utility, the SVG
`hidden` property that never toggled the theme icon, and points the dev
seed's profile_path at ./dev so previewing cannot rewrite data/profile.md.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NHyYupFdBiR4VfoUM7NjSM