Skip to content

feat(markdown): shared Markdown renderer - #2664

Open
camielvs wants to merge 2 commits into
masterfrom
banners-01-markdown
Open

feat(markdown): shared Markdown renderer#2664
camielvs wants to merge 2 commits into
masterfrom
banners-01-markdown

Conversation

@camielvs

@camielvs camielvs commented Aug 26, 2026

Copy link
Copy Markdown
Collaborator

Why

AiChat had 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 absolute http(s) URLs (so nothing can point into our own routes or run javascript:), 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:

element before after
h1 medium large, slightly tighter top margin
h2 small bold medium semibold
h3 small semibold marginally more space below
h4 small semibold small semibold, dimmed
h5 h6 unstyled small, dimmed
bullet / number markers default colour dimmed
task lists (- [ ]) rendered with a bullet bullet removed, checkbox spaced
table cells vertically centred top-aligned
last table row no bottom border bottom border ⚠️
images unstyled rounded, capped at container width

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.

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).
@github-actions

github-actions Bot commented Aug 26, 2026

Copy link
Copy Markdown

🎩 Preview

A preview build has been created at: banners-01-markdown/b525bf9

Comment thread src/components/shared/Markdown/Markdown.tsx
Comment thread src/components/shared/Markdown/Markdown.tsx Outdated
Comment thread src/components/shared/Markdown/Markdown.tsx Outdated
Comment thread src/components/shared/Markdown/Markdown.tsx Outdated
Comment thread src/utils/URL.ts
Comment thread src/routes/v2/shared/components/AiChat/components/renderMarkdown.tsx Outdated
- 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
camielvs force-pushed the banners-01-markdown branch from 47a18de to b525bf9 Compare August 27, 2026 01:18
@camielvs
camielvs marked this pull request as ready for review August 28, 2026 00:02
@camielvs
camielvs requested a review from a team as a code owner August 28, 2026 00:02
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