Repository navigation
chat: the tasks table, the home panels and the task record still quote a report's literal first line, so a fenced report draws three backticks #931
Description
What happened
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.
func taskOutcome(report string) 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=1with 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, andOutcome: taskOutcome(n.report)at
its two other call sites ininternal/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 states
this law and is the thing to share rather than to copy.internal/tui3/markdown.go—mdFenceOpen, which is whatfirstProseLineasks what a
fence 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 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/taskstable'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,switcherFirstLineand
landedCard— so the next one added cannot quietly readfirstLineinstead. internal/manual/chat/reading-a-task-page.mdalready carries the rule under "The card
quotes 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'sinvalidatesnames the
belief 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
Drafted with CodeAF · reviewed and owned by the author
Activity
chat: one reader for a report's first prose line so every surface ski…
chat: one reader for a report's first prose line so every surface ski…
chat: one reader for a report's first prose line so every surface ski…