Files
the-daily-epub/docs/plans/briefs/ui-pass/handoff-toc.md
T
thalladaandClaude Fable 5.1 ad2e99eeb9 UI pass: table-of-contents sidebar fixes and scroll sync
- The open hamburger panel now fills the viewport below the sticky bar
  (100dvh, measured bar offset) and locks the page behind it. Locking only
  the root element: locking body too made it its own scroll container and
  un-stuck the bar.
- Front-page scroll-spy: the current chapter marker, mobile label/counter,
  desktop "Chapter N of M" line and both progress bars follow the reader
  through "In This Issue"; passed chapters read in ink. The sidebar
  auto-scrolls to keep the current chapter in view.
- The sidebar scrollbar sits in a reserved gutter instead of over the text.
- Hamburger cross-fades to a close icon; 44px tap targets on phones.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MD4VWGq6mGcd8Bg67qyx9k
2026-09-04 17:05:39 +00:00

4.9 KiB

Task 1 handoff — table-of-contents sidebar

What changed

  • src/web/templates/_toc.html
    • Reworked the narrow-screen panel to use a measured viewport offset and a 100dvh-based height instead of the old 72vh cap.
    • Added live label, count, desktop-status, link-label, and per-link progress hooks used by the shared TOC state updater.
    • Moved current-link presentation onto .toc-link[aria-current] and kept the server-rendered aria-current="page" state for chapter pages.
    • Added both hamburger and close SVGs for the open-state cross-fade and raised the mobile toggle/link hit areas to at least 44 px.
    • Added explicit right padding around the scrollable list.
  • src/web/templates/issue_full.html
    • Marked The Brief, every article index item, and the colophon with matching data-toc-entry values for the front-page scroll-spy.
  • src/web/static/app.js
    • Added mobile panel sizing from the sticky bar's current viewport position, recalculated on open and layout/visual-viewport resize.
    • Added mobile document scroll lock and retained toggle, Escape, outside-click, link-click, and lg breakpoint close behavior.
    • Added a passive, requestAnimationFrame-throttled front-page scroll-spy. A single current-link update now drives aria-current="location", the mobile title/count, desktop chapter status, both progress bars, and TOC reveal.
    • Updated current-link reveal to use scrollTo, smooth unless reduced motion is requested, without scrolling the page.
    • Preserved progress-within-chapter behavior on article pages.
  • src/web/tailwind.css
    • Added the dynamic mobile panel height and scroll-lock rules.
    • Added stable scrollbar gutter, right-side spacing, thin/rule-colored cross-browser scrollbar styling, and overscroll containment.
    • Added the centralized current/hover/focus link styles and exact 150 ms opacity/scale/blur hamburger-to-close transition.
  • src/web/static/app.css
    • Rebuilt from the Tailwind source.
  • src/web/issue.rs
    • Added each TOC item's semantic progress position (including back matter) and coverage for those positions and the rendered scroll-spy hooks.
    • Made the current-link router assertion insensitive to HTML attribute order.

Design decisions

  • The open panel remains positioned directly below the shared sticky bar. JS measures only the bar's on-screen bottom edge into --toc-panel-top; CSS owns the final calc(100dvh - var(--toc-panel-top)) height. This handles both the bar still below the masthead and the bar already stuck at the top.
  • The Brief uses progress position 0. Article, World, and Behind entries use their chapter position. The colophon uses the issue total, so reaching it communicates completion even though World and Behind are separate pages.
  • Front-page position changes only toggle aria-current for current styling; the other text/progress surfaces are derived from that same link's label and progress metadata.
  • Scrollbar gutter and padding are both used: the gutter reserves space for classic scrollbars, while padding protects labels on overlay-scrollbar platforms.

Deviations and open items

  • No live screenshots could be captured. The sandbox rejected the assigned 127.0.0.1:3601 bind with Operation not permitted, exactly as anticipated by the shared brief. Consequently desktop/mobile, light/dark, open-panel, and scripted-scroll visual states remain to be checked outside the sandbox.
  • The full unit test run also found three OpenAI mock-server tests blocked by loopback binding in addition to the bind-dependent tests listed in the shared brief. They fail at the same listener creation point and are unrelated to this change.

Verification

  • node --check src/web/static/app.js — passed.
  • cargo fmt --check — passed.
  • npm run css — passed with Tailwind 4.3.3; src/web/static/app.css rebuilt.
  • npm run css:check — passed with no diff. The worktree lacked its own CLI shim, so both npm commands used the already-installed adjacent checkout's node_modules/.bin via PATH; all inputs and outputs stayed in this worktree.
  • cargo test --lib web:: — 83 passed, 0 failed.
  • cargo clippy --all-targets -- -D warnings — passed.
  • cargo test — library phase: 431 passed, 13 bind-dependent failures. The failures were 7 LLM mock-server tests, the documented relative-URL extraction test, and the documented 5 server tests; each failed with Operation not permitted while creating a loopback listener.
  • cargo test --test config_check --test e2e_pipeline --test m2_pipeline --test m3_curation --test m4_epub — 25 passed, 0 failed. m7_server was excluded because it requires the same forbidden loopback bind.
  • Attempted the configured dev-server command on port 3601 — build passed; bind failed with Operation not permitted, so screenshots were not possible.
  • git diff --check — passed.