From 6aedf36ca307f97919f39d655de13b3ad5ccd24f Mon Sep 17 00:00:00 2001
From: "opencode-agent[bot]"
<219766164+opencode-agent[bot]@users.noreply.github.com>
Date: Tue, 27 Oct 2026 15:11:22 +0800
Subject: [PATCH] fix(session-ui): copy Markdown selections without theme
colours (#59151)
Co-authored-by: Brendonovich <14191578+Brendonovich@users.noreply.github.com>
---
.../component-tests/markdown.spec.ts | 60 ++++++++++
.../src/components/markdown-clipboard.test.ts | 107 ++++++++++++++++++
.../src/components/markdown-clipboard.ts | 91 +++++++++++++++
.../session-ui/src/components/markdown.tsx | 18 +++
4 files changed, 276 insertions(+)
create mode 100644 packages/session-ui/src/components/markdown-clipboard.test.ts
create mode 100644 packages/session-ui/src/components/markdown-clipboard.ts
diff --git a/packages/session-ui/component-tests/markdown.spec.ts b/packages/session-ui/component-tests/markdown.spec.ts
index c1c1ab40609..8c120b3ad77 100644
--- a/packages/session-ui/component-tests/markdown.spec.ts
+++ b/packages/session-ui/component-tests/markdown.spec.ts
@@ -983,3 +983,63 @@ story("loads file URLs and Windows paths, and leaves unreadable images as alt te
await harness.getByLabel("Markdown text").fill("Still usable")
await expect(harness.locator('[data-component="markdown"]')).toHaveText("Still usable")
})
+
+async function copySelection(page: Page, select: "markdown" | "harness") {
+ await page.evaluate((select) => {
+ const harness = document.querySelector('[data-testid="markdown-fixture"]')!
+ const range = document.createRange()
+ range.selectNodeContents(select === "markdown" ? harness.querySelector('[data-component="markdown"]')! : harness)
+ getSelection()!.removeAllRanges()
+ getSelection()!.addRange(range)
+ document.addEventListener(
+ "copy",
+ (event) => {
+ document.body.dataset.copiedText = event.clipboardData?.getData("text/plain")
+ document.body.dataset.copiedHtml = event.clipboardData?.getData("text/html")
+ },
+ { once: true },
+ )
+ }, select)
+ await page.keyboard.press("ControlOrMeta+C")
+
+ return page.evaluate(() => ({ text: document.body.dataset.copiedText, html: document.body.dataset.copiedHtml }))
+}
+
+story("copies selected Markdown as semantic HTML without theme or highlight colours", async ({ page }) => {
+ await page.evaluate(async (fixture) => {
+ document.body.style.color = "rgb(237, 237, 237)"
+ const { mountMarkdown } = await import(fixture)
+ await mountMarkdown({
+ text: "## Answer\n\nSome **bold** and [docs](https://opencode.ai/docs).\n\n- one\n- two\n\n```ts\nconst value = 42\n```",
+ })
+ }, fixture)
+ const harness = page.getByTestId("markdown-fixture")
+ await expect(harness.locator('[data-component="markdown"]')).toHaveAttribute("data-markdown-ready", "")
+ await expect(harness.locator("pre code span[style]").first()).toBeAttached()
+
+ const copied = await copySelection(page, "markdown")
+
+ expect(copied.text).toContain("Some bold and docs.")
+ expect(copied.text).toContain("const value = 42")
+ expect(copied.html).toContain("
Answer
")
+ expect(copied.html).toContain('Some bold and docs.
')
+ expect(copied.html).toContain("one")
+ expect(copied.html).toMatch(/([^<]*<\/span>)+<\/code><\/pre>/)
+ expect(copied.html).not.toMatch(/style=|class=|