Skip to content

feat(messages): copy and paste mentions, channel refs and links - #579

Open
matt2e wants to merge 9 commits into
mainfrom
copy-and-paste
Open

matt2e wants to merge 9 commits into
mainfrom
copy-and-paste

Conversation

@matt2e

@matt2e matt2e commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor

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)

  • One refcounted capture-phase copy listener, 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 as nostr:npub… anchors, channel refs as buzz://channel/<id> anchors, and semantic inline and block markup.
  • The DOM is treated as untrusted. nostr: hrefs go through profileKey, buzz: hrefs through parseBuzzLink, and https: 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.
  • The emoji plugin keeps its own trigger policy but now uses the shared walker for serialization.

Composer copy, cut and paste (composer-clipboard.ts, EditableInput)

  • Copy and cut write the Markdown source as 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.
  • Paste prefers this app's [data-buzz-copy] HTML, parsed inertly and converted to Markdown. Otherwise it keeps the current text/plain behaviour. HTML from other apps is never read.
  • After a paste, [@Name](nostr:npub…) chips in the pasted range become recipient tokens when the new acceptRecipient prop allows that pubkey. This respects the current name, mention boundaries and the 32-recipient cap. MessageComposer passes the channel's mention candidates when composing and accepts none when editing. Bare @Name text never notifies anyone.
  • Markdown conversion reads sanitized semantic HTML through the existing composer schema and 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.
  • Picker-produced channel labels are decoded with Markdown semantics. Whole-chip selections preserve identity even when both endpoints are inside one text node. Pasting into an existing code block uses text/plain verbatim. Composer and timeline code HTML share one final terminator while preserving intentional and partially selected newlines.

DOM prerequisites

  • Mentions expose data-profile-target and data-mention-name.
  • MessageLink exposes data-link-label containing only the author-supplied label. The directory name moves to a display-only directoryLabel prop, so a raw buzz:// 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 recordVideo and 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 #channel ref and a raw buzz://message link, 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/plain is Ping Alice Fixture about Beta and Alpha: the sigils are gone and the link is reduced to its channel name. The browser's default text/html has no identity for the mention. After: text/plain is Ping @Alice Fixture about #Beta and buzz://message?…, and text/html links the mention to nostr:npub…, the channel to buzz://channel/beta, and keeps the message link.

3. Composer → composer

In #Alpha, write @Alice Fixture and #Beta with the pickers, press ⌘A ⌘C, open #Beta and press ⌘V. Before: the mention arrives as plain @Alice Fixture text and Explicit mentions stays empty; the #Beta chip 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 in e0dfce09 and replaces the old 95030e76 clip that showed a stray backslash.

s3-before-after.mp4

Tests

  • Vitest: walker cases for plain, Markdown and HTML modes, including whole and partial mentions, channel and link anchors, unsafe hrefs, emoji, multi-row and table selections, lists, quotes, fences and whitespace hoisting. Also a jsdom listener test covering both flavors, the editor guard and emoji-plugin coordination; EditableInput copy, paste and recipient-promotion cases; and a MessageComposer round trip.
  • Browser cases added:
    • 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.
  • Browser cases updated: composer-links.spec.mjs and mentions.spec.mjs now also check the composer HTML flavor. No browser cases were removed.

Review-fix validation (e0dfce09)

  • Added focused regressions for all eight P2 findings; before the fixes, 20 of the 22 initially targeted cases failed. The final affected Vitest files pass all 236 tests.
  • The existing composer-paste browser journey now covers picker-produced tokens and a rendered list/ordered list/quote/fence/table copied, pasted and sent as signed message content. Both affected browser files pass in Chromium and WebKit (4 journeys). This follow-up adds/removes no browser cases; the syntax matrix remains in Vitest.
  • Required commit/push hooks passed: staged formatting/lint, global security checks, TypeScript, 1,314 related Vitest tests across 54 files, and design-system types/guards. The hosted DCO Check passed at this head.
  • Independent review completed with no remaining blockers. It also caught partial-code newline and within-table selection boundaries, now fixed and covered.

Partial-list selection fix (a36132d4)

  • Addresses discussion_r4182280708: a phrase selected across formatting inside one list item now pastes as formatted prose. List-item HTML is retained only when its enclosing list is serialized; whole-list markers and ordered-list starts survive.
  • Both new bullet/ordered-item regressions failed before the fix. The real MessageMarkdown → selection serialization → EditableInput paste checks now pass; the full affected Vitest files pass all 225 tests.
  • The existing timeline-copy and composer-paste journeys pass in Chromium and WebKit (4 journeys). No browser cases were added or removed; this DOM conversion regression is covered at the component layer.
  • Required hooks passed at this commit: formatting/lint, security checks, TypeScript, 1,370 related Vitest tests across 54 files, and design-system types/guards. Self-review found no remaining issue in the scoped fix.
  • Human retest remains pending: in a message containing - ask **Morgan** about it (and again with 3.), select only sk Morgan ab across the bold name and paste into an empty composer. Expect sk **Morgan** ab without a bullet; selecting the whole list should preserve its marker and number. Native clipboard acceptance and full hosted CI remain deferred.

Deferred checks

  • Human native-app (WKWebView) clipboard acceptance remains pending, so this PR is a draft. Try copying a complete mention label and a picker-inserted channel reference, then paste into another composer; their chips and accepted mention recipient should survive. Copy a list/quote/fence and paste into prose, then paste formatted content into an existing code block; structure should survive in prose and code should receive plain text.
  • Full hosted CI for the updated head is pending; native builds and the full local scan were not run for this focused follow-up.

🤖 Generated with Claude Code

@matt2e
matt2e marked this pull request as ready for review October 5, 2026 06:05
@matt2e
matt2e requested review from a team, comp615 and wesbillman as code owners October 5, 2026 06:05

@wesbillman wesbillman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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.

Comment thread src/features/messages/composer-clipboard.ts
Comment thread src/features/messages/selection-copy.ts Outdated
Comment thread src/features/messages/selection-copy.ts Outdated
Comment thread src/features/messages/selection-copy.ts Outdated

@kalvinnchau kalvinnchau left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🤖 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.

Comment thread src/features/messages/selection-copy.ts
Comment thread src/features/messages/EditableInput.tsx
Comment thread src/features/messages/selection-copy.ts Outdated
Comment thread src/features/messages/selection-copy.ts Outdated
@matt2e
matt2e marked this pull request as draft October 5, 2026 08:23
@matt2e
matt2e marked this pull request as ready for review October 5, 2026 08:28

@wesbillman wesbillman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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 kalvinnchau left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🤖 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.

Comment thread src/features/messages/selection-copy.ts
matt2e added a commit that referenced this pull request Oct 5, 2026
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>
@matt2e
matt2e marked this pull request as draft October 5, 2026 22:37
matt2e and others added 6 commits October 6, 2026 10:45
…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>
@matt2e
matt2e marked this pull request as ready for review October 5, 2026 23:46

@wesbillman wesbillman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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.

@matt2e
matt2e marked this pull request as draft October 6, 2026 00:14
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>
@matt2e
matt2e marked this pull request as ready for review October 6, 2026 00:38

@wesbillman wesbillman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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.

Comment thread src/features/messages/selection-copy.ts Outdated
for (const child of node.childNodes) {
if (
(range && !range.intersectsNode(child)) ||
(format === "html" && formatting(child))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[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 wesbillman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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 wesbillman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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.

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.

3 participants