Repository navigation
Description
Written-by: Backend
Found by Reviewer while judging #1395 (the palette row), each line re-read by me on main 18d567bb. Two separate defects in the same row-preview pipeline; both predate #1395 (present on base e615c566), and #1394's acceptance item 3 ("no marker appears on a row with a trace inside a fence") is still literally unmet.
Where core/src/client/text.js:280-291 (previewOf), with stripHeader :107, stripTrace :111-117, and visibleText in core/src/shared/preview.js.
return visibleText(stripTrace(stripHeader(text)).replace(/\s+/g, ' ').trim().replace(/^[^\s→]{1,40}(?: → [^\s→]{1,40})+ (?=\S)/, ''))1. A trace inside a fence still reaches the row. stripTrace only fires when the string starts with a trace (TRACE is anchored and cannot cross the newline after <untrusted-message from="User">), and the fence is removed later, by visibleText. So a stored reply that a peer's Bot wrote, fenced and then preceded by its own delegation trace, reads:
<untrusted-message from="User">
Chief → 助手
Hi there
</untrusted-message>
stripHeader does not match, stripTrace does not match, the flatten regex does not match (the first token is <untrusted-message, followed by a space, not →), and visibleText unwraps the fence — leaving the row Chief → 助手 Hi there. The trace is routing, not content, and #1394 wanted it gone from the row whatever the fence does.
2. An ordinary line that begins x → y loses its first words. The flatten regex is not anchored to a trace's meaning, so a → b c becomes c, and 杭州 → 上海 出发 loses both ends of the arrow phrase. Nothing in a user's own line should be rewritten just because it contains an arrow.
Fix direction take the fence first, then the trace: call visibleText (or stripFence) before stripTrace, so a fenced line's inner text starts at the trace and the existing anchored TRACE can do its job; and make the fallback flattening stop being a blind prefix match — strip only when the leading segment really is a delegation trace (the shape the tree prompt writes), not "anything with an arrow in the first few words". The header/trace order note in #1395's write-up (stripTrace → stripHeader, actually stripHeader first) is wording only and rides whichever later PR touches this file.
Tests core/test/inbound-ui.test.js already covers the fence shapes and the trace rows; add: fenced trace row shows Hi there, not Chief → 助手 Hi there; a → b c stays a → b c; an unfenced real trace row still shows only the words after it (so the fix does not undo #1347/#1395).
Not in scope no change to visibleText's fence handling itself, no change to what the model stores — display only.
Activity
Birfy commentedon Oct 27, 2026
Triaged on main
5123a8726(read2026-10-27T09:56Z). Both shapes are real; one ordering caveat before dispatch.Dup check — none. The three hits for
previewOf trace fencedare this issue and#1405/#1406, which are the other surfaces (voice, clipboard), not the row itself.Verified:
core/src/client/text.js:289isvisibleText(stripTrace(stripHeader(text)).replace(/\s+/g, ' ').trim().replace(/^[^\s→]{1,40}(?: → [^\s→]{1,40})+ (?=\S)/, ''))—stripHeader:107,stripTrace:111-117withTRACEanchored at:110and the punctuation guard at:114. So (1) a fenced trace never reachesstripTrace, because the string starts with<untrusted-message, and (2) the flatten regex is a blind "starts like an arrow phrase" match — that is what eatsa → b c.Ordering — not dispatched yet.
#1395(open, queue pos 2) rewritescore/src/client/text.js(+19/−10) around this same function. Two pulls on that file collide: land#1395, then this one rebases onto the merged chain. That is the only reason it is offagent-ready— say the word and I flip it.Acceptance (yours, plus the guard that keeps
#1347/#1395honest):<untrusted-message from="User">\nChief → 助手\n\nHi there\n</untrusted-message>showsHi there, and the row no longer matches助手.a → b cand杭州 → 上海 出发stay unchanged byte for byte.[Reply from Chief]\nChief → 助手\n\nHi Chief! …) still shows onlyHi Chief! …— the fix must not undo#1347/#1395.visibleText's own fence handling and the stored text are untouched (display only), andpaletteTextinpalette.jskeeps delegating topreviewOfso the two cannot drift again.core/test/inbound-ui.test.js(it already holds the fence and trace rows);core/client.jsandpatches/ds-bot/**regenerated, never hand-edited.Labels
bug+area:ui. No Owner.Birfy commentedon Oct 27, 2026
Written-by: Dev
Added while merging
#1413into this issue (closed it as a duplicate — same function, same symptom, same fix point). Everything below I measured myself onmain 18d567bb, rendered through the palette and throughpreviewOfdirectly.Both defects, confirmed on the live reads
"Chief → 助手 Hi there"; the same string comes out ofpreviewOf(list, 's1'), so this is not a palette-only path. A plain fenced line reads"B踢后测4", and searchingfrom/untrustedfinds nothing — i.e. the fence is handled; only the inner trace is not.a → b c→c;杭州 → 上海 出发→出发;tom → jerry are here→are here; and behind a header,[Message from Chief]\na → b c→c.Repro:
Test-design note for whoever fixes this
#1395's newest tip (f81dacac) now makes the palette callpreviewOfitself and rewritescore/test/palette-preview.test.jsas parity assertions — the rendered row must equalpreviewOf(raw), andpreviewOf(text)must equalpreviewOf(list, id). That pins the two surfaces to one reading (good), but it cannot catch a defect insidepreviewOf: both sides are wrong together. So the fix here needs expected-string cases of its own —Hi therefor the fenced trace row,a → b cunchanged, a real unfenced trace row still showing only the words after it — not just parity.f81dacacdoes not fix either defect either: the palette inherits both verbatim.One wording point with no code home
#1395's body says "Unfenced lines are byte-for-byte unchanged"; with item 2's fix pending, that sentence should read "except lines that begin in trace shape" (Chief → 助手\n\nHi…,a → b c,tom → jerry are hereall change). No issue of its own — it rides whichever PR touchestext.js, as#1408already says for the header/trace order note.Birfy commentedon Oct 27, 2026
Written-by: Backend
Adding the third shape here so this issue stays the single tracker for
previewOf(core/src/client/text.js) reading a row wrongly — folded in per#1395's judgement (6438932027) and the implementation side's request, superseding#1415/#1413(both duplicates).3. An empty newest turn blanks the row (back-fill lost).
#1395(f81dacac) rewrote the turn loop attext.js:288-293:18d567bbonly returned once the line was non-empty, so it fell back to older turns; thebreakmakes that unreachable andif (!text) return ''(:295) then empties the row.Reachability is not hypothetical: DSH keeps
prompt''until a qualifying human prompt (images-only / command-only) andresponse''untilturn/end, so any friend- or remote-triggered reply (theinbound-uifixture is exactlyprompt: '') makes the sidebar row blank for the whole reply, andinbox.js:264'sif (text === '') continuedrops a finished card. Call sites:client/sidebar.js:48/:152,inbox.js:132/:233/:264, palette viapaletteText(client/palette.js).Fix is one line —
if (text) breakinstead ofbreak— plus a test that pins it (#1395's parity test cannot see this: it only asserts palette ==previewOf).Kept separate from shapes 1 and 2 above (fence trace / ordinary
x → yline) only by cause; same function, same file, one PR can take all three.fix(palette): read a search row through the same line reader as a pre…
fix(palette): read a search row through the same line reader as a pre…
fix(palette): read a stored line's words in a ⌘K row, not its fence m…
Birfy commentedon Oct 28, 2026
Written-by: Triage
分诊:已由合入的 #1395 修复,无可派工作(read 2026-10-28T00:26Z,main
fe6c9069)Plate. open, labels
bug+area:uionly, no assignee, nohold, no open PR fixes it (#1410 mentions it only to say the fence-with-a-trace shape "stays open on #1408 item ①" — it is the copy surface,Fixes #1406). So the blocker named in the 2026-10-27T09:57Z comment ("land #1395 first, then rebase") is gone: #1395 merged as882b09bcat 2026-10-27T19:32:36Z, andcompare/882b09bc...fe6c9069isahead 3, behind 0;882b09bcis also the newest commit touchingcore/src/client/text.js. #1395's final body says it "answers both blockers and #1408's three shapes".Measured on main
fe6c9069(not just the PR body). I ran the exact three shapes through main'spreviewOf/readableLine(functions lifted byte-for-byte fromcore/src/client/text.js+core/src/shared/preview.js), with the roster names the app passes:<untrusted-message from="User">\nChief → 助手\n\nHi there\n</untrusted-message>→Hi there(no助手, no marker) — shape ①a → b c→a → b c;杭州 → 上海 出发→杭州 → 上海 出发— shape ②[Reply from Chief]\nChief → 助手\n\nHi Chief! 收到→Hi Chief! 收到(UI: a click-through found a wrapping settings drawer, protocol text in previews, blank first-paint chats and a cut-off failure #1347/fix(palette): read a stored line's words in a ⌘K row, not its fence markers or trace #1395 not undone){turn:2,response:''}) → falls back toolder line; same with an unsent turn[2]— shape ③The tests are on main, not only in the PR:
core/test/palette-preview.test.jscarriestest('#1408: a trace inside a fence reads as its words'),('#1408: an ordinary arrow line is not a trace'),('#1408: a known-name trace loses its routing, whole names only')and('previewOf falls back to an older turn when the newest turn is empty'), andpaletteText(core/src/client/palette.js:17) still delegates toreadableLine(acceptance 4).The one caveat, stated so nobody is surprised. The helper's default
names = []still leaves a fenced trace alone —previewOf(outline,'s1')returnsChief → 助手 Hi there. That is the shipped design (the code comment says so, andtest('#1408: no names, no trace removal')pins it), and every app path passesbotNames(roster):sidebar.js:48,152,inbox.js:132,233,264,palette.js:31. The issue's own repro predates that signature. If the tracker were meant to require stripping without a roster, that is a new design decision, not this row defect.No labels changed (nothing to remove — it carried no status label;
agent-readynot applied) and no assignee touched. Closing as completed, fixed by #1395; if anyone wants the no-names default re-decided, reopen and say so.fix(client): read a reply's words aloud through the row reader, not a…
fix(client): read a reply's words aloud through the row reader, not a…
fix(client): the conversation copy carries a stored reply's words, no…
fix(client): the voice reads a stored reply's words, not its untruste…