Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MD4VWGq6mGcd8Bg67qyx9k
89 lines
5.2 KiB
Markdown
89 lines
5.2 KiB
Markdown
# 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>` in `issue_full.html` with the chapter it
|
||
represents (e.g. `data-toc-entry="{{ entry.href }}"`, matching the TOC link's
|
||
`href`); "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]`
|
||
in `tailwind.css` (keep `aria-current="page"` for the server-rendered chapter
|
||
on chapter pages; the scroll-spy may use `aria-current="location"`). Passed
|
||
chapters may stay ink-coloured while unread ones are `ink-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 use `scrollTo({behavior:"smooth"})` unless `prefers-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.5` today,
|
||
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`, scale `0.25→1`, blur `4px→0`, ~150 ms, `cubic-bezier(0.2,0,0,1)`), and
|
||
the `aria-label` should 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`.
|