Skip to content

Single page scrollbar with google-docs comment placement#7

Closed
AnnaXWang wants to merge 2 commits into
mainfrom
hypeship/single-page-scroll
Closed

Single page scrollbar with google-docs comment placement#7
AnnaXWang wants to merge 2 commits into
mainfrom
hypeship/single-page-scroll

Conversation

@AnnaXWang

@AnnaXWang AnnaXWang commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Summary

The comments viewer had two scrollbars: the document scrolled inside its sandboxed iframe, and the comment rail scrolled separately (kept in lockstep by a JS scroll-sync). This makes the scroll behavior work like Google Docs:

  • One scrollbar for the whole page. The overlay reports the doc's content height (jh:positions.docHeight) and the shell sizes the iframe to it, so the iframe never scrolls internally — the page scrolls the document.
  • No comment rail. The right column is now a transparent margin (no border/background/own scrollbar); cards are pinned at their highlight's document Y with the existing no-overlap clamping, and move with the page as it scrolls.
  • The chrome bar is sticky so it stays put while the page scrolls.
  • Focusing a card scrolls the page to the highlight when it's offscreen (the sandboxed iframe can no longer scroll itself, and its opaque origin blocks scrollIntoView propagation) — replaces the overlay-side scrollTo, which is removed.
  • The overlay re-reports docHeight via a ResizeObserver on body so late-loading images/fonts keep the iframe sized correctly.
  • Mobile keeps the right-drawer pattern, now viewport-fixed below the sticky bar with its own card scroll (the page behind it owns document scrolling).
  • Handshake fix (found while verifying): the overlay fires jh:ready when its script runs, which can beat the shell's message listener (the SSR'd iframe starts fetching before React hydrates); a missed ready meant anchors never painted. The shell now sends jh:ping every 250ms until the overlay answers — the ping reply already existed in the overlay protocol but was never used.
  • Dropped now-dead code: the rail scroll-sync effect, docScrollY/scrollY plumbing, viewTop, jh:scrollTo.

Test plan

npm run build and npm test (108 tests) pass. Verified end-to-end in headless Chromium against a local Postgres + next dev, with a seeded long doc (~5400px) carrying comments anchored at the top, middle, and bottom — 18/18 checks:

  • Page owns the only scrollbar (iframe content height == iframe height, rail overflow: visible with zero scroll range, no horizontal overflow)
  • Cards pixel-aligned with their highlights (doc-space Y match at top/middle/bottom), and stay beside them mid-scroll
  • Clicking a card whose highlight is offscreen smooth-scrolls the page to it (desktop + mobile drawer)
  • Select text → toolbar appears at the selection; 💬 opens the draft composer beside it
  • Mobile: drawer is viewport-fixed below the sticky bar, scrolls its own cards, scrim behind
  • Dark-theme doc: themed chrome, amber highlight, dark card in transparent margin — screenshots checked by eye for all of the above

🤖 Generated with Claude Code

Size the sandboxed iframe to the document's full content height (from the
overlay's docHeight report) so it never scrolls internally — the page owns
the one scrollbar. The comment rail stops being a separately-scrolling
column: it becomes a transparent margin column whose cards sit at their
highlight's document Y and scroll with the page. Drop the JS scroll-sync,
make the chrome bar sticky, fix the mobile drawer to the viewport, and move
scroll-to-highlight into the shell (the iframe can no longer scroll itself).
Keep docHeight fresh via a ResizeObserver in the overlay for late images.
@vercel

vercel Bot commented Jul 2, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
justhtml Ready Ready Preview, Comment Jul 2, 2026 3:14am

The overlay fires jh:ready when its script runs, which can beat the
shell's message listener — the SSR'd iframe starts fetching before React
hydrates — and a missed ready meant anchors were never sent, so no
highlights painted. The overlay already answers jh:ping with jh:ready;
have the shell ping every 250ms until the handshake lands.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant