Skip to content

Inbox: show a markdown choice label's words, not its markdown, in folded lines and answer rows - #2219

Open
backnotprop wants to merge 8 commits into
mainfrom
fix/inbox-choice-link-labels
Open

backnotprop wants to merge 8 commits into
mainfrom
fix/inbox-choice-link-labels

Conversation

@backnotprop

@backnotprop backnotprop commented Oct 26, 2026 •

Copy link
Copy Markdown
Owner

Problem

An Inbox question's choice label is the agent's inline markdown, for example - [ ] [15% off](forecast-15.html). The live question card renders it as markdown, and the link opens the attached file. Every other place that shows a pick as plain text showed the raw markdown instead: [15% off](forecast-15.html), **Email**, `push`. This was seen in a 0.28.31 Inbox capture.

Fix

These surfaces now show the label's words ("15% off"). Each one is a plain-text line or row, and most sit inside a button, so a link cannot go there. The new helper plainLabel in packages/inbox/folding.ts produces the words. It reuses cleanInline and matches what the live card shows:

  • <…> text stays (Array<string>, Result<T, E>). cleanInline drops tags only without keepTags, so prompts and message bodies read as before.
  • A backslash escape reads as its character (\\*, \\+).
  • A link reads as its text, including one whose address holds parentheses or whose text holds brackets.
  • An autolink reads as its address.
  • A picture-only label reads as its alt text, or its file name when it has no alt text.
Surface Helper
Folded line, plus its screen-reader label and tooltip folding.ts answerWords
The answers a sent reply carried (SentAnswers) ReplacedQuestion.tsx pickWords (via sentReply.ts)
The answers that go with the reply being written (ReplyAnswers) pickWords
Open questions list, "Draft · …" pickWords
Replaced card, "Your pick: …" pickWords
Decision mode stamp, live region, confirm decisions/model.ts answerWords
Decision mode swipe hint decisions/model.ts swipeHint (a swipe still commits the raw label)

Only picked choice labels change. Other and free-text answers are the person's own words and still show exactly as typed.

Unchanged on purpose:

  • The live card. It already renders inline markdown, and its relative links already open the attachment in the viewer.
  • The text the agent receives. The reply body (composeInboxReplyBody, Answer: [15% off](forecast-15.html)) and MCP read_thread still carry each label exactly as written, so agents match answers to their choices as before. The fix touches display helpers only; stored answers and agent-facing output are untouched. The e2e checks both the reply body and read_thread.
  • Not in scope:
    • Search snippets (shared/inbox/search.ts).
    • The phone push payload's choices. That label is the identifier the phone sends back.
  • Board. It shows no question choices.

This keeps the change local to folding.ts, ReplacedQuestion.tsx and decisions/model.ts, so it merges cleanly if folding.ts moves to @plannotator/core.

Tests

  • Unit tests:
    • folding.test.ts: the folded snippet, snippetText and plainLabel.
    • decisionsModel.test.ts: decision mode answerWords. The stored answer keeps the label.
  • DOM test packages/inbox/choiceLabels.test.tsx, added to the test.yml DOM list. It covers the folded lines and their aria-labels, the sent answers, the live card's link and code, the open questions Draft, the reply-box answers, and the replaced card's pick. Its 5 cases cover 6 surfaces. Four fail on the old code; the live-card case passes on both, because it guards behavior that already worked.
  • e2e test tests/e2e/inbox-choice-labels.spec.ts, registered in the Playwright config. It runs against the compiled binary through plannotator inbox mcp. It covers the folded lines with their tooltip and aria-label, the sent answers, the live card link opening the attachment, the open questions Draft, the reply-box answers, the agent's reply body and read_thread, and a phone layout.

Run locally:

  • bun run typecheck: pass.
  • The CI DOM list (DOM_TESTS=1 bun test --isolate over the test.yml list): 2908 pass, 0 fail.
  • bun test packages/inbox packages/core/question-block.test.ts: 549 pass.
  • e2e on a dev build (__CLI_VERSION__="0.0.0-dev"): inbox-choice-labels, inbox-thread-folding, inbox-replaced, inbox-open-questions, inbox-decision-mode and inbox-picks-composing all pass, 30 tests.

…r rows

A choice label is the agent's inline markdown ([15% off](forecast-15.html),
inline code, bold). The live card draws it as markdown, but the folded line,
the answers a sent reply carried, the reply box's answers, the open questions
Draft, a replaced card's unsent pick and decision mode's stamp showed the raw
markdown. They now show the label's words (plainLabel). The reply the agent
reads and read_thread keep each label exactly as written.
Review of #2219: plainLabel no longer drops <...> text (Array<string>,
Result<T, E>), reads a backslash escape as its character (\*, \+), reads a
link whose address holds parentheses or whose text holds brackets as its
text, an autolink as its address, and a picture with no alt as its file
name. cleanInline drops tags only without keepTags, so prompts and bodies
read as before. Decision mode's swipe hint says the labels' words
(swipeHint); a swipe still commits the label as written.
# Conflicts:
#	packages/inbox/decisions/DecisionCard.tsx
#	tests/e2e/playwright.config.ts

This branch has not been deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant