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'); });