Files
buzz/desktop/src/shared/lib/linkPreview.test.mjs
Taylor Ho 1922d49cb2 feat(desktop): adding rich link previews to messages (#3818)
## Overview

**Category:** improvement  
**User impact:** Link previews appear in the composer and travel as
privacy-safe sender-authored snapshots, so recipients never contact the
linked site merely by opening a conversation.
**Problem:** Cold-cache link paste could freeze the composer before the
URL painted; recipient-side unfurling leaked visits; invalid or
unresolved preview work could interfere with sending or leave dead cards
behind.
**Solution:** Paint pasted links before starting cold resolver work,
resolve only in the sender's composer, attach only complete validated
snapshots at Send, and render authored snapshots without recipient
fallback fetching.

## Behavior

- **Cold paste stays responsive:** bare and angle-bracket URL paste
paths commit the visible link before resolver work begins.
- **Sender-only fetching:** metadata is resolved while composing;
recipients render only the sender-authored snapshot.
- **Send never waits:** pending, failed, invalid, and unsendable
previews are omitted. They do not block or cancel the message.
- **Terminal misses disappear:** failed, timed-out, or 404 resolver
results remove the composer card while preserving visible link text.
- **Display-text links work:** Markdown links such as `[review the pull
request](…)` produce and send the same snapshots as bare URLs.
- **Compact and Rich presentation:** Compact remains the default; Rich
preserves source description line breaks and paragraphs.
- **Immediate draft-wide dismissal:** clicking × immediately hides all
previews for the draft, suppresses links pasted later, and emits only
`["link-preview", "none"]`. No confirmation detour. Suppression resets
after send or clearing the draft.
- **Zero recipient fallback:** missing, stale, malformed, off-relay,
unsupported, or suppressed snapshots remain ordinary visible links;
recipients never regenerate them.

## Implementation

- Resolve previews from deferred composer URL state so paste can paint
first.
- Upload finished preview media to the active community relay and
snapshot only valid, sendable media references.
- Atomically capture ready snapshots at submit time; never append a late
preview after send.
- Validate snapshot and suppression tags in desktop/native and relay
ingestion, rejecting duplicate or mixed forms.
- Render composer previews as stable 55px attachment cards at desktop
and narrow widths.
- Add deterministic E2E coverage for cold paste,
ready/pending/failed/invalid previews, display-text links, multiline
Rich descriptions, immediate dismissal, later-pasted links, and
suppression reset.

## Validation

Validated head: `9807ba8952f190e76153834abf8ab61dd40be5e2`

- Push hooks passed: `check-push-org`, branch skew, desktop check,
mobile tests, desktop tests, Rust tests, and desktop Tauri checks.
- Focused screenshot E2E at the validated head: 5/5 passed across
Compact/Rich composer and recipient states, 800px/420px geometry,
display-text links, multiline descriptions, and immediate dismissal.
- PR CI was triggered for this exact head and is currently running;
completed checks are green at the time of this update.
- Worktree is clean and both PR head and validated branch resolve to
`9807ba895…`.

## Screenshots

### Compact composer

| Loading | Ready |
|---|---|
| ![Compact composer
loading](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/3818/compact-composer-loading.png)
| ![Compact composer
ready](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/3818/compact-composer-ready.png)
|

### Rich composer

| Loading | Ready |
|---|---|
| ![Rich composer
loading](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/3818/rich-composer-loading.png)
| ![Rich composer
ready](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/3818/rich-composer-ready.png)
|

### Responsive composer

| 800px loading | 800px ready |
|---|---|
| ![800px composer
loading](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/3818/composer-800-loading.png)
| ![800px composer
ready](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/3818/composer-800-ready.png)
|

| 420px loading | 420px ready |
|---|---|
| ![420px composer
loading](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/3818/composer-420-loading.png)
| ![420px composer
ready](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/3818/composer-420-ready.png)
|

### Recipient presentation

| Compact | Rich |
|---|---|
| ![Recipient
compact](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/3818/recipient-compact.png)
| ![Recipient
rich](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/3818/recipient-rich.png)
|

### Display-text Markdown link

| Composer | Recipient |
|---|---|
| ![Display-text link in
composer](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/3818/display-text-composer.png)
| ![Display-text link with recipient
preview](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/3818/display-text-recipient.png)
|

### Rich multiline description

![Rich preview preserving description
paragraphs](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/3818/rich-multiline-recipient.png)

### Immediate dismissal

| Before × | Immediately after × |
|---|---|
| ![Preview before immediate
dismissal](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/3818/dismissal-before.png)
| ![Composer immediately after preview
dismissal](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/3818/dismissal-after.png)
|

---------

Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
2026-08-07 10:56:08 -07:00

506 lines
15 KiB
JavaScript

import assert from "node:assert/strict";
import test from "node:test";
import {
extractSupportedLinkPreviews,
isSupportedLinkAutolinkLabel,
parseSupportedLinkPreview,
} from "./linkPreview.ts";
test("parseSupportedLinkPreview parses GitHub pull request URLs", () => {
assert.deepEqual(
parseSupportedLinkPreview("https://github.com/block/sprout/pull/1234"),
{
kind: "github-pull-request",
href: "https://github.com/block/sprout/pull/1234",
provider: "GitHub",
title: "block/sprout #1234",
typeLabel: "PR",
},
);
});
test("parseSupportedLinkPreview parses GitHub repository URLs", () => {
assert.deepEqual(
parseSupportedLinkPreview("https://github.com/block/sprout"),
{
kind: "github-repository",
href: "https://github.com/block/sprout",
provider: "GitHub",
title: "block/sprout",
typeLabel: "repo",
},
);
});
test("parseSupportedLinkPreview trims markdown punctuation around GitHub URLs", () => {
assert.deepEqual(
parseSupportedLinkPreview("https://github.com/block/sprout/pull/1234)."),
{
kind: "github-pull-request",
href: "https://github.com/block/sprout/pull/1234",
provider: "GitHub",
title: "block/sprout #1234",
typeLabel: "PR",
},
);
});
test("parseSupportedLinkPreview ignores unsupported GitHub URLs", () => {
assert.equal(
parseSupportedLinkPreview("https://github.com/block/sprout/tree/main"),
null,
);
});
const BUZZ_OWNER =
"71d67180ba17e749ee825fc8819c9c6ee7003617e1c126504f9b658070ab9224";
test("parseSupportedLinkPreview parses Buzz relay git clone URLs", () => {
// Must pass the active relay origin for host validation.
assert.deepEqual(
parseSupportedLinkPreview(
`https://buzz.block.builderlab.xyz/git/${BUZZ_OWNER}/buzz-world-galaxy`,
"https://buzz.block.builderlab.xyz",
),
{
kind: "buzz-repository",
href: `buzz://repo?owner=${BUZZ_OWNER}&d=buzz-world-galaxy`,
provider: "Buzz",
title: "buzz-world-galaxy",
typeLabel: "repo",
},
);
// Same URL without a matching origin stays an ordinary external preview.
assert.equal(
parseSupportedLinkPreview(
`https://buzz.block.builderlab.xyz/git/${BUZZ_OWNER}/buzz-world-galaxy`,
)?.kind,
"generic-link",
);
});
test("parseSupportedLinkPreview strips .git suffix from clone URLs", () => {
assert.deepEqual(
parseSupportedLinkPreview(
`http://localhost:3000/git/${BUZZ_OWNER}/buzz-world.git`,
"http://localhost:3000",
),
{
kind: "buzz-repository",
href: `buzz://repo?owner=${BUZZ_OWNER}&d=buzz-world`,
provider: "Buzz",
title: "buzz-world",
typeLabel: "repo",
},
);
});
test("parseSupportedLinkPreview rejects malformed Buzz git URLs", () => {
for (const href of [
// Owner segment must be a 64-char lowercase hex pubkey.
"https://relay.example/git/not-a-pubkey/repo",
`https://relay.example/git/${BUZZ_OWNER.toUpperCase()}/repo`,
`https://relay.example/git/${BUZZ_OWNER.slice(0, 32)}/repo`,
// Missing or invalid repo segment.
`https://relay.example/git/${BUZZ_OWNER}`,
`https://relay.example/git/${BUZZ_OWNER}/.hidden`,
// Deeper transport paths are not repo links.
`https://relay.example/git/${BUZZ_OWNER}/repo/info/refs`,
]) {
// Structural non-matches remain ordinary external previews.
assert.equal(
parseSupportedLinkPreview(href, "https://relay.example")?.kind,
"generic-link",
href,
);
}
});
test("parseSupportedLinkPreview rejects clone URLs from non-relay hosts", () => {
// Correct path shape but origin does not match the active relay.
assert.equal(
parseSupportedLinkPreview(
`https://evil.example/git/${BUZZ_OWNER}/my-repo`,
"https://buzz.block.builderlab.xyz",
)?.kind,
"generic-link",
);
// github.com sharing the path shape must never become a Buzz repo card.
assert.equal(
parseSupportedLinkPreview(
`https://github.com/git/${BUZZ_OWNER}/my-repo`,
"https://buzz.block.builderlab.xyz",
),
null,
);
// No relay origin provided — stays external.
assert.equal(
parseSupportedLinkPreview(
`https://buzz.block.builderlab.xyz/git/${BUZZ_OWNER}/buzz-world`,
null,
)?.kind,
"generic-link",
);
});
const BUZZ_EVENT_ID =
"c3b589fa5713ba25bad6dc095e2de00a4ac8f50050fdea00fc6444e603be1dd1";
test("parseSupportedLinkPreview parses buzz:// PR and issue deep links", () => {
assert.deepEqual(
parseSupportedLinkPreview(
`buzz://pr?id=${BUZZ_EVENT_ID}&owner=${BUZZ_OWNER}&d=buzz-world`,
),
{
kind: "buzz-pull-request",
href: `buzz://pr?id=${BUZZ_EVENT_ID}&owner=${BUZZ_OWNER}&d=buzz-world`,
provider: "Buzz",
title: "buzz-world #c3b589fa",
typeLabel: "PR",
},
);
assert.deepEqual(
parseSupportedLinkPreview(
`buzz://issue?id=${BUZZ_EVENT_ID}&owner=${BUZZ_OWNER}&d=buzz-world`,
)?.typeLabel,
"issue",
);
assert.deepEqual(
parseSupportedLinkPreview(`buzz://repo?owner=${BUZZ_OWNER}&d=buzz-world`),
{
kind: "buzz-repository",
href: `buzz://repo?owner=${BUZZ_OWNER}&d=buzz-world`,
provider: "Buzz",
title: "buzz-world",
typeLabel: "repo",
},
);
});
test("parseSupportedLinkPreview rejects malformed buzz:// entity links", () => {
for (const href of [
`buzz://pr?owner=${BUZZ_OWNER}&d=buzz-world`,
`buzz://pr?id=short&owner=${BUZZ_OWNER}&d=buzz-world`,
`buzz://issue?id=${BUZZ_EVENT_ID}&owner=nope&d=buzz-world`,
`buzz://repo?owner=${BUZZ_OWNER}&d=.hidden`,
]) {
assert.equal(parseSupportedLinkPreview(href), null, href);
}
});
test("extractSupportedLinkPreviews picks up buzz:// links in prose", () => {
assert.deepEqual(
extractSupportedLinkPreviews(
`PR is up: buzz://pr?id=${BUZZ_EVENT_ID}&owner=${BUZZ_OWNER}&d=buzz-world — review please.`,
).map((preview) => [preview.kind, preview.title]),
[["buzz-pull-request", "buzz-world #c3b589fa"]],
);
});
test("extractSupportedLinkPreviews uses markdown labels for buzz:// links", () => {
assert.deepEqual(
extractSupportedLinkPreviews(
`[Add header links](buzz://pr?id=${BUZZ_EVENT_ID}&owner=${BUZZ_OWNER}&d=buzz-world)`,
).map((preview) => preview.title),
["Add header links"],
);
});
test("parseSupportedLinkPreview parses Linear issue URLs", () => {
assert.deepEqual(
parseSupportedLinkPreview(
"https://linear.app/buzz/issue/BUG-321/fix-link-previews",
),
{
kind: "linear-issue",
href: "https://linear.app/buzz/issue/BUG-321/fix-link-previews",
provider: "Linear",
title: "BUG-321",
typeLabel: "issue",
},
);
});
test("parseSupportedLinkPreview normalizes Linear issue URL variants", () => {
assert.deepEqual(
parseSupportedLinkPreview("linear.app/buzz/issue/a-7/fix-link-previews"),
{
kind: "linear-issue",
href: "https://linear.app/buzz/issue/a-7/fix-link-previews",
provider: "Linear",
title: "A-7",
typeLabel: "issue",
},
);
});
test("parseSupportedLinkPreview parses Google app URLs", () => {
assert.deepEqual(
[
"https://drive.google.com/file/d/abc123/view",
"https://drive.google.com/drive/folders/folder123",
"https://docs.google.com/document/d/doc123/edit",
"https://docs.google.com/spreadsheets/d/sheet123/edit",
"https://docs.google.com/presentation/d/slides123/edit",
].map((href) => parseSupportedLinkPreview(href)?.kind),
[
"google-drive-file",
"google-drive-folder",
"google-docs-document",
"google-sheets-spreadsheet",
"google-slides-presentation",
],
);
});
test("extractSupportedLinkPreviews returns unique supported links in order", () => {
assert.deepEqual(
extractSupportedLinkPreviews(
[
"See github.com/block/sprout/pull/1",
"and https://linear.app/buzz/issue/BUG-2/fix-preview",
"then https://github.com/block/sprout/pull/1 again.",
"plus https://docs.google.com/document/d/doc123/edit",
].join(" "),
).map((preview) => preview.title),
["block/sprout #1", "BUG-2", "Document"],
);
});
test("extractSupportedLinkPreviews picks up bare Buzz clone URLs in prose", () => {
assert.deepEqual(
extractSupportedLinkPreviews(
`master pushed; clone: https://buzz.block.builderlab.xyz/git/${BUZZ_OWNER}/buzz-world-galaxy and review please.`,
"https://buzz.block.builderlab.xyz",
),
[
{
kind: "buzz-repository",
href: `buzz://repo?owner=${BUZZ_OWNER}&d=buzz-world-galaxy`,
provider: "Buzz",
title: "buzz-world-galaxy",
typeLabel: "repo",
},
],
);
// Without a relay origin the URL is treated as an ordinary external link.
assert.deepEqual(
extractSupportedLinkPreviews(
`clone: https://buzz.block.builderlab.xyz/git/${BUZZ_OWNER}/buzz-world-galaxy`,
).map((preview) => preview.kind),
["generic-link"],
);
});
test("extractSupportedLinkPreviews uses markdown labels for Buzz repo links", () => {
assert.deepEqual(
extractSupportedLinkPreviews(
`[Buzz World](https://relay.example/git/${BUZZ_OWNER}/buzz-world-galaxy)`,
"https://relay.example",
).map((preview) => preview.title),
["Buzz World"],
);
});
test("extractSupportedLinkPreviews dedupes clone URL variants of one repo", () => {
assert.deepEqual(
extractSupportedLinkPreviews(
[
`https://relay.example/git/${BUZZ_OWNER}/buzz-world-galaxy`,
`https://relay.example/git/${BUZZ_OWNER}/buzz-world-galaxy.git`,
].join(" "),
"https://relay.example",
).map((preview) => preview.href),
[`buzz://repo?owner=${BUZZ_OWNER}&d=buzz-world-galaxy`],
);
});
test("clone URLs and buzz://repo links for the same repo dedupe to one card", () => {
assert.deepEqual(
extractSupportedLinkPreviews(
[
`https://relay.example/git/${BUZZ_OWNER}/buzz-world-galaxy`,
`buzz://repo?owner=${BUZZ_OWNER}&d=buzz-world-galaxy`,
].join(" "),
"https://relay.example",
).map((preview) => preview.href),
[`buzz://repo?owner=${BUZZ_OWNER}&d=buzz-world-galaxy`],
);
});
test("extractSupportedLinkPreviews handles markdown link serialization", () => {
assert.deepEqual(
extractSupportedLinkPreviews(
"[https://github.com/block/sprout/pull/44](https://github.com/block/sprout/pull/44)",
).map((preview) => preview.title),
["block/sprout #44"],
);
});
test("extractSupportedLinkPreviews uses useful markdown labels as titles", () => {
assert.deepEqual(
extractSupportedLinkPreviews(
"[Composer attachment polish](https://docs.google.com/document/d/doc123/edit)",
),
[
{
kind: "google-docs-document",
href: "https://docs.google.com/document/d/doc123/edit",
provider: "Google Docs",
title: "Composer attachment polish",
typeLabel: "document",
},
],
);
});
test("extractSupportedLinkPreviews includes multiple supported Google links", () => {
assert.deepEqual(
extractSupportedLinkPreviews(
[
"https://docs.google.com/document/d/doc123/edit",
"https://docs.google.com/spreadsheets/d/sheet123/edit",
"https://docs.google.com/presentation/d/slides123/edit",
].join(" "),
).map((preview) => preview.kind),
[
"google-docs-document",
"google-sheets-spreadsheet",
"google-slides-presentation",
],
);
});
test("extractSupportedLinkPreviews skips URLs inside inline and fenced code", () => {
assert.deepEqual(
extractSupportedLinkPreviews(
[
"`https://github.com/block/sprout/pull/1`",
"```",
"https://linear.app/buzz/issue/BUG-2/fix-preview",
"```",
"https://github.com/block/sprout/pull/3",
].join("\n"),
).map((preview) => preview.title),
["block/sprout #3"],
);
});
test("extractSupportedLinkPreviews skips URLs inside indented code", () => {
assert.deepEqual(
extractSupportedLinkPreviews(
[
" https://docs.google.com/document/d/hidden/edit",
"\tgithub.com/block/sprout/pull/4",
"https://github.com/block/sprout/pull/5",
].join("\n"),
).map((preview) => preview.title),
["block/sprout #5"],
);
});
test("extractSupportedLinkPreviews skips markdown image link URLs", () => {
assert.deepEqual(
extractSupportedLinkPreviews(
[
"![alt](https://docs.google.com/document/d/doc123/edit)",
"![alt](https://github.com/block/sprout)",
"[Composer attachment polish](https://docs.google.com/document/d/doc456/edit)",
].join("\n"),
).map((preview) => preview.title),
["Composer attachment polish"],
);
});
test("extractSupportedLinkPreviews treats other absolute HTTPS URLs as generic", () => {
assert.deepEqual(
extractSupportedLinkPreviews(
[
"https://evil-github.com/block/sprout/pull/1",
"https://example.com/go/https://docs.google.com/document/d/doc123/edit",
"(https://github.com/block/sprout/pull/2)",
].join(" "),
).map((preview) => preview.title),
["evil-github.com", "example.com", "block/sprout #2"],
);
});
test("extractSupportedLinkPreviews skips links inside inline spoilers", () => {
assert.deepEqual(
extractSupportedLinkPreviews(
[
"Keep",
"||[roadmap](https://docs.google.com/document/d/hidden/edit)||",
"hidden, but show https://github.com/block/sprout/pull/7",
].join(" "),
).map((preview) => preview.title),
["block/sprout #7"],
);
});
test("extractSupportedLinkPreviews skips links inside block spoilers", () => {
assert.deepEqual(
extractSupportedLinkPreviews(
[
"||",
"",
"https://linear.app/buzz/issue/BUG-99/hidden-spoiler-link",
"",
"||",
"https://github.com/block/sprout/pull/8",
].join("\n"),
).map((preview) => preview.title),
["block/sprout #8"],
);
});
test("isSupportedLinkAutolinkLabel matches normalized bare URL labels", () => {
const preview = parseSupportedLinkPreview("github.com/block/sprout/pull/5");
assert.ok(preview);
assert.equal(
isSupportedLinkAutolinkLabel(
"https://github.com/block/sprout/pull/5",
preview,
),
true,
);
assert.equal(isSupportedLinkAutolinkLabel("review this", preview), false);
});
test("parseSupportedLinkPreview parses generic HTTPS URLs", () => {
assert.deepEqual(
parseSupportedLinkPreview("https://example.com/articles/rich-previews"),
{
kind: "generic-link",
href: "https://example.com/articles/rich-previews",
provider: "example.com",
title: "example.com",
typeLabel: "link",
},
);
});
test("parseSupportedLinkPreview rejects generic HTTP URLs", () => {
assert.equal(
parseSupportedLinkPreview("http://example.com/articles/rich-previews"),
null,
);
});
test("extractSupportedLinkPreviews finds generic links and preserves exclusions", () => {
assert.deepEqual(
extractSupportedLinkPreviews(
[
"Read https://example.com/article first.",
"`https://hidden.example.com/secret`",
"then [the details](https://docs.example.org/details)",
].join(" "),
).map(({ kind, title }) => ({ kind, title })),
[
{ kind: "generic-link", title: "example.com" },
{ kind: "generic-link", title: "the details" },
],
);
});