Files
DongsenandGitHub 8e6250f2c1 feat(web): render GFM tables in markdown artifact and chat renderers (#1496)
* feat(web): render GFM pipe tables in markdown artifact and chat renderers

Both hand-rolled markdown parsers (artifact preview and assistant chat)
ignored pipe-table syntax, so GFM tables collapsed into a single
paragraph. Add a table block to each parser that:

- detects header+alignment+body rows;
- honors :--- / :---: / ---: column alignment;
- preserves inline formatting (code, bold, italic, links) inside cells;
- restores escaped \| as a literal pipe;
- breaks a preceding paragraph at a table start without a blank line.

Closes #1495

* fix(web): use scan-based table cell splitter, drop NUL placeholder

Address review on #1496:

- lefarcen (P1): the placeholder approach in both splitters was unsafe.
  artifacts/markdown.ts embedded literal NUL bytes around 'ODPIPE', which
  flipped the whole file to binary in GitHub diffs and hid the patch from
  review. runtime/markdown.tsx used a printable ' ODPIPE ' sentinel that
  could collide with real cell content.
- codex (P2): both splitters ran before inline parsing, so a literal '|'
  inside a backtick code span (e.g. a TypeScript union cell like
  \`\"ready\" | \"done\"\`) was treated as a column boundary and shredded
  the row.

Both issues collapse to one root cause — splitting by regex on '|'
without knowing what's a code span and what's an escape. Replace the
placeholder-then-split routine in both files with a single character-
level state-machine scanner that:

- treats '|' inside backticks as cell content (tracks inCode);
- resolves '\\|' to a literal '|' as it accumulates each cell;
- strips a single optional leading '|' and a single unescaped trailing
  '|' as row terminators rather than empty cells.

No placeholders, no NUL bytes, no collision surface. Add a test in each
test file covering the GFM-style pipe-inside-code-span cell, and the
runtime test additionally asserts the row produces exactly two <td>
cells (i.e. no phantom column from the embedded pipe).

* style(web): make markdown table apply in artifact viewer + add header bg, borders

The prior `.md-table` rules were scoped to `.prose-block`, which only
wraps the chat assistant path (AssistantMessage). The artifact preview
in FileViewer renders into `.markdown-rendered`, so tables in long-form
documents (the most common case) inherited no styling and showed as
borderless, structureless rows.

Lift the rule out of `.prose-block` so both paths share it. Visual
treatment matches the existing prose-block ecosystem (blockquote,
markdown-status):

- `var(--border)` for cell + outer borders, 6px outer radius.
- `var(--bg-panel)` on `<th>` for the header band — same token already
  used by blockquote and code-copy chip.
- Even-row zebra via `color-mix(in oklab, var(--bg-panel) 40%, transparent)`
  for a very faint stripe that holds up in both light and dark themes
  (no hardcoded color).
- Padding bumped from 4×8 to 8×12; cells were touching before.
- `display: block; overflow-x: auto` kept so wide tables scroll inside
  narrow columns instead of pushing layout.

* fix(web): wrap md-table in scroll container so columns fill width

Previously `.md-table` itself was `display: block; overflow-x: auto`
to let wide tables scroll horizontally. The side effect: a block-mode
table no longer behaves like `<table>` for layout, so columns collapse
to natural content width and leave empty space on the right when the
content is narrower than the container.

Split the two concerns: the new `.md-table-wrap` <div> owns the scroll
viewport (border, radius, overflow-x), and the inner `<table>` keeps
its native `display: table` with `width: 100%` so column widths
distribute across the available space again. Wide tables still scroll;
narrow tables now fill the container.

Both render paths (chat runtime + artifact HTML) emit the wrapper, and
the corresponding unit tests assert the nested shape.
2026-05-13 21:15:23 +08:00
..