Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions deno.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
"./react",
"./scripts/codemods",
"./extensions/ext-llm-anthropic",
"./extensions/ext-markdown-react",
"./extensions/ext-llm-google",
"./extensions/ext-llm-openai",
"./extensions/ext-auth-jwt",
Expand Down Expand Up @@ -155,6 +156,7 @@
"./extensions/distributed/rate-limit-support": "./src/extensions/distributed/rate-limit-support.ts",
"./extensions/distributed/routing-invalidation-support": "./src/extensions/distributed/routing-invalidation-support.ts",
"./extensions/dev-ui": "./src/extensions/dev-ui/index.ts",
"./extensions/markdown": "./src/extensions/markdown/index.ts",
"./extensions/dev-ui/protocol": "./src/extensions/dev-ui/protocol.ts",
"./extensions/rendering": "./src/extensions/rendering/index.ts",
"./extensions/contracts": "./src/extensions/contracts.ts",
Expand Down
33 changes: 31 additions & 2 deletions deno.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

58 changes: 50 additions & 8 deletions docs/guides/chat-ui.md
Original file line number Diff line number Diff line change
Expand Up @@ -304,12 +304,38 @@ different requests would share the same store.
Use chat context providers only when nested components need direct state access.
Prefer preset props or composition components first.

## Render Markdown in chat

`Chat` renders assistant answers and reasoning as semantic Markdown out of the
box. The built-in `@veryfront/ext-markdown-react` extension supplies the
renderer, so CommonMark plus GFM tables, task lists, strikethrough, and
autolinks work with no project setup. Fenced code renders through the shared
code block, with a language label and a copy button.

To use a different renderer, install one for the subtree. An application
renderer takes precedence over the built-in extension everywhere, including
inside chat:

```tsx
import { Chat } from "veryfront/chat";
import { MarkdownRendererProvider } from "veryfront/markdown";
import { ProjectMarkdownRenderer } from "./project-markdown-renderer.tsx";

export default function ChatPage() {
return (
<MarkdownRendererProvider renderer={ProjectMarkdownRenderer}>
<Chat agentId="assistant" />
</MarkdownRendererProvider>
);
}
```
Comment thread
coderabbitai[bot] marked this conversation as resolved.

## Present Markdown source safely

`veryfront/markdown` is the dependency-free Markdown boundary used by chat
surfaces. Without an installed rich renderer, it preserves the exact source in
an escaped `<pre><code>` element. This is useful when source visibility matters
more than semantic formatting:
`veryfront/markdown` is the dependency-free Markdown boundary. Used on its own,
without an installed rich renderer, it preserves the exact source in an escaped
`<pre><code>` element. This is useful when source visibility matters more than
semantic formatting:

````tsx
import { Markdown } from "veryfront/markdown";
Expand Down Expand Up @@ -337,10 +363,26 @@ escaped source is present in server HTML.

### Install a semantic renderer

Semantic Markdown is an explicit extension capability. Select a trusted
extension or application adapter that implements `MarkdownRendererProps`, then
install its component for the relevant subtree. In this example,
`ProjectMarkdownRenderer` comes from that adapter:
Semantic Markdown is an extension capability. Chat installs
`@veryfront/ext-markdown-react` for you; outside chat, install a renderer for
the relevant subtree yourself. Use the built-in extension:

```tsx
import { Markdown, MarkdownRendererProvider } from "veryfront/markdown";
import { MarkdownRenderer } from "@veryfront/ext-markdown-react/renderer";

export default function Result() {
return (
<MarkdownRendererProvider renderer={MarkdownRenderer}>
<Markdown>{answer}</Markdown>
</MarkdownRendererProvider>
);
}
```

Or select another trusted extension or application adapter that implements
`MarkdownRendererProps`. In this example, `ProjectMarkdownRenderer` comes from
that adapter:

```tsx
import { Markdown, MarkdownRendererProvider } from "veryfront/markdown";
Expand Down
4 changes: 3 additions & 1 deletion extensions/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ Extension availability is separate from contract requirement:
| Package | Contract | Description |
| ------------------------------------------------- | ----------------------------- | ----------------------------------------------------- |
| [`@veryfront/ext-content-mdx`](./ext-content-mdx) | `ContentProcessor` | MDX and Markdown processing via unified/remark/rehype |
| [`@veryfront/ext-markdown-react`](./ext-markdown-react) | `MarkdownRendererProvider` | Rich Markdown rendering for React chat surfaces via react-markdown and remark-gfm |
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
| [`@veryfront/ext-yaml`](./ext-yaml) | `SkillDocumentParserProvider` | YAML parsing for skill and agent documents |

### Development and rendering
Expand Down Expand Up @@ -128,6 +129,7 @@ baseline subset used by ordinary apps and local development.
| `@veryfront/ext-parser-babel` | `CodeParser` | Deferred; standard npm baseline |
| `@veryfront/ext-yaml` | `SkillDocumentParserProvider` | Deferred; standard npm baseline |
| `@veryfront/ext-content-mdx` | `ContentProcessor` | Deferred; standard npm baseline |
| `@veryfront/ext-markdown-react` | `MarkdownRendererProvider` | Deferred; standard npm baseline |
| `@veryfront/ext-css-tailwind` | `CSSProcessor` | Deferred; standard npm baseline |
| `@veryfront/ext-node-websocket-ws` | `NodeWebSocketServerProvider` | Deferred; standard npm baseline |
| `@veryfront/ext-dev-ui-react` | `DevUiAssetProvider` | Deferred; standard npm baseline |
Expand Down Expand Up @@ -175,7 +177,7 @@ raw transitive dependencies such as `bash-tool`, `just-bash`, `jose`,

| Runtime or service role | Install these extension packages |
| -------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| CLI, build image, or project server runtime | `@veryfront/ext-bundler-esbuild`, `@veryfront/ext-content-mdx`, `@veryfront/ext-css-tailwind`, `@veryfront/ext-dev-ui-react`, `@veryfront/ext-node-websocket-ws`, `@veryfront/ext-parser-babel`, `@veryfront/ext-yaml` |
| CLI, build image, or project server runtime | `@veryfront/ext-bundler-esbuild`, `@veryfront/ext-content-mdx`, `@veryfront/ext-css-tailwind`, `@veryfront/ext-dev-ui-react`, `@veryfront/ext-markdown-react`, `@veryfront/ext-node-websocket-ws`, `@veryfront/ext-parser-babel`, `@veryfront/ext-yaml` |
| Build with CSS optimization | `@veryfront/ext-css-lightning` (register explicitly) |
| Build with CSS purging or critical CSS | `@veryfront/ext-css-purgecss` (register explicitly) |
| Build with image optimization | `@veryfront/ext-image-sharp` (register explicitly) |
Expand Down
58 changes: 58 additions & 0 deletions extensions/ext-markdown-react/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# @veryfront/ext-markdown-react

Rich Markdown rendering for React surfaces, built on
[react-markdown](https://github.com/remarkjs/react-markdown) and
[remark-gfm](https://github.com/remarkjs/remark-gfm).

Provides the `MarkdownRendererProvider` contract. Chat installs this renderer by
default, so assistant answers render as semantic HTML without any project setup.

## What it renders

- CommonMark: headings, paragraphs, lists, blockquotes, links, images, emphasis,
inline code, and fenced code.
- GFM: tables, task lists, strikethrough, and autolinks.
- Fenced code through the shared `CodeBlock` primitive (language label, copy
button, and an optional syntax-highlight renderer).
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

## Safety

- Raw HTML in Markdown source is never injected. `rehype-raw` is not installed,
so `<script>` in source reaches the DOM as escaped text.
- react-markdown's default URL transform drops unsafe link protocols such as
`javascript:`.
- Links open in a new tab with `rel="noopener noreferrer"`.

## Use it directly

Chat wires this up for you. Install it explicitly when you render
`veryfront/markdown` outside chat:

```tsx
import { Markdown, MarkdownRendererProvider } from "veryfront/markdown";
import { MarkdownRenderer } from "@veryfront/ext-markdown-react/renderer";

export default function Answer({ source }: { source: string }) {
return (
<MarkdownRendererProvider renderer={MarkdownRenderer}>
<Markdown>{source}</Markdown>
</MarkdownRendererProvider>
);
}
```

Import from `@veryfront/ext-markdown-react/renderer` in client code. The package
root additionally exports the extension factory, which pulls in the extension
registry.

## Replace it

Pass your own renderer to `MarkdownRendererProvider` to override this one
everywhere, including inside chat. Pass `renderer={null}` on a single `Markdown`
instance to show plain escaped source instead.

## Test

```bash
deno task test
```
45 changes: 45 additions & 0 deletions extensions/ext-markdown-react/deno.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
{
"name": "@veryfront/ext-markdown-react",
"version": "0.1.0",
"exports": {
".": "./src/index.ts",
"./renderer": "./src/renderer.tsx"
},
"veryfront": {
"extension": true,
"activation": "auto",
"contracts": {
"provides": [
"MarkdownRendererProvider"
]
},
"capabilities": [],
"npm": {
"runtimeVersionFromManifest": true,
"peerDependencies": {
"react": "^19.2.4",
"react-dom": "^19.2.4"
}
}
},
"imports": {
"react": "../../react/react.ts",
"react/jsx-runtime": "../../react/jsx-runtime.ts",
"react-dom/server": "../../react/react-dom-server.ts",
"react-markdown": "npm:react-markdown@9.0.3",
"remark-gfm": "npm:remark-gfm@4.0.1",
"@std/assert": "jsr:@std/assert@1.0.19",
"@std/testing/bdd": "jsr:@std/testing@1.0.17/bdd",
"veryfront/extensions": "../../src/extensions/types.ts",
"veryfront/extensions/markdown": "../../src/extensions/markdown/index.ts",
"veryfront/markdown": "../../src/markdown/index.ts"
},
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "react"
},
"tasks": {
"test": "deno test --no-check --allow-all src/",
"check": "deno check src/index.ts src/renderer.tsx"
}
}
37 changes: 37 additions & 0 deletions extensions/ext-markdown-react/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/**
* react-markdown implementation of the `MarkdownRendererProvider` extension
* contract.
*
* @module extensions/ext-markdown-react
*/

import type { ExtensionFactory } from "veryfront/extensions";
import {
createMarkdownRendererProvider,
MarkdownRendererProviderName,
} from "veryfront/extensions/markdown";
import extensionPackage from "../deno.json" with { type: "json" };
import { MarkdownRenderer } from "./renderer.tsx";

const extMarkdownReact: ExtensionFactory = () => {
const provider = createMarkdownRendererProvider("ext-markdown-react", MarkdownRenderer);

return {
name: "ext-markdown-react",
version: extensionPackage.version,
contracts: {
provides: [MarkdownRendererProviderName],
},
capabilities: [],
setup(ctx) {
ctx.provide(MarkdownRendererProviderName, provider);
ctx.logger.debug("[ext-markdown-react] MarkdownRendererProvider registered");
},
teardown() {
// No resources to release.
},
};
};

export default extMarkdownReact;
export { MarkdownRenderer } from "./renderer.tsx";
Loading
Loading