Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MD4VWGq6mGcd8Bg67qyx9k
5.2 KiB
Task 1 — table-of-contents sidebar: mobile height, scroll sync, scrollbar
Worktree: /home/thallada/workspace/the-daily-epub-ui1 (branch ui-toc). Dev port 3601.
Files you own: src/web/templates/_toc.html, the TOC parts of
src/web/static/app.js (the [data-toc-*] blocks), the "table-of-contents"
section of src/web/tailwind.css, and the index entries in
src/web/templates/issue_full.html (only to add data attributes / ids). Backend:
src/web/issue.rs Toc/TocItem if a field helps. Read the shared brief first.
The signed-in issue pages (issue_full.html, article.html, world.html,
behind.html) share _toc.html: a sticky bar with a hamburger below lg, a
sticky sidebar at lg+. Chapters are separate pages; the issue front page lists
every chapter in "In This Issue".
1. The open hamburger panel must fill the screen (operator issue 1)
Today the panel is absolute top-full max-h-[72vh], so on a phone it stops
partway down with page content visible beneath it — it looks broken. Make the
open panel extend from the bottom of the sticky bar to the bottom of the
viewport (use 100dvh, not 100vh, so mobile browser chrome is accounted
for), with the chapter list scrolling inside it and the bar staying fixed above
it. Handle both states: the bar already stuck at the top of the viewport, and
the bar still in flow below the masthead on first open. Prefer a pure-CSS
solution; if the bar's on-screen position must be measured, set one CSS custom
property from app.js on open/resize and consume it in tailwind.css. Avoid
jumps of the underlying page when the panel opens or closes. Keep Escape, the
outside-click close, the tap-on-chapter close, and the lg breakpoint reset.
Content behind the panel must not scroll while it is open (e.g. toggle a class
on <html> that sets overflow:hidden, cleared on close).
2. Scroll-synced current chapter (operator issue 2)
On the issue front page the reader scrolls through "In This Issue"; each index entry corresponds to a TOC chapter. As the reader scrolls, the TOC's current marker must follow: when entry 5's card reaches the top region of the viewport, chapters 1–4 have visibly "passed" and 5 is current. Implement a scroll-spy in app.js (IntersectionObserver or a rAF-throttled scroll listener; either is fine, but it must be cheap and passive):
- Mark each index entry
<li>inissue_full.htmlwith the chapter it represents (e.g.data-toc-entry="{{ entry.href }}", matching the TOC link'shref); "The Brief" section maps to the Brief item, the colophon to the Colophon item. - The current entry is the last one whose top edge is above a threshold (roughly the sticky bar height + ~1/3 of the viewport). Before the first entry the Brief is current.
- Move the current styling to a single hook so JS only toggles one attribute:
give TOC links a class (say
toc-link) and style.toc-link[aria-current]intailwind.css(keeparia-current="page"for the server-rendered chapter on chapter pages; the scroll-spy may usearia-current="location"). Passed chapters may stay ink-coloured while unread ones areink-2; keep it subtle. - Update the sticky mobile bar's label and "N / total" counter and both
[data-toc-progress]bars from the same source of truth (the front page currently leaves the progress bar at 0; it should reflect the current chapter as the reader passes entries). The desktop "Chapter N of M / Front page" line should update too. Chapter pages keep today's progress-within-chapter logic. - When the current item changes, auto-scroll the TOC panel so the current
link is comfortably in view (there is already a
revealCurrent(); reuse it, make it usescrollTo({behavior:"smooth"})unlessprefers-reduced-motion, and make sure it works for the desktop sticky sidebar as well as the open mobile panel). Never scroll the window itself from this code. - Chapter pages already mark the chapter current; make sure the sidebar reveals it on load (desktop and mobile) — verify on a long TOC.
3. Scrollbar must not overlap the TOC text (operator issue 11)
In the sidebar the scrollbar is painted over the right edge of the chapter
labels. Reserve the gutter (scrollbar-gutter: stable, plus right padding on
the panel so the list ends before the gutter) so the scrollbar sits to the
right of the content on every platform, including overlay-scrollbar macOS.
Keep scrollbar-width: thin and the rule-coloured thumb.
Polish while you are there
- Chapter links: ≥ 44 px tall tap targets on phones (they are
py-2.5today, check), hover/focus-visible states consistent with the nav. - The hamburger icon should animate to a close icon while open using the
cross-fade recipe (both SVGs in the DOM, one absolutely positioned; opacity
0→1, scale0.25→1, blur4px→0, ~150 ms,cubic-bezier(0.2,0,0,1)), and thearia-labelshould flip to "Close contents".
Verification
Screenshot the front page and one chapter, desktop + phone, light + dark, with
the panel open on the phone; script a scroll on the front page (Playwright
page.mouse.wheel or window.scrollTo) and screenshot the sidebar at two
scroll positions to prove the marker moves and the panel auto-scrolls. Run the
web router tests. Write handoff-toc.md.