@dtpr/ui/html
@dtpr/ui/html
render_datachain tool.Import
import { renderDatachainDocument, trustAsHtml } from '@dtpr/ui/html'
import type { RenderedSection, RenderDatachainOptions, SafeHtml } from '@dtpr/ui/html'
Requires @vue/server-renderer (installed as a direct dep of @dtpr/ui). Runs in any Node-compatible runtime — Node, Bun, Cloudflare Workers (via nodejs_compat).
renderDatachainDocument
Render a complete standalone HTML document — <!doctype html>, embedded stylesheet, SSR'd body, and a tiny client-side accordion script.
Signature
async function renderDatachainDocument(
sections: readonly RenderedSection[],
options?: RenderDatachainOptions,
): Promise<string>
Parameters
| Param | Type | Description |
|---|---|---|
sections | readonly RenderedSection[] | Ordered list of sections with their element displays. |
options.locale | string (default 'en') | <html lang> and the locale used by Intl formatting inside element detail. |
options.title | string (default 'DTPR datachain') | <title> text. |
options.emptyHtml | SafeHtml | Trusted HTML inserted when sections is empty. Declare trust via trustAsHtml(...). |
Returns
Promise<string> — a complete HTML document.
RenderedSection
interface RenderedSection {
id: string
title: string
elements: readonly ElementDisplay[]
}
Produce elements by running each element through deriveElementDisplay from @dtpr/ui/core.
trustAsHtml
Brand a string as SafeHtml so it can be passed to options.emptyHtml. The brand is a phantom type — there is no runtime sanitization.
function trustAsHtml(html: string): SafeHtml
trustAsHtml on content you have sanitized (DOMPurify, etc.) or strings you control (static constants, generated markup from trusted sources). Do not wrap raw user input.MCP Apps example
The produced HTML is served with the text/html;profile=mcp-app mime type (SEP-1865):
import { renderDatachainDocument } from '@dtpr/ui/html'
import { deriveElementDisplay } from '@dtpr/ui/core'
const sections = categories.map((c) => ({
id: c.id,
title: extractWithLocale(c.name, 'en').value,
elements: instance.elements
.filter((p) => elementById.get(p.element_id)?.category_id === c.id)
.map((p) => deriveElementDisplay(elementById.get(p.element_id)!, p, 'en')),
}))
const html = await renderDatachainDocument(sections, { locale: 'en' })
return new Response(html, {
headers: { 'content-type': 'text/html;profile=mcp-app' },
})
This is exactly how render_datachain produces the body that resources/read returns.
Empty state
import { renderDatachainDocument, trustAsHtml } from '@dtpr/ui/html'
const html = await renderDatachainDocument([], {
emptyHtml: trustAsHtml('<p>No datachain to display.</p>'),
})
Omit emptyHtml to get a neutral <p class="dtpr-empty" role="status"> placeholder.
See also
- MCP
render_datachain - MCP resources
- Core —
deriveElementDisplay. - Vue components