Skip to content

feat(site): the playground renders the answer - #16

Merged
lroolle merged 1 commit into
mainfrom
feat/playground-markdown
Aug 13, 2026
Merged

feat(site): the playground renders the answer#16
lroolle merged 1 commit into
mainfrom
feat/playground-markdown

Conversation

@lroolle

@lroolle lroolle commented Aug 13, 2026

Copy link
Copy Markdown
Member

What

The playground streamed the model's answer as plain preformatted text, so any markdown it wrote arrived raw. This renders it: headings, bold/italic, inline code, fenced code blocks, lists, links, blockquotes and tables.

Before: the answer was textContent - a wall of literal ##, **, and unformatted ``` fences.
After: rendered markdown inside the terminal-styled transcript, code blocks with copy buttons, a raw/rendered toggle, and a copy button on the whole answer. The command panel - the page's point - stays prominent and in sync.

Renderer: hand-rolled, not vendored

New site/md.js, ~200 lines, same dual-export shape as pow.js. No library: DESIGN.md's pitch is no build step and TASTE.md already rejected pulling in a dependency (three.js) that would sell the reasons the site is good. A markdown lib would be the same trade for the same reason.

Security (model output is hostile)

  • Every run of model text is HTML-escaped exactly once before it is emitted; no path passes raw model HTML through. <script>alert(1)</script> renders as inert &lt;script&gt;....
  • Link hrefs survive only if http(s) or mailto; anything with another scheme is dropped and the link renders as plain text, so [x](javascript:alert(1)) produces no javascript: href. The href value is escaped too, which neutralises entity-encoded and quote-breakout attempts.
  • Streaming re-renders the accumulated buffer per chunk; the renderer tolerates half-written markdown (unclosed fence, dangling *) without throwing.

Both security cases plus partial-stream inputs are unit-tested in site/md.test.js; playground.dom.test.js proves the page actually routes the answer through the renderer.

UX (restrained, in-contract)

Copy button on the answer and on each code block; raw/rendered toggle; usage/cost line unchanged (already the CLI's stderr line); reasoning still shown dim. All styled off the .term tokens so the transcript stays dark in both themes. No document-level horizontal scroll at any breakpoint. Web-search toggle and PoW enrol flow untouched.

One narrow fix surfaced by rendering: the site dresses its own h2/h3 with a ## / ### prefix as a design signature - which made a rendered answer heading read as markdown that failed to render. Suppressed for .pg-body only.

Verification

make check green. Verified in a real browser (CloakBrowser) at 1280px and 390px against the live free tier (freeseek.1lm.io, real PoW + real request; e.g. 139 in / 225 out / ~\$0.000082): markdown renders, code blocks carry a working copy button, the command panel reflects the configured request, no sideways scroll at 390px.

🤖 Generated with Claude Code

Model answers are markdown; render them. A small hand-rolled renderer
(site/md.js) does headings, bold/italic, inline code, fenced code blocks,
lists, links, blockquotes and tables in the codebase's vanilla, stdlib-only
style - no vendored library, per DESIGN.md/TASTE.md.

Model output is treated as hostile: every byte is HTML-escaped before it is
emitted, and link hrefs are dropped unless they are http(s) or mailto, so a
javascript: or data: URL never becomes an href. The streamed buffer is
re-rendered per chunk and the renderer tolerates half-written markdown
mid-stream without throwing.

UX, in-contract with the terminal palette: a copy button on each answer and
on every code block, a raw/rendered toggle so the exact markdown is one click
away, the reasoning stream still shown dim, and the command panel kept
prominent and in sync - it still shows the equivalent deepseek invocation for
whatever is configured.

Tests: md.test.js unit-tests the renderer including the two security cases
(<script> renders inert, a javascript: link produces no javascript: href) and
partial-stream inputs; playground.dom.test.js proves the page routes the
answer through the renderer. make check is green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@lroolle
lroolle merged commit 58f710e into main Aug 13, 2026
8 checks passed
@lroolle
lroolle deleted the feat/playground-markdown branch August 13, 2026 07:04
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