Skip to content

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

Closed

Description

@santoshkumarradha

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=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

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 /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.
  • internal/manual/chat/reading-a-task-page.md already 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's invalidates names 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:chatThe v3 surface a person sits in front of (internal/tui3)bugSomething the code does that it should notgood first issueSmall and isolated, the acceptance already written, no ruling owed; a safe first pull requestsev:papercutA wording, a hint, a small wrongness that costs a moment

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions