You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
2026-09-11, dev@eaa3d69de. #889 fixed the landed card: its quoted half is now the
report's first line that says something, so a report opening with a code fence no longer
draws ```. The same report still draws three backticks everywhere else it is quoted,
because the card is not the only road.
internal/session's taskOutcome is a second, independent reading of the same field:
// taskOutcome is the report's first sentence, capped.functaskOutcome(reportstring) string {
line:=strings.TrimSpace(firstLine(report))
...
}
It fills TaskIndexEntry.Outcome, which is what the tasks table, the home panels and the
task record all draw. So a task whose report opens with a fence lands with a correct card
and a row on /tasks reading ```.
Replication
Deterministic (no model).internal/session — a pure function:
go test ./internal/session -run TestTaskOutcomeSkipsAFence -count=1
with a report of
the seven files are written and the suite passes
Expected today:taskOutcome returns three backticks. Expected after the fix: it returns the seven files are written and the suite passes.
The same value reaches the surface through internal/tui3's taskFirstSentence
(taskstable.go) and switcherFirstLine (switcher.go), neither of which can recover it —
by then the whole value IS the fence marker.
Field (real models).OPENROUTER_API_KEY, the chat surface, any /task or quick task
whose worker answers with a fenced block first. Land it, then open /tasks and the home
page. One run, cents.
Where
internal/session/task_index.go — taskOutcome, and Outcome: taskOutcome(n.report) at
its two other call sites in internal/session/orchestrate.go.
One reader, not two. The law is already written down in firstProseLine: a blank line
says nothing, a fence marker says nothing, and the first line that is neither is what the
work came to. What is missing is a home both packages can reach — mdFenceOpen is private
to internal/tui3 and taskOutcome lives in internal/session, so today neither can call
the other.
Copying the loop into internal/session is explicitly the wrong fix: two opinions about
what a fence is drift, and #889 removed exactly that shape from internal/tui3. Lift the
fence-marker reading and the first-prose-line reading into one small shared package and have
both callers ask it.
No person-facing wording changes: a row that quoted a sentence still quotes the same
sentence, and a row that had nothing to quote still draws nothing rather than empty
quotation marks.
Acceptance
e2e:go test -tags e2e ./internal/e2e/ — a task whose report opens with a fence
lands, and the /tasks table's row for it shows the prose sentence; the screen never
contains a quoted run of three backticks. The control: a report that opens with prose
draws exactly the row it draws today.
e2e: the home page's work band for the same task shows the prose sentence, not the
marker.
Unit: one table in the shared package pins the law (leading fence, fence with a
language word, tilde fence, blank lines, fence-only report, empty report), and a
structural law test asserts that every road that quotes a report's first line goes
through it — taskOutcome, taskFirstSentence, switcherFirstLine and landedCard — so the next one added cannot quietly read firstLine instead.
What happened
2026-09-11,
dev@eaa3d69de. #889 fixed the landed card: its quoted half is now thereport's first line that says something, so a report opening with a code fence no longer
draws
```. The same report still draws three backticks everywhere else it is quoted,because the card is not the only road.
internal/session'staskOutcomeis a second, independent reading of the same field:It fills
TaskIndexEntry.Outcome, which is what the tasks table, the home panels and thetask record all draw. So a task whose report opens with a fence lands with a correct card
and a row on
/tasksreading```.Replication
Deterministic (no model).
internal/session— a pure function:go test ./internal/session -run TestTaskOutcomeSkipsAFence -count=1with a report of
the seven files are written and the suite passes
Expected today:
taskOutcomereturns three backticks.Expected after the fix: it returns
the seven files are written and the suite passes.The same value reaches the surface through
internal/tui3'staskFirstSentence(
taskstable.go) andswitcherFirstLine(switcher.go), neither of which can recover it —by then the whole value IS the fence marker.
Field (real models).
OPENROUTER_API_KEY, the chat surface, any/taskor quick taskwhose worker answers with a fenced block first. Land it, then open
/tasksand the homepage. One run, cents.
Where
internal/session/task_index.go—taskOutcome, andOutcome: taskOutcome(n.report)atits two other call sites in
internal/session/orchestrate.go.internal/tui3/render.go—firstProseLine, the reader chat: a task card quotes the report's first line literally, so a report that opens with a code fence draws"`"as its outcome #889 added, which already statesthis law and is the thing to share rather than to copy.
internal/tui3/markdown.go—mdFenceOpen, which is whatfirstProseLineasks what afence is.
internal/tui3/taskstable.go—taskFirstSentence;internal/tui3/switcher.go—switcherFirstLine.The fix
One reader, not two. The law is already written down in
firstProseLine: a blank linesays nothing, a fence marker says nothing, and the first line that is neither is what the
work came to. What is missing is a home both packages can reach —
mdFenceOpenis privateto
internal/tui3andtaskOutcomelives ininternal/session, so today neither can callthe other.
Copying the loop into
internal/sessionis explicitly the wrong fix: two opinions aboutwhat a fence is drift, and #889 removed exactly that shape from
internal/tui3. Lift thefence-marker reading and the first-prose-line reading into one small shared package and have
both callers ask it.
No person-facing wording changes: a row that quoted a sentence still quotes the same
sentence, and a row that had nothing to quote still draws nothing rather than empty
quotation marks.
Acceptance
go test -tags e2e ./internal/e2e/— a task whose report opens with a fencelands, and the
/taskstable's row for it shows the prose sentence; the screen nevercontains a quoted run of three backticks. The control: a report that opens with prose
draws exactly the row it draws today.
marker.
language word, tilde fence, blank lines, fence-only report, empty report), and a
structural law test asserts that every road that quotes a report's first line goes
through it —
taskOutcome,taskFirstSentence,switcherFirstLineandlandedCard— so the next one added cannot quietly readfirstLineinstead.internal/manual/chat/reading-a-task-page.mdalready carries the rule under "The cardquotes three backticks instead of a sentence"; it must stop being about the card alone
and say it of every row that quotes a report. The change entry's
invalidatesnames thebelief that chat: a task card quotes the report's first line literally, so a report that opens with a code fence draws
"`"as its outcome #889 fixed this everywhere.Follow-up to #889 (PR #930), which fixed the landed card and reported this seam rather than
crossing it.
🤖 Generated with Claude Code