Skip to content

feat(app): add copy rich text option for message content#16056

Open
anduimagui wants to merge 12 commits intoanomalyco:devfrom
anduimagui:opencode/glowing-otter
Open

feat(app): add copy rich text option for message content#16056
anduimagui wants to merge 12 commits intoanomalyco:devfrom
anduimagui:opencode/glowing-otter

Conversation

@anduimagui
Copy link
Copy Markdown
Contributor

@anduimagui anduimagui commented Mar 4, 2026

Issue for this PR

Closes #10693

Type of change

  • Bug fix
  • New feature
  • Refactor / code improvement
  • Documentation

What does this PR do?

This fixes assistant response copy behavior for desktop so rich text paste targets (for example Apple Mail) no longer fall back to Times when copying from rendered markdown.

It adds explicit clipboard payloads (text/plain + text/html) for markdown selection copy and assistant rich copy, includes inline fallback font/link/code styles in copied HTML, and introduces a desktop settings control for assistant copy mode (Plain text, Rich text, Ask each time) with rich text as the default for new settings. The settings row was also adjusted so the selector stays aligned on the right, and the copy button icon remains consistent across modes.

How did you verify your code works?

  • Ran bun test src/components/markdown-copy.test.ts in packages/ui
  • Ran bun test src/components/session-review-search.test.ts in packages/ui
  • Ran bun test src/i18n/parity.test.ts in packages/app
  • Push hook bun turbo typecheck passed before push

Screenshots / recordings

Copy options in settings:
Screenshot 2026-03-18 at 18 49 14

Message copy button dropdown in chat:
Screenshot 2026-03-18 at 18 49 36

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

@anduimagui anduimagui requested a review from adamdotdevin as a code owner March 4, 2026 22:49
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 4, 2026

The following comment was made by an LLM, it may be inaccurate:

Found one potential related PR:

PR #11638: feat(tui): add rich text clipboard support for copying formatted content
#11638

This PR may be related as it also deals with rich text clipboard support, though it appears to be for TUI (terminal UI) while PR #16056 is focused on desktop. The PRs target different platforms but share the same core feature of handling rich text in clipboard operations with explicit payload formats.

@anduimagui
Copy link
Copy Markdown
Contributor Author

yh this PR intentionally scopes to desktop/webview chat UI @adamdotdevin
no TUI codepaths touched

@anduimagui anduimagui force-pushed the opencode/glowing-otter branch from 8db1ade to 5275451 Compare March 5, 2026 09:37
@anduimagui anduimagui changed the title fix(copy): add desktop rich copy mode with clear settings toggle feat(app): add copy rich text option for message content Mar 18, 2026
Move markdown clipboard cleanup and copy behavior into shared clipboard helpers so text-part rendering can stay focused on message display and future rich-copy reuse avoids conflicts in message-part.
Allow the markdown clipboard helper to accept queryable roots instead of ParentNode so shared UI consumers typecheck cleanly across package boundaries and CI environments.
@andrewdunndev
Copy link
Copy Markdown

I just closed #19035 which overlapped with this PR. Your approach is cleaner -- dedicated utility modules, tests, and the settings toggle is a better UX than the per-message dropdown I was building.

Two things from my implementation that might be worth considering here:

1. Email-specific copy mode

Your rich text copy preserves the rendered styles, which works well for Notion/GitHub/Docs. But when pasting into Gmail or Outlook, the app-specific CSS classes don't render. I built an "email sanitization" pass that strips all classes/styles and inlines email-safe CSS directly on elements:

// Strip all classes/styles, then inline email-safe CSS
div.querySelectorAll("a").forEach(a => 
  a.setAttribute("style", "color: #1a73e8; text-decoration: underline;"))
div.querySelectorAll("code").forEach(code => {
  if (code.parentElement?.tagName !== "PRE")
    code.setAttribute("style", "background: #f1f3f4; padding: 1px 4px; border-radius: 3px; font-family: monospace;")
})
// ... pre, blockquote, table, th/td similarly
// Wrap in: <div style="font-family: Arial, Helvetica, sans-serif; font-size: 14px;">

This could be a third copy mode ("Copy for Email") or a variant of your rich text mode that detects the target. Either way, the inline styles are what make it render correctly in email clients.

2. DOMPurify before DOM parsing

Your cleanMarkdownHTML uses DOMParser on the rendered HTML. If the markdown contains untrusted content (model output with embedded HTML), setting innerHTML on even an off-DOM element can trigger network loads from <img>, <video>, etc. Running DOMPurify first (with FORBID_TAGS: ["img", "video", "audio", "iframe"] for the clipboard path) prevents this. The app already uses DOMPurify in Markdown.tsx so the dependency is there.

Happy to contribute either of these as a follow-up PR if this one lands, or as additions here if you'd prefer.

Sanitize rich clipboard markup before serializing it and strip unsafe embedded media from copied assistant responses. Keep coverage for the clipboard serializer so the rich copy path stays safe in fallback environments too.
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.

[FEATURE]: Copy assistant messages as rich text for pasting into Google Docs, Notion, etc.

2 participants