Skip to content

feat(composer): wrap selection on buzz:// link paste#1044

Closed
tellaho wants to merge 2 commits into
mainfrom
tho/buzz-link-on-paste
Closed

feat(composer): wrap selection on buzz:// link paste#1044
tellaho wants to merge 2 commits into
mainfrom
tho/buzz-link-on-paste

Conversation

@tellaho

@tellaho tellaho commented Jun 14, 2026

Copy link
Copy Markdown
Collaborator
Screen Recording 2026-06-14 at 3 42 11 PM

Overview

Category: improvement
User Impact: Highlighting text in the chat composer and pasting a buzz:// link now wraps the selection in a hyperlink — exactly like pasting a standard https:// link.

Problem: Pasting a buzz:// URL over a text selection replaced the highlighted text with the raw URL instead of turning it into a link. Standard https:// links already wrapped-on-paste, so the inconsistency was confusing.

Solution: TipTap's built-in linkOnPaste handler detects URLs with linkifyjs, which only knows standard schemes (http/https/mailto) — it has no idea buzz:// is a URL, so the wrap-on-paste handler bailed. This adds a custom ProseMirror paste plugin (prepended before TipTap's defaults) that recognizes any well-formed buzz:// URL and wraps the highlighted selection in a link mark. Per tho's direction, all buzz:// URLs wrap, not just the buzz://message?... deep-link format.

File changes

desktop/src/features/messages/lib/messageLink.ts
Added isBuzzUrl() — a broad check (via the URL API) for any well-formed buzz:// URL, distinct from the existing message-link-only isMessageLink().

desktop/src/features/messages/lib/useRichTextEditor.ts
Extended the Link extension's addProseMirrorPlugins with a buzzLinkOnPaste plugin that wraps a non-empty selection in a link mark when the clipboard payload is a single buzz:// URL. Runs before the parent plugins so it claims the paste; otherwise defers to TipTap's defaults.

desktop/src/features/messages/lib/messageLink.test.mjs
Added coverage for isBuzzUrl(): message links, non-message buzz:// URLs, whitespace tolerance, and rejection of standard/malformed/empty input.

Reproduction Steps

  1. Run the desktop app and open the chat composer.
  2. Copy a buzz:// URL (e.g. buzz://message?channel=...&id=...).
  3. Type some text, highlight a portion of it, and paste.
  4. The highlighted text becomes a hyperlink pointing at the buzz:// URL (previously it was clobbered with the raw URL text).
  5. Repeat with a standard https:// URL to confirm existing behavior is unchanged.

Testing

  • pnpm typecheck
  • pnpm lint ✅ (only pre-existing warnings in unrelated e2e files)
  • pnpm test ✅ 720 passing

npub1223z34hd7vtwc6qj4s7flsxkj644nlre2nthu7lrrmkumhu3xddsrx9r6w and others added 2 commits June 14, 2026 11:11
Pasting a buzz:// URL over a text selection clobbered it with raw text
instead of wrapping it in a link, unlike standard https:// links. The
cause: TipTap's built-in linkOnPaste handler uses linkifyjs to detect
URLs, and linkifyjs doesn't recognise the buzz scheme.

Add a custom ProseMirror paste plugin (prepended before TipTap's
defaults) that detects any well-formed buzz:// URL and wraps the
highlighted selection in a link mark — matching the standard-URL UX.
Detection lives in a new isBuzzUrl() helper alongside the existing
message-link parsing.

Per tho: ALL buzz:// URLs wrap, not just the message-link format.

Co-authored-by: Taylor Ho <taylorkmho@gmail.com>
Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
Comment-only pass — logic unchanged. Keeps the why (linkifyjs doesn't
know buzz://) and drops the play-by-play.

Co-authored-by: Taylor Ho <taylorkmho@gmail.com>
Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
@tellaho

tellaho commented Jun 14, 2026

Copy link
Copy Markdown
Collaborator Author

Closing as redundant — the behavior this PR adds already exists on main.

Why

The composer's Link extension on main is configured with protocols: ["buzz"] + linkOnPaste: true (landed in the "Rename to Buzz" PR #960, June 11). Verified against the installed @tiptap/extension-link@3.22.5 source:

  1. onCreate calls registerCustomProtocol("buzz") for each entry in protocols — so linkifyjs does recognize the buzz:// scheme.
  2. The built-in linkOnPaste handler checks for a non-empty selection, runs the paste through linkifyjs find(), and if the whole payload is a single link, calls setMark(...{ href }) to wrap the selection.

That is line-for-line what this PR's custom buzzLinkOnPaste ProseMirror plugin re-implements. Confirmed empirically: pasting a buzz:// URL over highlighted text on main already hyperlinks the selection.

Correction

The original root cause ("linkifyjs doesn't recognize buzz://, so the selection gets clobbered") was incorrect — protocols: ["buzz"] teaches linkifyjs the scheme, and #960 quietly fixed this for free. The PR was built against a problem that no longer existed.

Scope check

Diff of the branch's own two commits (b2ad3074..HEAD) touches only the 3 feature files (messageLink.ts, messageLink.test.mjs, useRichTextEditor.ts) — no unrelated fixes were packed in, nothing else lost by closing.

Still worth landing separately

The biome dead-code cleanup (the latent noUnusedVariables debt in onboarding.spec.ts that surfaces under PR-context lint) is being tracked as its own standalone PR to main — independent of this one.

@tellaho tellaho closed this Jun 14, 2026
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