Skip to content

fix(session-ui): copy Markdown selections without theme colours - #59151

Merged
opencode-agent[bot] merged 1 commit into
devfrom
desktop-sanitized-copy
Oct 27, 2026
Merged

opencode-agent[bot] merged 1 commit into
devfrom
desktop-sanitized-copy

Conversation

@opencode-agent

@opencode-agent opencode-agent Bot commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

Issue for this PR

Closes #54003

Type of change

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

What does this PR do?

When you select text in a message and copy it, Chromium writes text/html with the computed styles inlined. In a dark theme that includes the light text colour, and code blocks keep their syntax-highlight style spans. Pasted into Docs, Gmail or Slack, you get white-on-white text and theme-coloured code.

Markdown now handles copy for selections that stay inside it:

  • text/plain is selection.toString(), the same text as before.
  • text/html is the selected range cloned from the DOM. Cloning doesn't carry computed styles. It drops the copy button, svg icons, aria-hidden favicons and .sr-only, and removes every attribute except href (http/https/mailto), img src (http/https), alt, colspan, rowspan and start. So bold, italic, headings, lists, tables, code, links and blockquotes come through, but no colours or classes.
  • A partial selection is wrapped in its semantic ancestors (strong, pre > code, ul > li, table rows, …) so it keeps its meaning. Selecting part of a code block pastes as code, for example.

A selection that leaves the message (for example, across tool cards) keeps the browser's default copy, because the surrounding UI can contain hidden content that a DOM clone would pick up.

How did you verify your code works?

  • bun test src/components/markdown-clipboard.test.ts: structure kept, colours, classes and copy buttons removed, partial selections wrapped, unsafe or local links dropped.
  • New component tests in component-tests/markdown.spec.ts run in Chromium: they render real Markdown in a light-text page, select it and press Ctrl/Cmd+C, then check the text/html contains <h2>, <strong>, a link, list items and <pre><code> with no style=, class=, <button> or <svg>. They also check that a selection extending outside the Markdown is left to the browser. The existing Markdown component specs I ran still pass. renders class and connected subgraph diagrams in light also fails on dev on this machine.
  • bun run typecheck, bun run typecheck:tsc (session-ui) and bun run lint:changed.

Checklist

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

Requested by: @Brendonovich (Brendan via Slack)


Added by the opencode bot (AI-generated).

Related: #54002, #56672

@opencode-agent opencode-agent Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This fixes the problem. On the base, copying a selection from a message puts Chromium's inlined computed styles (text colour, background, fonts) into text/html. With this change the HTML is clean semantic markup and text/plain is unchanged. I checked this with a Playwright test that reads the real clipboard in both checkouts, plus the new unit and component tests and typecheck:tsc.

Public API: unchanged. This is internal to the session UI's Markdown component.

One thing worth fixing: once a Mermaid diagram has rendered, its hidden source block is still copied into the HTML (see the inline comment).

Related: #54002, #56672

"style",
"template",
"svg",
'[aria-hidden="true"]',

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Once a Mermaid diagram renders, its source <pre> is still in the DOM, hidden with display: none ([data-mermaid-ready="true"] > pre / [data-mermaid-pending="true"] > pre in markdown.css). The clone picks it up. When I selected a message containing a diagram, text/html contained <pre><code>flowchart LR\n A[Start] --&gt; B[End]</code></pre>. text/plain (and the base's HTML) don't contain that source; the plain text has the diagram labels instead. This is the same hidden-content problem that the fallback for selections leaving the message is meant to avoid. Could the hidden Mermaid <pre> be added to removedSelector, e.g. [data-mermaid-ready="true"] > pre, [data-mermaid-pending="true"] > pre? That would keep the HTML in line with what's on screen.

@opencode-agent
opencode-agent Bot force-pushed the desktop-sanitized-copy branch from 862a6ea to 51f1bff Compare October 27, 2026 06:56

@opencode-agent opencode-agent Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The latest push is a rebase onto dev with no changes to this PR's files. The clipboard unit test, the two new Chromium copy tests and typecheck:tsc still pass. The public API is unchanged.

The earlier point about the hidden Mermaid source being copied into text/html is still open.

@opencode-agent
opencode-agent Bot enabled auto-merge (squash) October 27, 2026 07:09
@opencode-agent
opencode-agent Bot merged commit 6aedf36 into dev Oct 27, 2026
20 checks passed
@opencode-agent
opencode-agent Bot deleted the desktop-sanitized-copy branch October 27, 2026 07:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEATURE]: Desktop copy should preserve basic formatting (bold, italic, lists, tables) but not colour

1 participant