Repository navigation
Conversation
wesbillman
left a comment
There was a problem hiding this comment.
Four source-demonstrated copy/paste defects need fixes; details are inline: picker-label escaping, whole-chip selection, emphasis flanking, and code delimiters.
Star Lord automated source review via Wes's account. Head bf2223c576381f63ed84bef42642ed6e0080216c; base 0a8521e897224b6313428dd707134d6d41a14a55. No tests or app execution. One CI snapshot shows CI required failed (WebKit journeys 1/6); failure cause not investigated. Native WKWebView clipboard acceptance and visual/privacy inspection of all six attached videos remain unverified. This is a non-blocking COMMENT review, not approval.
kalvinnchau
left a comment
There was a problem hiding this comment.
🤖 Four additional copy/paste defects are detailed inline: lost block structure, Markdown inserted into code blocks, an added code-block newline, and extra plain-text block spacing. The four existing findings remain applicable. No approval or merge action.
wesbillman
left a comment
There was a problem hiding this comment.
No further source changes requested. The eight earlier clipboard findings are addressed in the follow-up source and regression cases; I found no additional actionable defects.
Star Lord automated source review via Wes’s account — head e0dfce09e06edfb8814bbc469cb705e71ea1fd85, base 0a8521e897224b6313428dd707134d6d41a14a55. No code or tests executed; native WKWebView acceptance remains unverified, and the three refreshed MP4 attachments could not be visually inspected (unsupported by the available image tool). The single CI snapshot showed failed Chromium 6/6 and WebKit 6/6 browser shards with other shards still running; this is not a runtime-validation or merge-readiness verdict.
kalvinnchau
left a comment
There was a problem hiding this comment.
🤖 One new partial-list copy regression is detailed inline. The eight earlier findings are addressed by the repair; fix this selection-boundary case before merge. No approval or merge action.
Keep list-item HTML only when serializing its enclosing list, so a phrase selected across inline formatting does not acquire an unintended bullet when pasted into the composer. Preserve whole-list markers and numbering. Cover rendered bullet and ordered items through selection serialization and the real editor paste handler, including whole-list controls. Fixes the finding in PR #579 discussion_r4182280708. Signed-off-by: Matt Toohey <contact@matttoohey.com>
…and links Selections copied from the channel timeline and thread panel previously used the browser default, which drops the @/# sigils, mention identity and raw link destinations. One capture-phase document copy listener, scoped to selections inside [data-message-id] and never inside an editor, now writes two flavors: - text/plain: @name, #name, bare URLs for raw links, `label (url)` for authored labels, custom-emoji shortcodes, newline block boundaries. - text/html: <div data-buzz-copy="timeline"> with semantic HTML — mentions as <a href="nostr:npub…">@name</a>, channel refs as <a href="buzz://channel/<id>">#name</a>, strong/em/code/s, p/br/pre. The walker in selection-copy.ts also has an identity-preserving Markdown mode ([@name](nostr:…), [#name](buzz://channel/…), fenced blocks) and accepts any Element/DocumentFragment, so the composer paste path can read the HTML flavor back. The DOM is treated as untrusted: nostr: hrefs pass profileKey, buzz: pass parseBuzzLink, https: the credential-free check; anything else is text. A mention button or link anchor whose selected text is not its whole label is emitted as plain text with no identity. Prerequisite DOM changes: the mention element exposes data-profile-target and data-mention-name; MessageLink anchors expose data-link-label holding the authored or directory label (empty for a raw destination, since the visible text drops the channel # and DM labels have none). The emoji plugin's copy listener keeps its trigger policy but delegates serialization to the shared walker, which runs first for message selections and emits shortcodes itself; its private text walk is removed. Both surfaces register through one refcounted hook in the inner Timeline and ThreadPanel so exactly one document listener exists; the hook guards DOM-less test environments like the emoji plugin does. Tests: walker cases for both modes plus HTML (whole/partial mention, channel anchor, raw and labelled buzz://message and https links, emoji, multi-row and table selection, inline code and fenced block, unknown elements, unsafe hrefs, HTML round-trip), a jsdom listener test covering both flavors, the editor guard and emoji-plugin coordination, and a Playwright journey that pointer-selects across a rendered mention button and channel ref and asserts the real copy event payload in Chromium and WebKit (WebKit includes <button> text in the selection). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: Matt Toohey <contact@matttoohey.com>
Composer copy and cut now write both clipboard flavors. text/plain stays the Markdown source, except that identity and channel locators read as @name and ProseMirror slice with a clipboard DOMSerializer, in the shapes the selection-copy walker reads back: recipients, identity links, channel refs, resources and URLs as anchors; marks as strong/em/s/code; spoilers as ||…|| text; paragraphs, line breaks and fenced code as p/br/pre. Paste prefers this app's own [data-buzz-copy] HTML, parsed inertly and serialized back to Markdown by the walker, and otherwise keeps today's text/plain behaviour; other apps' HTML is never read. After any paste the editor promotes [@name](nostr:npub…) chips inside the pasted range to recipient tokens when the host's new acceptRecipient prop accepts the pubkey, under the person's current name, at a mention boundary and under the 32-recipient cap. MessageComposer accepts the channel's mention candidates and nothing during edits. Bare @name text never notifies. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: Matt Toohey <contact@matttoohey.com>
…ape on copy Four review findings on the copy/paste work: - Composer → composer paste of block structure regressed once text/html was preferred: the walker's markdown mode flattened lists, quotes and fences. It now writes ul/ol items with `- ` and `N. ` markers (honouring start), indents continuation lines under the marker, prefixes every blockquote line with `> ` (nested quotes compound), joins list/quote/fence siblings with a blank line, and fences pre > code with the info string from class="language-…" or the composer's data-language. Whitespace text nodes between blocks are ignored in markdown mode only, so the text and HTML flavors are byte-identical to before. The timeline's own <ol> of message rows is layout, not a list. - Emphasis marks with edge whitespace produced invalid delimiters (`a **bold **b`); markdown mode now hoists the whitespace outside strong, em and strike, matching composerMarkdown. Code spans keep theirs. - composerClipboard serializes a code block's language as <pre><code class="language-…">, omitting the class when null so existing fixtures stay byte-identical; the walker also emits the class in HTML mode. - MessageLink's data-link-label now carries only the author-supplied label. The directory channel name moves to a display-only directoryLabel prop (visible text, aria-label and LinkLabelContext are unchanged), so a raw buzz://message or buzz://channel destination in prose copies as its URL in every flavor and keeps its authored shape on round trip. ReferenceText's #name refs are authored and still restore the sigil. Tests: walker cases for whitespace hoisting, list/quote/fence blocks (whole and partial selections), the language class and the updated fence case; the listener test renders a raw and a labelled message link through MessageMarkdown and asserts the display name stays while the copy is the URL; a composer round trip (marked whitespace, bullet list, ordered list from 3, quote, sh fence → HTML → markdown equals composerMarkdown, and pastes back as the same source); the composer-paste browser spec now carries both a raw and an authored message link. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: Matt Toohey <contact@matttoohey.com>
Pasting a copied message into a stream or forum where a mentioned person is not a member left their [@name](nostr:npub…) as a display chip that looks like a mention but sends no p or mention tag, so nobody was notified and the sender was not told. The @ picker offers that person as an outside row and Send asks before adding them; the paste gate called the same mentionCandidates() without the one-element directory the picker passes. acceptRecipient now passes that directory entry, named from the profile cache. The pasted label is never used as the name, so [@jane](nostr:<Eve>) cannot produce a chip and consent dialog that say "Jane" while inviting Eve. A key with no cached profile is still declined. DMs, groups, sessions and edits are unchanged, because mentionCandidates only reads the directory in streams and forums and edits still decline. Tests: for stream and forum, a pasted nonmember with a cached profile is promoted under the cached name rather than the pasted label, and appears in Explicit mentions. Send opens the nonmember dialog, and Do nothing sends a mention reference with no p tag. An uncached key in the same paste stays display-only. Both cases fail against the previous gate. The existing member test is retitled, since it never covered the picker's outside rows. docs/agents.md records that pasted mentions follow the picker's nonmember consent. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Matt Toohey <contact@matttoohey.com>
Address the eight copy/paste review findings. Decode picker labels with Markdown semantics and preserve whole-chip identity when selection endpoints share one label text node. Keep list, quote and table HTML structure while filtering renderer-only whitespace. Read sanitized clipboard HTML through the existing composer schema and Markdown serializer so emphasis flanking and code delimiter sizing match message sending. Use one code-block terminator across clipboard producers, preserve intentional and partially selected newlines, and paste plain text verbatim into existing code blocks. Add regression coverage for each failure and extend the browser journey through picker copy/paste and timeline structure to signed message content. Signed-off-by: Matt Toohey <contact@matttoohey.com>
Keep list-item HTML only when serializing its enclosing list, so a phrase selected across inline formatting does not acquire an unintended bullet when pasted into the composer. Preserve whole-list markers and numbering. Cover rendered bullet and ordered items through selection serialization and the real editor paste handler, including whole-list controls. Fixes the finding in PR #579 discussion_r4182280708. Signed-off-by: Matt Toohey <contact@matttoohey.com>
wesbillman
left a comment
There was a problem hiding this comment.
No further changes requested in this follow-up: the partial-list finding is addressed by the enclosing-list guard and renderer-to-editor regression cases, with whole-list markers and numbering retained. I found no actionable regression introduced by this fix.
Star Lord automated source review via Wes’s account; head be19a4474dbb33b105f347a7fd1d2c2ecae5a311, base 0bbf40aaaf0eb1fdb21a08c3500079922c812df8. No code/tests executed; hosted browser/native checks were still running at the single snapshot; human/native WKWebView acceptance and visual/privacy inspection of the three MP4 attachments remain unverified.
Keep selected plain-text whitespace and rendered paragraph boundaries. Use the timeline clipboard origin to restore blank lines on rich paste while preserving composer line semantics and the shared send serializer. Cover partial paragraph selections and real timeline-to-composer paste, plus composer whitespace round trips. Verified 233 focused tests, TypeScript, and the existing emoji clipboard journey in Chromium and WebKit. Signed-off-by: Matt Toohey <contact@matttoohey.com>
wesbillman
left a comment
There was a problem hiding this comment.
One P2 regression in the spacing follow-up: renderer-only whitespace is copied into plain text again (inline), reopening the previously fixed spacing finding. The timeline/composer paragraph distinction otherwise looks consistent.
Star Lord automated source review via Wes’s account. Head ebd9ce01a5885dd7c25738c01e8d473272bf61cc; base 0bbf40aaaf0eb1fdb21a08c3500079922c812df8. No code/tests/app executed. One hosted snapshot shows failing Chromium/WebKit shard 1 and CI required; causes not established here. Native clipboard acceptance remains pending. Public description/diff/commits inspected; the two currently linked MP4s were not visually inspected, so their privacy/content remains unverified.
| for (const child of node.childNodes) { | ||
| if ( | ||
| (range && !range.intersectsNode(child)) || | ||
| (format === "html" && formatting(child)) |
There was a problem hiding this comment.
[P2] Keep renderer-only newlines out of plain-text copy
This reintroduces the earlier spacing defect: removing the text-mode filter copies formatting nodes inside/around rendered lists and blockquotes as content. The changed real-renderer expectation in EditableInput.test.tsx:2629–2630 now explicitly accepts two\n\n\nquoted—two blank lines at a source boundary containing only one. Plain-text destinations (including paste into an existing code block) receive this extra spacing. Preserve authored whitespace and the new paragraph separation, but exclude renderer-only structural whitespace rather than restoring it globally; retain a mixed-block regression that rejects the extra newline.
Plain-text copies of rendered messages picked up whitespace the renderer writes only for HTML layout: the `\n` text node between block siblings and the one after every `<br>`. Blank lines stacked between blocks, a soft break copied (and pasted back) as a paragraph break, and P↔PRE or H↔P boundaries got a single newline while P↔P got a blank line. The walker now skips layout whitespace in every flavor, drops the newline that follows a `<br>`, and separates any two Markdown blocks (P, H1–6, UL, OL, BLOCKQUOTE, PRE, TABLE) with a blank line, matching the composer's serializer. List items, table rows and layout containers stay single lines. Tests: the DOM-separator case now asserts renderer whitespace never reaches the clipboard; three walker expectations gain the blank line at their P↔TABLE, P↔PRE and list/quote/fence boundaries; the real-renderer round trip adds `First\nSecond` and a paragraph-fence-paragraph case and drops the extra newline from the mixed-block case. All seven fail before this change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Matt Toohey <contact@matttoohey.com>
wesbillman
left a comment
There was a problem hiding this comment.
No changes requested in this follow-up: the renderer-whitespace fix resolves the previous spacing finding, with no new actionable defect found in the three-file repair.
Star Lord automated source review via Wes’s account; head c7e08501e03b976d1bb4c74ae772ca9baa67166d, base 0bbf40aaaf0eb1fdb21a08c3500079922c812df8; no code, tests or apps executed. The hosted snapshot includes a Chromium 1/6 failure, whose cause was not investigated. Native clipboard acceptance and visual/privacy inspection of both MP4 attachments remain unverified.
Update the composer-paste spec to expect the four blank lines between rendered Markdown blocks, matching the accepted clipboard output. Preserve the existing paste-and-send assertions. Verified the full composer-paste spec in Chromium and WebKit (2 passed). Signed-off-by: Matt Toohey <contact@matttoohey.com>
wesbillman
left a comment
There was a problem hiding this comment.
No changes requested in this test-only follow-up: the four blank-line boundaries now match the existing Markdown-block serializer, while list/table separators and the paste-and-signed-send assertions remain intact.
Star Lord automated source review via Wes’s account; head f4938f730374f60adaa4a55c65b630f8fc31f942, base 0bbf40aaaf0eb1fdb21a08c3500079922c812df8, following the previous review.
Source-only: no tests or app execution; the CI snapshot was still running (Windows skipped), native clipboard acceptance remains unverified, and both MP4 attachments remain visually/privacy-unverified because asset requests returned HTTP 403.
Summary
Copying from the timeline or the composer used to drop mention identity, the
@/#sigils and raw link destinations. This PR has copy write both clipboard flavors and has paste read this app's own HTML back.Timeline and thread copy (
selection-copy.ts)copylistener, limited to selections inside[data-message-id]and never inside an editor.text/plain:@Name,#name, bare URLs for raw links,label (url)for authored labels, custom-emoji shortcodes, and newlines between blocks.text/html:<div data-buzz-copy="timeline">with mentions asnostr:npub…anchors, channel refs asbuzz://channel/<id>anchors, and semantic inline and block markup.nostr:hrefs go throughprofileKey,buzz:hrefs throughparseBuzzLink, andhttps:hrefs through the credential-free check; anything else becomes plain text. A partially selected mention or link is copied as plain text with no identity.Composer copy, cut and paste (
composer-clipboard.ts,EditableInput)text/plain, with identity and channel locators shown as@Name/#name. They also write a<div data-buzz-copy="composer">HTML flavor in the shape the walker reads.[data-buzz-copy]HTML, parsed inertly and converted to Markdown. Otherwise it keeps the currenttext/plainbehaviour. HTML from other apps is never read.[@Name](nostr:npub…)chips in the pasted range become recipient tokens when the newacceptRecipientprop allows that pubkey. This respects the current name, mention boundaries and the 32-recipient cap.MessageComposerpasses the channel's mention candidates when composing and accepts none when editing. Bare@Nametext never notifies anyone.composerMarkdown, preserving lists, quotes, fence languages, emphasis flanking and collision-safe code delimiters. Table cells and rows retain tab/newline boundaries. Renderer-only whitespace is excluded from plain-text copy.text/plainverbatim. Composer and timeline code HTML share one final terminator while preserving intentional and partially selected newlines.DOM prerequisites
data-profile-targetanddata-mention-name.MessageLinkexposesdata-link-labelcontaining only the author-supplied label. The directory name moves to a display-onlydirectoryLabelprop, so a rawbuzz://link copies as its URL.Before / after
Each player pairs Before on the left (
0a8521e8) with After on the right (e0dfce09), with persistent revision labels. Before reuses the original merge-base recordings; all three After takes were refreshed at the current PR head, including the fixed picker-channel copy/paste behavior. Matching copy, paste, channel-switch and result steps are aligned with slight timing adjustments, and results pause for 4–5 seconds. Each 800×620 pane retains its original resolution in a 1616×704 H.264 MP4; use fullscreen to read the clipboard details.Recorded in headless Chromium with Playwright
recordVideoand the same synthetic browser-test scenarios, so no real people or channels appear. The recording script injects the dashed orange "Recording overlay" inspector, pointer, key badges and step captions; these and the persistent comparison labels are not part of the app. The inspector shows either the open composer's Explicit mentions or the clipboard after copy, with long HTML link values shortened using…. Copy and paste use real ⌘C/⌘V on Chromium's in-memory headless clipboard.1. Timeline → composer
Select a message with an
@mention, a#channelref and a rawbuzz://messagelink, press ⌘C, then press ⌘V in the composer. Before: the paste is plain text (Ping Alice Fixture about Beta and Alpha) and Explicit mentions stays empty. After: the mention, channel and message-link chips come back, and Alice is added to Explicit mentions, shown as the avatar next to the @ button.s1-before-after.mp4
2. Timeline → plain text
The same selection, with the overlay reading the clipboard after ⌘C. Before:
text/plainisPing Alice Fixture about Beta and Alpha: the sigils are gone and the link is reduced to its channel name. The browser's defaulttext/htmlhas no identity for the mention. After:text/plainisPing @Alice Fixture about #Beta and buzz://message?…, andtext/htmllinks the mention tonostr:npub…, the channel tobuzz://channel/beta, and keeps the message link.3. Composer → composer
In #Alpha, write
@Alice Fixtureand#Betawith the pickers, press ⌘A ⌘C, open #Beta and press ⌘V. Before: the mention arrives as plain@Alice Fixturetext and Explicit mentions stays empty; the#Betachip survives. After: the mention chip comes back, Alice is added to Explicit mentions, and the channel chip correctly remains#Beta. This refreshed take shows the picker-label fix ine0dfce09and replaces the old95030e76clip that showed a stray backslash.s3-before-after.mp4
Tests
EditableInputcopy, paste and recipient-promotion cases; and aMessageComposerround trip.timeline-copy.spec.mjs: makes a real pointer selection across a rendered mention button and channel ref, then checks the actual copy event payload. jsdom has no real selection or layout, and WebKit puts<button>text in the selection.composer-paste.spec.mjs: copies from the timeline and pastes into the real composer, then checks that chips, the recipient and both raw and authored links come back.composer-links.spec.mjsandmentions.spec.mjsnow also check the composer HTML flavor. No browser cases were removed.Review-fix validation (
e0dfce09)Partial-list selection fix (
a36132d4)- ask **Morgan** about it(and again with3.), select onlysk Morgan abacross the bold name and paste into an empty composer. Expectsk **Morgan** abwithout a bullet; selecting the whole list should preserve its marker and number. Native clipboard acceptance and full hosted CI remain deferred.Deferred checks
🤖 Generated with Claude Code