Skip to content

/view: render HTML natively, HTML files and HTML inside Markdown (#454) - #455

Merged
raiseCatError merged 3 commits into
release/v0.22.0from
feature/v022-view-html
Oct 19, 2026
Merged

raiseCatError merged 3 commits into
release/v0.22.0from
feature/v022-view-html

Conversation

@raiseCatError

Copy link
Copy Markdown
Owner

Implements #454 (QA F01). Kept separate from the security fixes, with its own tests.

What renders: HTML is mapped onto the Markdown renderer's own blocks, so wrapping, narrow widths, search, NO_COLOR and Safe glyphs come from the tested renderer. There's no browser engine and no DOM.

  • Blocks: headings, paragraphs and containers (div, section, center, details/summary, figure); ordered and unordered lists (nested, task check boxes); tables (header row, alignment); <pre> code with its language class; block quotes; rules; definition lists.
  • Inline: emphasis, <code>/<kbd>, strikethrough, links and images.
  • In Markdown: HTML blocks follow CommonMark's kinds, and Markdown between them stays Markdown, as on GitHub. Inline tags come from a whitelist; an unknown <word> stays text.
  • Files: .html, .htm and .xhtml open rendered; m shows the source.

Never run or loaded

  • Scripts, styles, templates, embedded and interactive content and <head> are dropped with their contents.
  • Handlers and all but a few attributes are ignored, and CSS isn't applied.
  • An image is its alt text; nothing is fetched or read, so no local file is disclosed.
  • Links keep the renderer's http(s)-only rule, and a refused target shows as text.
  • Character references use the existing safe decoder (moved unchanged into markup.ts).
  • The parser is linear and bounded: 200k tokens, 48 levels deep (deeper content stays, flattened), 4 KiB per tag, 2 KiB per attribute, under the existing 1 MiB render limit. html.ts imports nothing that reads files or the network.

Opt-in: HTML renders only in /view. Agent replies, help and GitHub bodies still show tags as text.

Also fixed here (separate commit): the Markdown wrap glued two words together when the second fit only without the space ("A calmerterminal"). It's present on 68c1cbb7 in every Markdown surface.

Not included: inline image graphics (kitty/iTerm). They would mean reading local files or fetching remote ones, so they need an explicit opt-in design; images show as text for now.

Tests: tests/viewHtml.test.ts covers the README case, document structure, hostile markup, controls through text/entities/attributes, size, nesting and linearity bounds, no I/O imports, the opt-in boundary, narrow/NO_COLOR/Safe, and viewer integration. Plus a wrap regression in markdownRender. Rendering, viewer, agent, Ask and report suites: 604 pass, 0 fail. The project's own README renders with no literal tags left.

Physical retest: A2 (/view) and a short HTML walkthrough; see #444.

A space was dropped when the space and the next word did not fit, but the
next word itself was then measured without it: when the word alone fit
exactly, it was drawn straight after the previous one ("aaaa bbbb" at 8
columns gave "aaaabbbb"; a README line gave "A calmerterminal"). Every
Markdown surface used this wrap (agent replies, help, /view, GitHub
bodies). The next word now starts the next row.

Found while testing /view's HTML rendering (QA F01); present on 68c1cbb.
…F01)

Requested in the v0.22 physical QA: README files showed their embedded
<picture>, <img> and <div> literally. /view now renders HTML as terminal
rows, with no browser engine, through the Markdown renderer's own blocks,
so wrapping, narrow widths, search, NO_COLOR and Safe glyphs come from
the tested renderer.

Supported subset: headings, paragraphs and containers (div, section,
center, details/summary, figure), ordered and unordered lists (nested,
task check boxes), tables (header row, alignment), <pre> code with its
language class, block quotes, rules, definition lists; inline emphasis,
code and keys, strikethrough, links and images. In Markdown, HTML blocks
follow CommonMark's kinds and Markdown between them reads as Markdown,
as on GitHub; inline tags are read from a whitelist, and an unknown
<word> stays text. .html/.htm/.xhtml open rendered; m shows the source.

Never run or loaded: scripts, styles, templates, embedded and interactive
content and the document head are dropped with their contents; event
handlers and every attribute but a few are ignored; CSS is not applied;
an image is its alt text (never fetched or read, so no local file is
disclosed); links keep the renderer's http(s)-only rule and a refused
target shows as text; character references decode through the existing
safe decoder (moved to markup.ts unchanged). The parser is linear, at
most 200,000 tokens and 48 levels deep (deeper content stays, flattened),
4 KiB per tag, 2 KiB per attribute; rendering stays under the existing
1 MiB limit. html.ts imports nothing that reads files or the network.

HTML is rendered only where /view asks (MarkdownOptions.html): agent
replies, help and GitHub bodies keep showing tags as text.

Tests: tests/viewHtml.test.ts (README, document structure, hostile
markup, controls via text/entities/attributes, size and nesting bounds,
no I/O imports, opt-in, narrow/NO_COLOR/Safe, viewer integration).
Rendering, viewer, agent, Ask and report suites: 604 pass, 0 fail.
…document (QA F01)

Self-review of the HTML reader: a quoted attribute was searched for its
closing quote past the tag's own bound, and in Markdown each unclosed
<code> searched to the end again (a 64 KiB paragraph of them took 125 ms,
so a 1 MiB document could take seconds to open). The quote search now
stops at the tag's bound, and close-tag searches remember their answer
per name, so they only move forward. Worst cases measured: 14 ms per
64 KiB paragraph, about 300 ms per MiB of HTML; covered in the limits test.
@raiseCatError

Copy link
Copy Markdown
Owner Author

Integrated into release/v0.22.0 at fa7ec665 (merge commits, in the order #448, #449, #456, #447, #451, #453, #455). Dispatched CI on that exact head: all 13 jobs green (run 39257608055).

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