Skip to content

feat(conversation): add a link preview slot below messages - #3219

Open
joahg wants to merge 1 commit into
block:mainfrom
joahg:joah/link-preview-slot
Open

joahg wants to merge 1 commit into
block:mainfrom
joahg:joah/link-preview-slot

Conversation

@joahg

@joahg joahg commented Nov 10, 2026

Copy link
Copy Markdown
Member

Linear: AGNTOPS-773

Why

Core Buzz should unfurl links, and plugins should be able to override the default per site (Joah, plugin bounty thread). Plugins have no slot below a message today. The marketplace link-previews plugin renders its cards inside the link anchor, then moves the DOM nodes under the bubble and watches them with MutationObservers.

What

  • ctx.conversation.registerLinkPreview({ id, title, matches(url), order?, component }). Components receive { url, messageId, open }. open() routes through the host's link handler, as clicking the link does, and returns false once the contribution or message retires.
  • Override model: for each link the lowest order among matches draws its card, the same resolution registerLink and panels use. A default web card sits at the catch-all band (100), so a plugin claims its own site at 0. A component that renders nothing leaves that link without a card.
  • Which links get cards (previewLinks): credential-free HTTPS links in the message's Markdown prose, once each in the order written. Bare and labeled links count. <url> doesn't, since it asks for no preview, matching Let authors dismiss link previews with a one-link edit #2677's convention. Neither do links in code, images, or links naming one of the message's attachments. Diffs, agent envelopes and messages drawn by a message renderer get none.
  • MessageRow mounts the stack between the bubble and its attachments. After three cards, the rest fold behind "Show N more previews". Each card has its own ContributionBoundary, so a failing renderer drops only its card.
  • Docs: plugin-architecture.md and plugin-authoring-guide.md.

No core card ships in this PR; the bundled buzz.link-previews plugin follows it in the stack.

Review notes

  • contracts.ts and service.tsx are FOUNDATION. This adds one contribution kind beside registerLink, with the same validation and lifetime. Joah asked for this extension point explicitly.
  • editedInBody in MessageRow is now derived from markdownBody, which the previews also use. Behavior is unchanged.

Testing

  • vitest: preview-links.test.ts (new), LinkPreviews.test.tsx (new: order override, fold and unfold, host open, a throwing card isolated), service.test.tsx (new: registration validation and withdrawal), and the existing MessageRow tests: 223 passed.
  • tsc --noEmit is clean.
  • A browser spec that drives real message rows through this slot is in the bundled-plugin PR, since it needs a card.

Part of a stack:

  1. feat(plugins): let link previews read a public page's preview fields #3217: page reads.
  2. This PR: the slot.
  3. Bundled buzz.link-previews, on by default.

🤖 Generated with Claude Code

registerLinkPreview contributes a card below a message for one of its
HTTPS links. The lowest order among matches draws it, so a plugin can claim
its own URLs ahead of a catch-all web card. Bare and labeled links in
prose get cards; <url>, code, images and attachment URLs do not. Past
three cards the rest fold behind one button.

Linear: AGNTOPS-773

Co-authored-by: Hertz <c6741012416ef5c49a865db79682113385c5fb998e8c890234fb442b59e22748@buzz.block.builderlab.xyz>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Joah Gerstenberg <joah@squareup.com>
@joahg
joahg requested a review from a team as a code owner November 10, 2026 18:12

This branch has not been deployed

No deployments
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