Readable assistant answers: GFM markdown and presentation blocks
Owner priority (2026-10-13). An answer's table rendered on ONE line ("| Type | Count | |---|---| | JPEG ... | 81 | ..."), and the reply said "You can open the lists filtered by JPEG or PNG" with no links.
Root cause
AssistantMarkdown rendered react-markdownwithout remark-gfm. GFM tables never parsed, so their lines collapsed into one paragraph through soft line breaks. The backend keeps \n: events are JSON, and the turn's text buffer is untouched.
Fix
Markdown:
remark-gfm and remark-breaks are added (deps remark-gfm@4.0.1, remark-breaks@4.0.0).
The sanitize schema and the link and image rules are unchanged.
Tables are styled for light and dark, and numeric columns align right.
SSE data: fields that span several lines keep their newlines.
Presentation blocks: a new presentation message part and SSE event, following the qbit-assistant contract (the server side is the companion qbit-assistant PR).
summary: the one-line answer first.
metrics: big-number tiles.
table: uses the server-formatted display values, with per-row links.
chart: bar, pie or line with recharts, lazy-loaded. It is accessible: an aria summary and a hidden data list.
filterLinks and recordLinks.
actions: a prompt sends the next message; an href navigates.
Hrefs become links only when internalAppHref accepts them (same-origin /app), the same rule as markdown links.
Unknown kinds are skipped. A part with no renderable blocks falls back to its markdown.
Storybook: the owner's example, built from blocks.
Tests
The owner's exact answer text:
it renders a 2-column table with 3 rows;
lists, headings and line breaks render;
splitting the text across many text.delta events gives the same table.
Blocks: every block kind; href safety (javascript:, protocol-relative and external hrefs never become links); action prompts; unknown kinds; the reducer presentation event.
Results:pnpm typecheck passes and pnpm lint has 0 errors. The assistant and API suites pass (371 tests). In the full run, two standalone-build tests (tests/standalone/acceptance-stage, tests/acceptance/support/standalone-variant) fail on Windows on main too; that is environment-only, and CI is the gate.
Bundle
+44.7 KiB gzip (+174 KiB raw) across compiled chunks, mostly recharts, which loads only when a chart block renders. Size limits are report-only.
Screenshots of the owner's example (light and dark, 390 and 1440 px) follow once the qbit side lands, from the demo server.
## Readable assistant answers: GFM markdown and presentation blocks
**Owner priority (2026-10-13).** An answer's table rendered on ONE line ("| Type | Count | |---|---| | JPEG ... | 81 | ..."), and the reply said "You can open the lists filtered by JPEG or PNG" with no links.
### Root cause
`AssistantMarkdown` rendered `react-markdown` **without `remark-gfm`**. GFM tables never parsed, so their lines collapsed into one paragraph through soft line breaks. The backend keeps `\n`: events are JSON, and the turn's text buffer is untouched.
### Fix
- **Markdown:**
- `remark-gfm` and `remark-breaks` are added (deps `remark-gfm@4.0.1`, `remark-breaks@4.0.0`).
- The sanitize schema and the link and image rules are unchanged.
- Tables are styled for light and dark, and numeric columns align right.
- SSE `data:` fields that span several lines keep their newlines.
- **Presentation blocks:** a new `presentation` message part and SSE event, following the qbit-assistant contract (the server side is the companion qbit-assistant PR).
- `summary`: the one-line answer first.
- `metrics`: big-number tiles.
- `table`: uses the server-formatted `display` values, with per-row links.
- `chart`: bar, pie or line with recharts, **lazy-loaded**. It is accessible: an aria summary and a hidden data list.
- `filterLinks` and `recordLinks`.
- `actions`: a `prompt` sends the next message; an `href` navigates.
- Hrefs become links only when `internalAppHref` accepts them (same-origin `/app`), the same rule as markdown links.
- Unknown kinds are skipped. A part with no renderable blocks falls back to its `markdown`.
- **Storybook:** the owner's example, built from blocks.
### Tests
- **The owner's exact answer text:**
- it renders a 2-column table with 3 rows;
- lists, headings and line breaks render;
- splitting the text across many `text.delta` events gives the same table.
- **Blocks:** every block kind; href safety (`javascript:`, protocol-relative and external hrefs never become links); action prompts; unknown kinds; the reducer `presentation` event.
- **Results:** `pnpm typecheck` passes and `pnpm lint` has 0 errors. The assistant and API suites pass (371 tests). In the full run, two standalone-build tests (`tests/standalone/acceptance-stage`, `tests/acceptance/support/standalone-variant`) fail on Windows on `main` too; that is environment-only, and CI is the gate.
### Bundle
+44.7 KiB gzip (+174 KiB raw) across compiled chunks, mostly recharts, which loads only when a chart block renders. Size limits are report-only.
Screenshots of the owner's example (light and dark, 390 and 1440 px) follow once the qbit side lands, from the demo server.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Readable assistant answers: GFM markdown and presentation blocks
Owner priority (2026-10-13). An answer's table rendered on ONE line ("| Type | Count | |---|---| | JPEG ... | 81 | ..."), and the reply said "You can open the lists filtered by JPEG or PNG" with no links.
Root cause
AssistantMarkdownrenderedreact-markdownwithoutremark-gfm. GFM tables never parsed, so their lines collapsed into one paragraph through soft line breaks. The backend keeps\n: events are JSON, and the turn's text buffer is untouched.Fix
remark-gfmandremark-breaksare added (depsremark-gfm@4.0.1,remark-breaks@4.0.0).data:fields that span several lines keep their newlines.presentationmessage part and SSE event, following the qbit-assistant contract (the server side is the companion qbit-assistant PR).summary: the one-line answer first.metrics: big-number tiles.table: uses the server-formatteddisplayvalues, with per-row links.chart: bar, pie or line with recharts, lazy-loaded. It is accessible: an aria summary and a hidden data list.filterLinksandrecordLinks.actions: apromptsends the next message; anhrefnavigates.internalAppHrefaccepts them (same-origin/app), the same rule as markdown links.markdown.Tests
text.deltaevents gives the same table.javascript:, protocol-relative and external hrefs never become links); action prompts; unknown kinds; the reducerpresentationevent.pnpm typecheckpasses andpnpm linthas 0 errors. The assistant and API suites pass (371 tests). In the full run, two standalone-build tests (tests/standalone/acceptance-stage,tests/acceptance/support/standalone-variant) fail on Windows onmaintoo; that is environment-only, and CI is the gate.Bundle
+44.7 KiB gzip (+174 KiB raw) across compiled chunks, mostly recharts, which loads only when a chart block renders. Size limits are report-only.
Screenshots of the owner's example (light and dark, 390 and 1440 px) follow once the qbit side lands, from the demo server.
🤖 Generated with Claude Code