Skip to content

chat: the folded thinking row's preview can be a fence line or a broken glyph #1642

Closed

Description

@HugoXK

Implementation: FlyBot (ours) — PR follows within 24h. Do not pick up without a pickup-ok comment on this issue.

Written-by: FlyBot Lead
Claimed-by: FlyBot Lead
Claim-Id: 7d03bc2618bf475f8bc75c29fb62c2e1
Branch: dev/1642-chat-the-folded-thinking-row-s-preview-c
Base: 1ed614a
At: 2026-10-29T05:29:22Z

Implementation: FlyBot (ours) — PR follows within 24h. Do not pick up without a pickup-ok comment.

The folded thinking row's one-line preview can end up saying nothing (```js) or ending in a broken glyph — it is the row's only text in the folded state, so it has to be worth reading.

Written-by: FlyBot Lead

Current behaviour (main 1ed614a7)

core/src/client/cells.js:

const PEEK_MAX = 160
const peekLine = (text) => {
  const line = text.split('\n').map((part) => part.replace(/\s+/g, ' ').trim()).find((part) => part !== '')
  return line === undefined ? '' : line.slice(0, PEEK_MAX)
}

Three observations from the real renderer:

  1. A fence line is taken as the preview. Reasoning ```js\nconst x = 1\n``` gives peek = "```js" — markup, not what the Bot was thinking.
  2. Truncation cuts UTF-16 code units. 'a'.repeat(159) + '😀' + ' tail' gives length 160 ending in the lone surrogate d83d, which renders as U+FFFD.
  3. Truncation is silent. A clipped line has no ellipsis, so the reader cannot tell it is partial.

What to build

Make the folded preview honest:

  • skip lines that are only markup: a fence (```, ~~~, with or without a language) and horizontal rules (---, ***, ___);
  • take the first line that has prose after that filtering;
  • truncate on a grapheme boundary (never inside a surrogate pair), and append an ellipsis character when the line was cut;
  • when there is no prose line at all, fall back to a short neutral summary (e.g. the line count) instead of a fence fragment or an empty row — an empty frame stays forbidden, which is what #1624 fixed;
  • key/string: if the fallback needs a word, use the existing translation table and add en + zh entries rather than hard-coding one language.

Out of scope: the row's structure, the open/closed caret behaviour, turn-reasoning.js, and the #1636 open-state rule.

Acceptance

  • the two inputs above give a meaningful preview and a complete emoji boundary; a cut line ends with the ellipsis;
  • no-prose input (only fences/rules) gives the neutral summary, never a bare fence fragment and never an empty row;
  • existing core/test/thinking-row.test.js cases stay green (they pin the folded row and the open state), plus three new cases: fence, emoji boundary, no-prose fallback;
  • TZ=UTC ARCH_DOCS_BASE=<live main> npm run verify exits 0, ds-bot-patches check clean, generated artifacts regenerated only if the source change needs it.

Activity

  1. HugoXK commented on Oct 29, 2026

    @HugoXK
    ContributorAuthor

    Release

    Written-by: FlyBot Lead
    Released: 7d03bc2618bf475f8bc75c29fb62c2e1
    Reason: Released: merged as #1643 (merge 48fe6f4)
    At: 2026-10-29T06:42:48Z

  2. added 2 commits that reference this issue on Oct 29, 2026
    48fe6f4
    703bead
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

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions