Doc formats: rich content for humans + agents
Doc bodies in Dock accept the full friendly format set: diagrams, math, callouts, custom SVG, sandboxed HTML, toggles, cross-references, embeds. Every format is markdown-fenced-block addressable so agents emit them natively, and every format round-trips losslessly through get_doc. Bundle weight is paid only on docs that actually use a format (lazy-loaded on first encounter, cached for the session).
The format catalog
| Format | Markdown | Per-doc cap | Per-source cap |
|---|---|---|---|
| Inline images |  | 200 images | n/a (URL reference) |
| Native videos | Lone URL on its own line (.mp4 / .webm / .mov / .mkv / .m4v) | 20 videos | 5 GB per file (upload time) |
| Mermaid diagrams | ```mermaid | 50 diagrams | 30 KB source |
| Math (KaTeX) | $inline$ / $$block$$ | 500 expressions | 8 KB source |
| Callouts | > [!NOTE|TIP|IMPORTANT|WARNING|CAUTION] | (global doc cap) | (global) |
| SVG (sanitized) | ```svg | 50 blocks | 100 KB post-sanitize |
| HTML (sandboxed) | ```html | 5 blocks | 50 KB post-sanitize |
| Toggle / details | <details><summary>X</summary>...</details> | (global) | (global) |
| Cross-refs | [[slug]] / [[slug#tab]] | 200 refs | n/a |
| Embeds (oEmbed) | Lone URL on its own line (safelisted provider) | 20 embeds | 2 KB URL |
Inline images
Standard CommonMark  syntax. The URL is referenced directly: no server-side reupload, no resizing, no compression. Any publicly-reachable HTTPS URL works. The alt text is the accessible label and shows in place of the image if the URL fails to load, always include it.
Here's the proposed login flow:

The agent step lands between magic-link click and dashboard paint.Renders as a block-feeling figure (full width up to 100%, rounded corners, drop shadow) even though the underlying TipTap node is inline-grouped, that's what makes markdown round-trip cleanly. The allowBase64: false setting rejects data: URIs to keep doc bodies small and crawlable; upload first, then reference.
To attach a user-uploaded file, the human-side UI hits POST /api/workspaces/:slug/upload (10 MB cap) for images or POST /api/workspaces/:slug/upload-media (5 GB cap, client-side multipart) for anything larger. Both return a Vercel Blob URL; reference it from markdown.
Native videos
A lone URL on its own paragraph with a video file extension (.mp4, .m4v, .webm, .mov, .mkv) becomes a native HTML5 <video controls preload="metadata"> player. No iframe, no provider wrapper, no transcoding. The source file's quality is the rendered quality, 4K masters at H.264/H.265 stream cleanly via HTTP range requests.
# Launch walkthrough
Recording from yesterday's rehearsal:
https://cdn.dock.ai/launch-rehearsal-4k.mp4
The intro hits the founding story; jump to 1:42 for the demo segment.Signed-param + timestamp fragments are tolerated (?token=…&Expires=… from S3-style signed URLs, #t=01:42 for a deep-link starting position). Mid- paragraph URLs stay as plain links, the auto-promote only fires when the entire paragraph is the URL.
For user-uploaded files, the slash menu "Video" item or drag-drop of a video file pushes the upload through @vercel/blob/client direct-multipart so the file bypasses the Vercel function body cap (4.5 MB) and streams in parallel chunks straight to Blob. 5 GB hard ceiling per file (Vercel Blob's platform max); longer recordings split into clips.
Why not iframe through an oEmbed provider: YouTube / Vimeo / Loom all apply their own per-tier bitrate caps and compression pipelines. For source-quality playback (design reviews, raw camera footage, screen recordings agents need to inspect frame- by-frame), reference the file directly with a video node.
Mermaid diagrams
Drop a ```mermaid fenced block. 15 sub-types covered by one library: flowchart, sequence, gantt, ER, state, class, mindmap, timeline, pie, quadrant, sankey, XY-chart, packet, block, journey.
# Auth flow
```mermaid
sequenceDiagram
Agent->>Server: POST /upgrade
Server-->>Agent: 402 payment_required
Agent->>User: surface payment URL
```Diagrams render as SVG (zoom-friendly on mobile, indexable for search). Bad syntax surfaces as an error tile with the source visible, your content is never lost. The library is async-loaded on first encounter and cached for the session.
Math (KaTeX)
Inline with $x$, block with $$x$$ on its own lines. LaTeX, KaTeX-rendered with trust: false, no \\href, \\input, or \\includegraphics. ARIA + MathML output by default for screen readers.
The latency budget is $L < 200ms$ at p99.
Cost model:
$$
C = N \cdot \frac{r}{1000}
$$Callouts
GFM-standard. Five variants: NOTE (info), TIP (helpful), IMPORTANT (call attention), WARNING (heads-up), CAUTION (danger).
> [!IMPORTANT]
> Tokens expire 60 seconds after mint.
> [!CAUTION]
> Production data; review before shipping.SVG (the universal escape hatch)
For diagrams Mermaid doesn't model: geometry, custom layouts, decorative figures. Drop a ```svg fenced block with raw SVG markup. The sanitizer strips <script>, <foreignObject>, every on* event handler, and javascript: URLs at write time AND render time.
```svg
<svg viewBox="0 0 100 100">
<title>Right triangle</title>
<polygon points="10,90 90,90 10,10" fill="none" stroke="#0A84FF" stroke-width="2"/>
<text x="50" y="100" text-anchor="middle">a</text>
<text x="0" y="55">b</text>
<text x="55" y="50" transform="rotate(-45 55 50)">c</text>
</svg>
```Always include a <title> element for screen readers, the parser pulls it into the node's alt attr. SVG renders to a centered figure with overflow-auto for mobile zoom.
HTML (sandboxed)
For embedded mockups, landing-page snippets, design previews, anything an agent would render as HTML in a brief. Drop a ```html fenced block. The body is sanitized (no <script>, no on* handlers, no javascript: URLs, no data:text/html) and rendered in a sandboxed iframe. Scripts are disabled by default; data:image/* URIs are allowed for inline images.
```html
<style>
.card { padding: 16px; border: 1px solid #ddd; border-radius: 8px; }
.card h2 { margin: 0 0 8px; }
</style>
<div class="card">
<h2>Welcome to Dock</h2>
<p>The AI workspace for you, your team, and every agent you run.</p>
<a href="https://trydock.ai">Get started</a>
</div>
```For full-page mockups beyond the 50 KB block cap, graduate to a dedicated HTML surface. The same sanitizer + sandbox primitive renders both.
Toggle / details
Native <details> disclosure widget. Useful for FAQ-style content, long appendices, rare-path branches.
<details>
<summary>Risks + mitigations</summary>
- Risk A: handle via retry with exponential backoff
- Risk B: fall back to cached value, log degraded state
- Risk C: page on-call
</details>Cross-references
Inline links to other workspaces, surfaces, or rows. Five forms:
[[my-workspace]], bare workspace slug[[my-org/my-workspace]], org-prefixed[[my-workspace#brief]], specific surface (tab)[[my-workspace#row-cuid42]], specific row[[my-workspace|My Label]], custom display text
Every cross-ref creates a Backlink row. The target's "Referenced from" sidebar widget lists every doc that points at it (Roam-style backlinks but typed: workspace, surface, or row). Targets the reader can't see render as plain text, no info leak about existence.
Slug renames are handled automatically: the cross-ref's stored slug stays as the original spelling for round-trip readability, but resolution chases through WorkspaceSlugAlias.
External embeds
Paste a URL on its own paragraph from a safelisted provider; it becomes a sandboxed iframe. URLs not on the safelist stay as plain links, no auto-promotion outside the explicit list.
Supported providers (v1):
- YouTube (
youtube.com/watch?v=+youtu.be) - Vimeo
- Loom (share URLs)
- Figma (file / design / board / proto)
- CodePen
- GitHub gists
Watch the demo:
https://www.loom.com/share/abc123def456789
Design system spec:
https://www.figma.com/file/abcXYZ/design-systemPer-provider sandbox attrs are tuned for each service's needs. allow-top-navigation and allow-forms are NEVER enabled. referrerpolicy="no-referrer" on every iframe so the embed can't see the source workspace URL.
For agents: validate before writing
The validate_doc_markdown MCP tool parses your markdown and returns counts per format type, structured cap-breach errors, and warnings, without writing anything. Use this when iterating on rich-format markdown to catch problems before burning a real update_doc or append_doc_section call.
{
"ok": true,
"errors": [],
"warnings": [
{
"code": "cross_ref_unresolved",
"message": "Cross-ref [[old-slug]] doesn't resolve to an accessible workspace.",
"details": { "payload": "old-slug" }
}
],
"parsed": {
"byteSize": 4280,
"nodeCount": 87,
"depth": 4,
"headingCount": 3,
"paragraphCount": 12,
"imageCount": 3,
"videoCount": 1,
"mermaidCount": 1,
"mathCount": 4,
"svgCount": 0,
"calloutCount": 2,
"crossRefCount": 5,
"embedCount": 1,
"detailsCount": 1
}
}Round-trip guarantee
Every format in this catalog round-trips losslessly through get_doc → markdown → update_doc. Source is preserved verbatim for code-shaped formats (Mermaid, math, SVG); the syntactic form is preserved for cross-refs (so a renamed target's original spelling survives in your prose). This means an agent can round-trip any doc body through markdown without losing fidelity.
Cap behavior
Cap breaches return a DocGuardError with a structured limit field (e.g. mermaid_count, math_source_bytes). Agents can branch on the limit name to decide whether to retry with smaller content, split across multiple docs, or surface the issue to their user. The full set of limit names is in src/lib/doc-guard.ts.
Related
- Doc body API: pass
markdownon PUT / POST sections; server converts to TipTap. - Doc mode: UI for the same surfaces (slash menu, image upload, shortcuts).
- MCP tool catalog ,
update_doc/append_doc_section/validate_doc_markdown.