From 1b7e03cd2ccedabb303898b2157ca0897db95601 Mon Sep 17 00:00:00 2001 From: Codeman maintainer Date: Thu, 12 Nov 2026 02:21:08 +0100 Subject: [PATCH] fix(response-viewer): the Copy Markdown button is named by the words on it, and the full view's fallback has a test (#810) The button's aria-label replaced its visible text as the accessible name with a sentence that does not contain "Copy Markdown" (WCAG 2.5.3, Label in Name), so a voice-control user could not say what they see. The label is gone and the sentence stays as the title. No test reached the line that keeps the brief view's answer when the full view holds no answer of its own: the existing case returns earlier, on an empty list. A full view that holds only a prompt reaches it. The PR's own changeset is folded into the 1.51.0 one. Co-Authored-By: Claude Fable 5.2 --- .changeset/response-copy-markdown.md | 5 ----- src/web/public/index.html | 2 +- test/response-viewer-copy-markdown.test.ts | 19 ++++++++++++++++++- 3 files changed, 19 insertions(+), 7 deletions(-) delete mode 100644 .changeset/response-copy-markdown.md diff --git a/.changeset/response-copy-markdown.md b/.changeset/response-copy-markdown.md deleted file mode 100644 index e04891cc..00000000 --- a/.changeset/response-copy-markdown.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"aicodeman": patch ---- - -Response viewer: add a Copy Markdown button that copies the raw source of the newest answer (every part of its turn), so headings, lists and code fences paste intact. An empty viewer and a refused clipboard both say so. diff --git a/src/web/public/index.html b/src/web/public/index.html index 18407ae4..9d1a3248 100644 --- a/src/web/public/index.html +++ b/src/web/public/index.html @@ -733,7 +733,7 @@ Last Response
- +
diff --git a/test/response-viewer-copy-markdown.test.ts b/test/response-viewer-copy-markdown.test.ts index f29c3d13..29f9d27e 100644 --- a/test/response-viewer-copy-markdown.test.ts +++ b/test/response-viewer-copy-markdown.test.ts @@ -178,6 +178,18 @@ describe('what Copy Markdown copies', () => { await app.copyResponseViewerMarkdown(); expect(app._copyText).toHaveBeenCalledWith('the answer on screen'); }); + + it('keeps the brief answer when the full view holds a prompt and no answer of its own', async () => { + mountViewer(); + const app = makeApp( + { text: 'the brief answer' }, + { messages: [{ kind: 'prompt', role: 'user', text: 'a question', turn: 1 }] } + ); + await app.toggleResponseViewer(); + await app.loadFullContext(); + await app.copyResponseViewerMarkdown(); + expect(app._copyText).toHaveBeenCalledWith('the brief answer'); + }); }); describe('what Copy Markdown reports', () => { @@ -255,7 +267,12 @@ describe('Copy Markdown markup, layout and strings', () => { const tag = actions.match(/]*id="responseViewerCopyMarkdown"[^>]*>/)?.[0] ?? ''; expect(tag).toContain('type="button"'); expect(tag).toContain('onclick="app.copyResponseViewerMarkdown()"'); - expect(tag).toContain('aria-label="Copy response as Markdown"'); + expect(tag).toContain('title="Copy response as Markdown"'); + // The words on the button are its accessible name (WCAG 2.5.3, Label in + // Name): an aria-label would replace them with a name that does not + // contain "Copy Markdown", so a voice-control user could not say what + // they see. The longer sentence stays as the title. + expect(tag).not.toContain('aria-label'); expect(actions).toContain('>Copy Markdown'); });