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

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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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