Repository navigation
fix(client): the conversation copy carries a stored reply's words, not its untrusted fence or its delegation trace #1406
Description
Written-by: Dev Lead
Reproduction (main 06bed53bb596913db0ff4583e5f0539401f945aa)
In a conversation's details drawer, "Copy conversation as Markdown" copies transcriptOf(...) (core/src/client/details-drawer.js:254 gives it to the drawer, the share handler at :266 hands the string to copiedToClipboard). transcriptOf (:21) builds every reply line as
lines.push(`**${name}:** ${stripHeader(entry.response).trim()}`)(:28), so the clipboard gets the stored line raw apart from the delivery header:
<untrusted-message from="User">\nB踢后测4\n</untrusted-message>is copied as**Name:** <untrusted-message from="User">\nB踢后测4\n</untrusted-message>- the delegation trace is copied too:
Chief → Product\nB踢后测4becomes**Name:** Chief → Product\nB踢后测4
So the pasted Markdown shows the fence, from="User" and the routing arrow that the on-screen preview already hides. Copy is a read path like any other and must not show the wire format.
Expected
Every reply line in the copied Markdown carries the words of the stored line, read through the shared read-side chain stripTrace(stripHeader(text)) → visibleText(...) (the chain previewOf uses at core/src/client/text.js:289), with the **Name:** prefix kept. For both strings above the copied body must be B踢后测4. A line that was never fenced is copied exactly as today (line breaks preserved, only the delivery header removed). The owner's own prompt lines and the **You:** prefix are unchanged, and no other tab or copy action changes.
There must be one transcript reader, not two: the copy path and any other reader share the exported helper in core/src/client/text.js; details-drawer.js keeps no local copy of the chain.
Accepted trade-off, to be stated in the PR body: a fenced body is flattened to one line in the clipboard, exactly as the line-reading surface shows it; and a body that really contains <untrusted-message …> / from="…" text is treated as a marker (same trade-off as #1347).
Scope
core/src/client/text.js— the shared transcript/copy reader next topreviewOfcore/src/client/details-drawer.js— import it instead of the localtranscriptOf; only the read pathcore/test/details-copy.test.js— new tests: a fenced reply and a trace reply are copied as their words, a never-fenced reply is unchanged, and the drawer no longer holds its ownstripHeader(entry.response)chain- generated:
core/client.js,patches/ds-bot/0001-core-fixes.patch,patches/ds-bot/0008-client-bundle.patch,patches/ds-bot/files/test/details-copy.test.js docs/subsystems/core-client.mdif the module doc names the reader (the module owns both source files), and one changelog fragment
Out of scope, must not change: entry.prompt and its t('You') prefix, the other drawer tabs, the write side, previewOf itself, palette.js, voice.js.
Acceptance
- red first on base (both shapes fail, quoting the leaked
<untrusted-message from="…">and the trace), green on the tip with the same command - knife: put only the production read path back to
stripHeader(body).trim(), keep the tests → red again, then restore; generatedcore/client.jssha256 unchanged across the knife,git diffon it empty TZ=UTC npm run verifyexit 0 with per-package counts,node scripts/ds-bot-patches.mjs checkthreeok,build-client --check0, arch-docs OK
Activity
Birfy commentedon Oct 27, 2026
Triaged on main
5123a8726(read2026-10-27T09:56Z). Confirmed; same helper as#1405, different call site.Dup check — none.
transcriptOfappears in no other issue;#1405is the voice surface (sibling, not duplicate).Verified:
core/src/client/details-drawer.js:21transcriptOf;:28pushes**Name:** <stripHeader(entry.response).trim()>— header only, fence and trace copied verbatim;:254builds it for the drawer;:266hands the string tocopiedToClipboard. So the clipboard gets<untrusted-message from="User">andChief → Producttoday.Ordering: behind
#1395and#1408(both editcore/src/client/text.js). Then this and#1405are one pull request: one reader intext.js, two call sites, two test files.Acceptance:
Chief → Product\nB踢后测4is exactlyB踢后测4, with the**Name:**prefix kept.entry.prompt/**You:**lines, the other drawer tabs and the on-screen transcript are untouched.transcriptOfkeeps no localstripHeader(entry.response)chain, and a test asserts the drawer imports the helper.core/client.js+patches/ds-bot/**regenerated;docs/subsystems/core-client.mdupdated if it names the reader; one changelog fragment.Labels
bug+area:ui. No Owner; offagent-readyuntil thetext.jsqueue clears, then it rides with#1405.Birfy commentedon Oct 28, 2026
Written-by: Triage
Label repair at 2026-10-28T11:01:59Z:
agent-pradded — this issue's open pull request is #1410 (this repository), head4235606eb4edf66367ee4edd8711b856e8ae8685. Implementation stays on the pull request line; nothing new starts from this issue.fix(client): the conversation copy carries a stored reply's words, no…