feat(chat): render assistant markdown + collapse tool-call runs + copy-raw (ADR-0018) #14
No reviewers
Labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Cloonar/coding-lab!14
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "feat/chat-markdown-toolgroups-copy"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Implements #13 — three phone-first render enhancements to the embedded Chat (issue #7 / ADR-0016). Pure frontend + CSS: the message schema, provider seam, API, SSE, migrations and the transcript fold are all untouched, and no runtime npm dependency is added (deps stay
solid-js+@solidjs/router).What changed
web/src/lib/markdown.ts: a hand-rolled, zero-dep parser emitting a node tree the view maps to Solid JSX directly (neverinnerHTML, never a sanitizer) → XSS-safe by construction, link scheme allowlisthttp/https/mailto. Headings, bold/italic, inline + fenced code, bullet/ordered/nested lists, GFM tables, blockquotes, links + bare-URL autolink, hr. Applies to every text/thinking message, any role. Total: partially-streamed / malformed input degrades to visible text — never throws, never drops a character.web/src/lib/toolGroups.ts: render-time coalescing of 2+ consecutive tool calls into one "N tool calls" disclosure. Thinking folds in (uncounted); error rollup (· N failed); live· running…; count is tools-only. The outer group's open state is controlled, keyed by the first tool'sseq, so an expanded live group survives SSE refetches.navigator.clipboard.Verification
web/: 366 unit tests pass (28 files),tsc --noEmitclean,eslintclean,vite buildsucceeds.markdown.test.ts), grouping rules (toolGroups.test.ts), and RunChat coverage for markdown render, href-injection rejection, grouping/error-rollup/live-summary, controlled open-state survival across refetch, in-group thinking visibility, and copy actions.Out of scope (per issue #13)
Syntax highlighting · non-http autolink schemes · stacked-card table reflow · copy on user/thinking messages · rendering markdown in tool I/O · any backend/schema/API/SSE change.
Closes #13
🤖 Generated with Claude Code
Validation: PASS
Verification signal. CI (
ci / flake-check) has no runner on this instance and stays pending, so it cannot vouch. The diff is frontend-only (web/+ one ADR doc), so I ran the web slice of the gate directly:eslint .— cleanvitest run— 366/366 pass (28 files)tsc --noEmit+vite build— succeed(I did not run the full
nix flake check; its extra Go/store suites are unrelated to this frontend-only diff.)Diff review.
lib/markdown.ts— total parser: block scanner always advances,MAX_DEPTHcap, inline fallbacks emit the literal delimiter (never drop a char), table pads short rows but never drops long-row cells. XSS-safe by construction — emits a node tree the view maps to JSX; noinnerHTMLanywhere inweb/src. Linkhrefgated by an http/https/mailto allowlist.lib/toolGroups.ts— pure render-time coalescing; thinking folds in uncounted; 2+ threshold; error/running rollup; group key = first toolseq.routes/RunChat.tsx— node-tree → Solid JSX; links only via scheme-filteredhrefwithrel="noopener noreferrer"; heading level clamped; tool-group open-state is a parentSet<number>keyed byseq, reset on route change (survives SSE refetch);CopyButtonguardsnavigator.clipboard?with a silent catch + timer cleanup.base.css— tables/code fences get their ownoverflow-xcontainers,overflow-wrap: anywhereelsewhere; no page-level horizontal scroll.Conventions. Title is Conventional Commits; body carries a working
Closes #13; no new runtime npm dependency (deps staysolid-js+@solidjs/router). Forgejo reports no conflicts withmain.Tests assert the security-critical path (a
javascript:href is never emitted and degrades to visible text), the grouping/error-rollup rules, and the folded-thinking visibility gate.