Skip to content

Section deeplinks + comment permalinks in the doc viewer#9

Merged
AnnaXWang merged 1 commit into
mainfrom
hypeship/section-comment-deeplinks
Jul 9, 2026
Merged

Section deeplinks + comment permalinks in the doc viewer#9
AnnaXWang merged 1 commit into
mainfrom
hypeship/section-comment-deeplinks

Conversation

@AnnaXWang

@AnnaXWang AnnaXWang commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Adds two kinds of shareable deeplinks to the /d/:slug viewer, in the style of Slack Canvas / GitHub heading anchors.

What's new

Section deeplinks — hover a heading → a link icon appears in its left gutter → click copies https://justhtml.sh/d/<slug>#<section-id>. Opening that URL scrolls the doc to that heading (with a brief highlight flash).

Comment permalinks — hover/open a comment card → a link icon (top-right) copies https://justhtml.sh/d/<slug>#comment-<id>. Opening it opens + pins the thread and scrolls to it.

What is a "section"

A section is the region introduced by a heading (<h1><h6>); every heading gets one unique fragment id. Ids are computed by the new lib/docs/sections.ts (extractSections):

  • author-provided id on the heading wins (respects existing anchors);
  • otherwise a GFM-style slug of the heading text (lowercase, punctuation/emoji dropped, spaces → hyphens, Unicode-aware);
  • empty slug (emoji/punctuation-only heading) → section-<n>;
  • collisions deduped in document order (-1, -2, …);
  • never mints the reserved comment-<n> shape, so section and comment fragments can't collide.

Ids are stateless — recomputed from the current HTML each render, no storage, no schema/migration. Same edit behavior as GitHub anchors: body-text edits keep links stable; renaming/adding/reordering headings can change a section id. Comment permalinks are durable (the comment id is persistent and its anchor already re-anchors through edits).

How it fits the architecture

The doc renders as arbitrary user HTML in a sandboxed, opaque-origin iframe, so native #fragment scrolling can't cross the boundary. Following the existing server-owns-identity / overlay-consumes split (see lib/docs/anchor.ts):

  • page.tsx computes the ordered section list and passes initialSections to the shell.
  • CommentsShell forwards it as jh:sections, runs a hash router (#comment-<id> → focus thread; else → section scroll), handles jh:copyLink, and shows a "copied" toast.
  • overlay.ts assigns each id to the heading at the same document-order index, sets scroll-margin-top, injects the gutter link icon, and handles jh:scrollToSection. Comment scroll reuses the existing jh:focus path.

/d/:slug/raw stays byte-pristine (all injection is client-side in the overlay variant). The sandbox is unchanged — the section-icon click is delegated to the shell for the clipboard write (Clipboard API with an execCommand fallback), since the opaque-origin iframe can't reliably use the clipboard.

Files

  • lib/docs/sections.ts (+ sections.test.ts) — section extraction/id algorithm, 10 unit tests.
  • app/d/[slug]/page.tsx — compute + pass initialSections.
  • app/d/[slug]/CommentsShell.tsxjh:sections, hash router, comment focus, copy helper + toast, card link icon.
  • lib/docs/overlay.tsjh:sections / jh:scrollToSection, heading ids + gutter icon.

Testing

  • npm test — 118 passing (10 new in sections.test.ts, all pre-existing green).
  • tsc --noEmit clean; npm run build succeeds; npm run spec:check clean (no API/spec change).
  • Overlay JS parse-checked (it lives in a template literal, so tsc/build don't validate its syntax).

Manual verification still needed (overlay/clipboard are DOM/browser behavior the repo doesn't unit-test): publish a doc with duplicate/nested/emoji headings + a few comments (anchored, resolved, orphaned, a reply); hover both icons → copy → open the URL in a fresh tab → correct scroll/pin; check light/dark/mobile; and confirm the section-icon clipboard fallback across Chrome/Safari/Firefox (the delegated copy is the one at-risk path).

Notes

  • Independent of the drag-select bugfix PR; both touch CommentsShell.tsx, so a trivial rebase may be needed if that merges first.

Note

Medium Risk
Large client-side changes to CommentsShell, overlay postMessage, and rail scroll behavior; clipboard from iframe is delegated with fallback—needs manual browser checks.

Overview
Adds shareable permalinks for the /d/:slug viewer: #comment-<id> opens and pins a thread; #<section-id> scrolls to a heading. Copy affordances use link icons on comment cards (shell) and heading gutters (overlay → jh:copyLink).

Server: extractSections derives GFM-style heading ids from stored HTML (author id wins, dedupe, guard against comment-<n> collisions). page.tsx passes initialSections into the shell.

Shell: Shared fragmentFor / parseHash codec; hash router on load, hashchange, and iframe reload (overlayReadyNonce); copyLink preserves ?viewtoken=; toasts; rail “show card” mode for doc-level/orphan/mobile comment links; comment/section focus are mutually exclusive.

Overlay: jh:sections assigns ids and gutter icons; jh:scrollToSection with pending scroll; deferred focus scroll after anchor paint; section anchors excluded from anchorable text.

Reviewed by Cursor Bugbot for commit 3eb1c9a. Bugbot is set up for automated code reviews on this repo. Configure here.

@vercel

vercel Bot commented Jul 8, 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 9, 2026 5:23pm

Comment thread app/d/[slug]/CommentsShell.tsx Outdated
@AnnaXWang AnnaXWang force-pushed the hypeship/section-comment-deeplinks branch from d07e8a2 to 4c3f2d9 Compare July 8, 2026 23:45
Comment thread lib/docs/sections.ts Outdated
Comment thread lib/docs/sections.ts Outdated
Comment thread app/d/[slug]/CommentsShell.tsx
Comment thread app/d/[slug]/CommentsShell.tsx
Comment thread app/d/[slug]/CommentsShell.tsx
Comment thread app/d/[slug]/CommentsShell.tsx Outdated
Comment thread app/d/[slug]/CommentsShell.tsx Outdated
Comment thread app/d/[slug]/CommentsShell.tsx
Comment thread app/d/[slug]/CommentsShell.tsx
Comment thread app/d/[slug]/CommentsShell.tsx Outdated
Comment thread lib/docs/overlay.ts Outdated
Comment thread app/d/[slug]/CommentsShell.tsx Outdated
Comment thread app/d/[slug]/CommentsShell.tsx Outdated

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 74a7f42. Configure here.

Comment thread lib/docs/overlay.ts
Sections: a hover-revealed link icon in each heading's left gutter copies a
/d/<slug>#<section-id> URL; opening it scrolls the doc to that heading.
Section ids are stable GFM-style slugs derived from the HTML by a new
lib/docs/sections extractor (author id wins, deduped, section-N fallback,
reserved comment-<n> namespace), forwarded to the overlay which assigns them
to headings in document order and paints the icon.

Comments: a link icon on each comment card copies /d/<slug>#comment-<id>;
opening it opens + pins the thread and scrolls to it — via the doc highlight
for anchored threads, or the rail card for doc-level/orphaned ones.

Both share one hash router, a copy helper (Clipboard API with an execCommand
fallback for the section icon, whose click is delegated out of the sandboxed
iframe), and a "copied" toast. The copied URL carries the current ?viewtoken=
(before the fragment) so deeplinks resolve for recipients of a private doc
opened via a capability link. Stateless: ids are recomputed each render, no
storage or schema change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@AnnaXWang AnnaXWang force-pushed the hypeship/section-comment-deeplinks branch from 74a7f42 to 3eb1c9a Compare July 9, 2026 17:22
@AnnaXWang AnnaXWang merged commit 351b92c into main Jul 9, 2026
5 checks passed
@AnnaXWang AnnaXWang deleted the hypeship/section-comment-deeplinks branch July 9, 2026 17:28
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.

2 participants