feat(markdown): shared Markdown renderer - #2664
Open
camielvs wants to merge 2 commits into
Open
Conversation
Extracts the markdown rendering AiChat had built for itself into a shared `Markdown` component, and adds an `UntrustedMarkdown` variant for text the app did not author. AiChat now renders through the shared base, keeping only its own overrides (entity/component chips, fenced code blocks).
Collaborator
Author
This was referenced Aug 26, 2026
🎩 PreviewA preview build has been created at: |
camielvs
commented
Aug 26, 2026
camielvs
commented
Aug 26, 2026
camielvs
commented
Aug 26, 2026
camielvs
commented
Aug 26, 2026
camielvs
commented
Aug 26, 2026
camielvs
commented
Aug 26, 2026
- Fenced blocks no longer inherit the inline-code pill; `pre` owns the block surround for labelled and unlabelled fences alike. - Links render through `Link` rather than a raw anchor. That needed one fix in the primitive: it wrapped its children in a `div`, which is not allowed inside a `<p>` and which React flagged on every render — AiChat already renders `Link` inside markdown today. A `span` fixes it with no visual change, since the layout comes from the flex classes. - Internal links stay in the same tab and gain no external affordance; only absolute http(s) hrefs get `target="_blank"` and the icon. - Restore `last:border-b-0` on table rows. - `UntrustedMarkdown` locks `img` after the caller spread so the guard cannot be overridden. - `chatComponents` regains its `satisfies Components` check. - Add `toAbsoluteHttpUrl` unit tests covering the protocol filtering. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
camielvs
force-pushed
the
banners-01-markdown
branch
from
August 27, 2026 01:18
47a18de to
b525bf9
Compare
camielvs
marked this pull request as ready for review
August 28, 2026 00:02
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Why
AiChathad built a full markdown renderer for itself. The notices feature later in this stack needs to render markdown too — and from a source outside the app, so it needs stricter handling. Rather than keep a second copy, this lifts AiChat's renderer into a shared component.What you get
Two entry points:
Markdown— for text the app authored. The same styling AiChat had, now in one place.UntrustedMarkdown— for text the app did not author. Raw HTML never renders, links are narrowed to absolutehttp(s)URLs (so nothing can point into our own routes or runjavascript:), and images degrade to their alt text instead of fetching a remote file.AiChat keeps only its own overrides: entity and component chips, and syntax-highlighted fenced code blocks.
Reviewer notes
This is the one PR in the stack that changes something you can already see. AiChat's markdown picks up the shared styling, which differs slightly from what it had:
h1h2h3h4h5h6- [ ])That last row is an unintended drop in the extraction, not a decision — a one-word fix if whoever owns AiChat would rather keep the old look. The rest are the shared component's choices; worth a glance to confirm they're an improvement and not a surprise.
Tests: both entry points, with the untrusted path's escapes (script tags,
javascript:links, relative URLs, remote images) asserted directly.