diff --git a/openspec/changes/add-inline-html-renderer/.openspec.yaml b/openspec/changes/add-inline-html-renderer/.openspec.yaml
new file mode 100644
index 0000000000000..5e6d53a3fdaa4
--- /dev/null
+++ b/openspec/changes/add-inline-html-renderer/.openspec.yaml
@@ -0,0 +1,2 @@
+schema: spec-driven
+created: 2026-07-24
diff --git a/openspec/changes/add-inline-html-renderer/design.md b/openspec/changes/add-inline-html-renderer/design.md
new file mode 100644
index 0000000000000..55eb767ae1879
--- /dev/null
+++ b/openspec/changes/add-inline-html-renderer/design.md
@@ -0,0 +1,99 @@
+## Context
+
+HTML artifact previews are rendered by `HTMLPreview` (`code-block-preview/html-preview.ts`), which delegates to `linkIframe` (`code-block-preview/iframe-container.ts`). Today `linkIframe`:
+
+1. Points the iframe at `https://affine.run/static/container.html`.
+2. Adds a permissive sandbox including both `allow-scripts` **and** `allow-same-origin`.
+3. On `onload`, posts the artifact HTML to the remote container via `contentWindow.postMessage(html, 'https://affine.run')`.
+
+The remote page is the isolation boundary: it runs on the `affine.run` origin, so artifact scripts execute cross-origin to the host app. The cost is a hard runtime dependency on affine.run for something that is otherwise a purely local render. This renderer is shared by two call sites — the chat code-artifact preview (`ai-tools/code-artifact.ts` → ``) and inserted `affine:code` blocks with `preview: true` in docs (`CodeBlockHtmlPreview` extension) — so both benefit from a local replacement.
+
+The only existing local path (`adapter-panel/.../adapter-panel-body.ts`) uses `