Linked References — Shaping

Frame

Problem

  • The guide page links to essays that expand on concepts, but clicking navigates away — losing the reader's place and breaking flow
  • Essays link to other essays and /note/ pages inline, but there's no way to peek at referenced content without committing to a full navigation
  • The site has a rich internal link network (guide → essays, essays → essays, essays → notes) but no way to preview or explore connections without leaving the current page

Outcome

  • Readers can preview linked content without leaving their current page
  • Readers can open linked content alongside the current page for deeper reference
  • The interaction feels natural and lightweight — enhancing reading flow rather than interrupting it

Requirements (R)

IDRequirementStatus
R0Preview linked internal content without navigating awayCore goal
R1Open linked content in a closeable side panelMust-have
R2Works for all internal link types (guide→essay, essay→essay, essay→note)Must-have
R3Works on mobile (side panel becomes overlay or bottom sheet)Must-have
R4Regular links navigate normally (shift+click opens panel); reference-configured links open panel by default (double-click navigates)Must-have
R5Content fetched on panel open; preloading out of scope for nowNice-to-have
R6Static site compatible (no server-side runtime)Must-have
R7Links inside the panel replace the panel contentMust-have (revisit later)
R8Hovering an internal link shows a tooltip preview of the linked contentMust-have

Open requirements (not yet decided)

  • How reference links are authored (MDX component? data attribute? other syntax?)
  • Whether panel open state affects the URL

Shapes

Shape A: Vanilla JS + event delegation

PartMechanism
A1JS module on every page intercepts internal link hover/click via event delegation on document
A2Tooltip: fetch linked page HTML, extract <main>, show floating preview near cursor
A3Panel: fetch linked page HTML, extract <main>, inject into fixed side panel DOM element
A4Reference links: MDX component <Ref href="..."> renders <a data-ref href="...">
A5Mobile: panel becomes full-width overlay instead of side-by-side

Tradeoffs:

  • Simpler, no framework overhead
  • Harder to manage complex state (loading, history within panel) as feature grows
  • Works with Astro's static output with no hydration cost

Shape B: Preact Island

PartMechanism
B1<LinkManager> Preact island wraps page content, manages panel/tooltip state reactively
B2Same fetch + HTML extraction as A2/A3
B3Same <Ref> MDX component as A4
B4Mobile: same overlay behavior, managed via reactive state

Tradeoffs:

  • More complex setup, adds Preact hydration to pages
  • Easier to extend as feature grows (loading states, back/forward within panel, etc.)
  • Reactive state is a natural fit for open/closed, loading, current content

Selected: Shape A (refined)

Research confirmed Astro can generate a static JSON manifest at build time via an endpoint. This makes tooltips instant (no per-link fetch). Only the panel fetch needs to hit full HTML.

PartMechanism
A1Build-time Astro endpoint generates /api/pages.json with title, excerpt, slug for every page
A2Vanilla JS module fetches manifest once on page load, caches in memory
A3Event delegation on document intercepts hover and click on all internal links
A4Tooltip: look up page in manifest (instant), position with Floating UI, show on hover
A5Panel: fetch full page HTML on open, extract <main>, inject into fixed side panel DOM element
A6Reference links: MDX component <Ref href="..."> renders <a data-ref href="...">
A7Mobile: panel becomes full-width overlay instead of side-by-side

Fit Check (R × A)

ReqRequirementStatusA
R0Preview linked internal content without navigating awayCore goal
R1Open linked content in a closeable side panelMust-have
R2Works for all internal link types (guide→essay, essay→essay, essay→note)Must-have
R3Works on mobile (side panel becomes overlay or bottom sheet)Must-have
R4Regular links navigate normally (shift+click opens panel); reference-configured links open panel by default (double-click navigates)Must-have
R5Content fetched on panel open; preloading out of scope for nowNice-to-have
R6Static site compatible (no server-side runtime)Must-have
R7Links inside the panel replace the panel contentMust-have (revisit later)
R8Hovering an internal link shows a tooltip preview of the linked contentMust-have

Breadboard

Places

#PlaceDescription
P_BBuildAstro build step
P1Content PageAny page with internal links (guide, essay, note)
P2Side PanelReference content viewer; full overlay on mobile

UI Affordances

#PlaceComponentAffordanceControlWires OutReturns To
U1P1contentregular internal linkhover / shift+click / click→ N3, → N7
U2P1contentreference link (data-ref)hover / click / double-click→ N3, → N7
U3P1tooltiptooltip previewrender
U4P2side-panelpanel content arearender
U5P2side-panelclose buttonclick→ N11
U6P2side-panelpanel linkclick→ N13

Code Affordances

#PlaceComponentAffordanceControlWires OutReturns To
N_B1P_Bastropages.json.ts endpointbuild→ S1
N1P1link-managerinitLinkManager()page load→ N2, → N3, → N5, → N7
N2P1link-managerfetch('/api/pages.json')call→ S2
N3P1link-managerdocument mouseenter listenerobserve→ N4
N4P1link-managershowTooltip(url, el)call→ U3
N5P1link-managerdocument mouseleave listenerobserve→ N6
N6P1link-managerhideTooltip()call→ U3
N7P1link-managerdocument click listenerobserve→ N8
N8P1link-managerhandleLinkClick(event, el)call→ N9 if shift+click or data-ref
N9P1link-manageropenPanel(url)call→ N10
N10P1link-managerfetchPageContent(url)call→ N12
N11P2side-panelclosePanel()call→ S3
N12P2side-panelrenderPanel(content)call→ S3, → U4
N13P2side-panelhandlePanelLink(url)call→ N10

Data Stores

#PlaceStoreDescription
S1P_B/api/pages.jsonStatic manifest: [{url, title, excerpt}]
S2P1manifest cacheMap<url, {title, excerpt}> — in-memory, fetched once
S3P2panel state{open: boolean, url: string, content: Element}

Diagram

BUILD: Astro Build P1: Content Page link-manager content area P2: Side Panel page load fetch hover hover shift+click click shift+click or data-ref N_B1: pages.json.ts S1: /api/pages.json U3: tooltip preview N1: initLinkManager() N2: fetch manifest S2: manifest cache N3: mouseenter listener N4: showTooltip() N5: mouseleave listener N6: hideTooltip() N7: click listener N8: handleLinkClick() N9: openPanel() N10: fetchPageContent() U1: regular link U2: reference link (data-ref) U4: panel content U5: close button U6: panel link S3: panel state N11: closePanel() N12: renderPanel() N13: handlePanelLink()

Open questions

  • U3 tooltip: should hideTooltip() (N6) toggle a CSS class rather than wire to U3 directly?
  • P2 as separate Place: on desktop the panel is non-blocking; on mobile it's a full overlay. Modeled as P2 in both cases for simplicity.
  • Authoring syntax for reference links (<Ref> component vs other)
  • Whether panel open state affects the URL