Skip to content

fix(session-ui): scroll Markdown fragment links to their heading - #59550

Merged
Brendonovich merged 1 commit into
devfrom
md-preview-anchor-scroll
Oct 29, 2026
Merged

Brendonovich merged 1 commit into
devfrom
md-preview-anchor-scroll

Conversation

@opencode-agent

@opencode-agent opencode-agent Bot commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

Clicking a #heading link in a Markdown preview (file viewer, and Markdown in messages) did nothing: the link was rendered as an external target="_blank" link, headings carry no ids, and the desktop window-open handler drops renderer URLs.

  • marked-base: fragment-only links render as plain in-page links (no external-link class, target, or link icon).
  • New setupFragmentLinks on the Markdown root: on click it resolves the fragment against that document only — headings by their GitHub-style slug (repeats as -1, -2), then sanitized raw HTML id/name (user-content- prefix) — and scrolls the nearest scroll container to it. The app URL and hash never change, and a missing target is a no-op instead of a navigation.
  • Scrolls the scroller directly rather than scrollIntoView, which also scrolled a clipped ancestor in the file panel and pushed the Preview/Source toolbar up ~12px.

Testing

  • bun test for slugging/target lookup and the parser output.
  • New real-browser component test: clicking [Host services](#host-services) and a repeated-heading link #usage-1 lands each heading at the top of the scroller without changing the URL; fails without the fix.
  • Full markdown.spec.ts component suite passes (37).
  • Checked in the app's file viewer (e2e harness) with a dynamic-modules.md-style table: the link scrolls to the "Host services" heading and the toolbar stays put.

Visual

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 240 72" width="240" height="72" fill="none">
  <style>
    .doc{animation:jump 3s ease-in-out infinite}
    .hit{animation:hit 3s ease-in-out infinite}
    @keyframes jump{0%,30%{transform:translateY(0)}45%,85%{transform:translateY(-52px)}100%{transform:translateY(0)}}
    @keyframes hit{0%,20%{opacity:.25}28%,32%{opacity:1}45%,100%{opacity:.25}}
    @media (prefers-reduced-motion:reduce){.doc,.hit{animation:none}}
  </style>
  <rect x="60" y="4" width="120" height="64" rx="6" stroke="currentColor" stroke-opacity=".3"/>
  <clipPath id="c"><rect x="61" y="5" width="118" height="62" rx="5"/></clipPath>
  <g clip-path="url(#c)">
    <g class="doc" fill="currentColor">
      <rect x="70" y="14" width="44" height="4" rx="2" opacity=".35"/>
      <rect class="hit" x="118" y="14" width="40" height="4" rx="2"/>
      <rect x="70" y="24" width="96" height="3" rx="1.5" opacity=".2"/>
      <rect x="70" y="32" width="80" height="3" rx="1.5" opacity=".2"/>
      <rect x="70" y="40" width="90" height="3" rx="1.5" opacity=".2"/>
      <rect x="70" y="48" width="70" height="3" rx="1.5" opacity=".2"/>
      <rect x="70" y="64" width="56" height="6" rx="2" opacity=".8"/>
      <rect x="70" y="76" width="96" height="3" rx="1.5" opacity=".2"/>
      <rect x="70" y="84" width="84" height="3" rx="1.5" opacity=".2"/>
      <rect x="70" y="92" width="92" height="3" rx="1.5" opacity=".2"/>
      <rect x="70" y="100" width="64" height="3" rx="1.5" opacity=".2"/>
      <rect x="70" y="108" width="88" height="3" rx="1.5" opacity=".2"/>
    </g>
  </g>
</svg>

Requested by: @Brendonovich (Brendan via Slack)


Added by the opencode bot (AI-generated).

Fixes #57971

@opencode-agent opencode-agent Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The fix works. Your new browser test fails on the base (the fragment link still opens with target="_blank") and passes with this change, along with the rest of markdown.spec.ts (37). The fragment-link and parser unit tests pass, and tsc is clean for session-ui and ui. The user-content- fallback matches the sanitizer's existing SANITIZE_NAMED_PROPS setting.

Public API: unchanged. The new functions are internal to session-ui, and the marked-base link output changes only for #… links.

No blocking concerns. The new tests are small, and the browser test waits with expect.poll, not sleeps.

Fixes #57971

@Brendonovich
Brendonovich merged commit 7ab721a into dev Oct 29, 2026
23 checks passed
@Brendonovich
Brendonovich deleted the md-preview-anchor-scroll branch October 29, 2026 08:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Markdown renderer externalizes in-document anchors (target="_blank") and emits headings without ids, so TOC links never scroll

1 participant