From 53fac0e68ee69e46ecfb88ba3183a26059094fe7 Mon Sep 17 00:00:00 2001 From: arimu1 Date: Sun, 19 Jul 2026 21:40:01 +0700 Subject: [PATCH] fix(kimi-web): ignore non-string markdown copy payloads Markstream @copy can deliver a native ClipboardEvent. Coercing that object wrote the literal "[object ClipboardEvent]" to the clipboard. Only fall back when the payload is a non-empty string. Resolve #1812 Co-authored-by: Cursor --- .changeset/fix-web-copy-clipboard-event.md | 5 +++++ apps/kimi-web/src/components/chat/Markdown.vue | 8 ++++++-- apps/kimi-web/src/lib/clipboard.ts | 11 +++++++++++ apps/kimi-web/test/clipboard.test.ts | 16 +++++++++++++++- 4 files changed, 37 insertions(+), 3 deletions(-) create mode 100644 .changeset/fix-web-copy-clipboard-event.md diff --git a/.changeset/fix-web-copy-clipboard-event.md b/.changeset/fix-web-copy-clipboard-event.md new file mode 100644 index 0000000000..3af0a95101 --- /dev/null +++ b/.changeset/fix-web-copy-clipboard-event.md @@ -0,0 +1,5 @@ +--- +"@moonshot-ai/kimi-code": patch +--- + +Fix kimi web code-block copy writing "[object ClipboardEvent]" when a non-string copy payload is emitted. diff --git a/apps/kimi-web/src/components/chat/Markdown.vue b/apps/kimi-web/src/components/chat/Markdown.vue index 3e20657ddc..38c9c608a9 100644 --- a/apps/kimi-web/src/components/chat/Markdown.vue +++ b/apps/kimi-web/src/components/chat/Markdown.vue @@ -16,7 +16,7 @@ import { useIsDark } from '../../composables/useIsDark'; import type { FilePreviewRequest } from '../../types'; import { collectFilePathAliases, findFilePathLinks } from '../../lib/filePathLinks'; import { markdownRenderPlan } from '../../lib/markdownPerformance'; -import { copyTextToClipboard } from '../../lib/clipboard'; +import { asCopyableText, copyTextToClipboard } from '../../lib/clipboard'; import * as katexWorkerModule from 'markstream-vue/workers/katexRenderer.worker?worker&type=module'; import * as mermaidWorkerModule from 'markstream-vue/workers/mermaidParser.worker?worker&type=module'; import Tooltip from '../ui/Tooltip.vue'; @@ -338,10 +338,14 @@ const codeBlockProps = { loading: false, }; -function copyCodeBlockFallback(code: string): void { +function copyCodeBlockFallback(payload: unknown): void { // markstream emits `copy` even when it skipped the write because the // Clipboard API is unavailable. Reuse our plain-HTTP fallback in that case, // while avoiding a duplicate write after markstream succeeds on HTTPS. + // Also ignore non-string payloads (e.g. native ClipboardEvent) — coercing + // those would write the literal "[object ClipboardEvent]" to the clipboard. + const code = asCopyableText(payload); + if (!code) return; const clipboard = typeof navigator !== 'undefined' ? navigator.clipboard : undefined; if (clipboard && typeof clipboard.writeText === 'function') return; void copyTextToClipboard(code); diff --git a/apps/kimi-web/src/lib/clipboard.ts b/apps/kimi-web/src/lib/clipboard.ts index 832d9c445d..30c2cf8a2f 100644 --- a/apps/kimi-web/src/lib/clipboard.ts +++ b/apps/kimi-web/src/lib/clipboard.ts @@ -9,6 +9,17 @@ // so a `.then().catch()` chain cannot recover. We therefore probe for the API // first and fall back to a temporary