diff --git a/README.md b/README.md
index 686ece22..e235bab1 100644
--- a/README.md
+++ b/README.md
@@ -34,9 +34,14 @@ business knowledge it builds and maintains for you.
> No extra usage billing from **ktx**.
-
+
+
+
+
+
+
## Why ktx
General-purpose agents struggle on data tasks. They re-explore your warehouse
diff --git a/docs-site/app/diagram-studio/page.tsx b/docs-site/app/diagram-studio/page.tsx
new file mode 100644
index 00000000..205ebd7a
--- /dev/null
+++ b/docs-site/app/diagram-studio/page.tsx
@@ -0,0 +1,12 @@
+import type { Metadata } from "next";
+
+import { DiagramStudio } from "@/components/diagram-studio/studio";
+
+export const metadata: Metadata = {
+ title: "Diagram studio",
+ robots: { index: false, follow: false },
+};
+
+export default function DiagramStudioPage() {
+ return ;
+}
diff --git a/docs-site/components/diagram-studio/flows.ts b/docs-site/components/diagram-studio/flows.ts
new file mode 100644
index 00000000..cddf75cb
--- /dev/null
+++ b/docs-site/components/diagram-studio/flows.ts
@@ -0,0 +1,328 @@
+import { type Edge, MarkerType, type Node } from "@xyflow/react";
+
+import { C } from "./nodes";
+
+const EDGE_COLOR = "#b3bcc4";
+const MARKER_COLOR = "#9aa6ad";
+
+const labelStyle = {
+ fontFamily: "var(--font-inter), system-ui, sans-serif",
+ fontSize: 15,
+ fontWeight: 600,
+ fill: C.inkMuted,
+};
+const labelBgStyle = { fill: "#ffffff", stroke: C.chipBorder, strokeWidth: 1 };
+const labelBg = {
+ labelBgPadding: [8, 4] as [number, number],
+ labelBgBorderRadius: 6,
+ labelStyle,
+ labelBgStyle,
+};
+
+const marker = { type: MarkerType.ArrowClosed, color: MARKER_COLOR, width: 16, height: 16 };
+const edgeStyle = { stroke: EDGE_COLOR, strokeWidth: 2 };
+
+/* ============================== INGESTION =============================== */
+
+const SRC_W = 300;
+const SRC_H = 138;
+const SRC_GAP = 24;
+const srcY = (i: number) => i * (SRC_H + SRC_GAP);
+
+export const ingestionNodes: Node[] = [
+ {
+ id: "title",
+ type: "title",
+ position: { x: 0, y: -96 },
+ data: {
+ width: 560,
+ eyebrow: "1 · Ingestion",
+ title: "ktx builds your context layer",
+ },
+ },
+ {
+ id: "db",
+ type: "card",
+ position: { x: 0, y: srcY(0) },
+ data: {
+ width: SRC_W,
+ height: SRC_H,
+ accent: C.teal,
+ rows: [
+ { kind: "title", text: "Databases" },
+ { kind: "desc", text: "Schemas, keys, query history" },
+ { kind: "muted", text: "Postgres · Snowflake · BigQuery · …" },
+ ],
+ handles: [{ side: "right", type: "source", id: "out" }],
+ },
+ },
+ {
+ id: "bi",
+ type: "card",
+ position: { x: 0, y: srcY(1) },
+ data: {
+ width: SRC_W,
+ height: SRC_H,
+ accent: C.orange,
+ rows: [
+ { kind: "title", text: "BI tools" },
+ { kind: "desc", text: "Dashboards, explores, usage" },
+ { kind: "muted", text: "Metabase · Looker · …" },
+ ],
+ handles: [{ side: "right", type: "source", id: "out" }],
+ },
+ },
+ {
+ id: "model",
+ type: "card",
+ position: { x: 0, y: srcY(2) },
+ data: {
+ width: SRC_W,
+ height: SRC_H,
+ accent: C.amber,
+ rows: [
+ { kind: "title", text: "Modeling code" },
+ { kind: "desc", text: "Metrics, models, joins, entities" },
+ { kind: "muted", text: "dbt · LookML · MetricFlow · …" },
+ ],
+ handles: [{ side: "right", type: "source", id: "out" }],
+ },
+ },
+ {
+ id: "docs",
+ type: "card",
+ position: { x: 0, y: srcY(3) },
+ data: {
+ width: SRC_W,
+ height: SRC_H,
+ accent: C.emerald,
+ rows: [
+ { kind: "title", text: "Docs & notes" },
+ { kind: "desc", text: "Policies, definitions, notes" },
+ { kind: "muted", text: "Notion · any text · …" },
+ ],
+ handles: [{ side: "right", type: "source", id: "out" }],
+ },
+ },
+ {
+ id: "engine",
+ type: "engine",
+ position: { x: 420, y: 52 },
+ data: {
+ width: 380,
+ height: 520,
+ steps: [
+ { n: 1, title: "Source connectors", desc: "Read each source in its shape" },
+ { n: 2, title: "Context builder", desc: "Evidence into proposed updates" },
+ { n: 3, title: "Reconciliation", desc: "Merge with existing context" },
+ { n: 4, title: "Validation", desc: "Check references & semantics" },
+ ],
+ handles: [
+ { side: "left", type: "target", id: "in" },
+ { side: "right", type: "source", id: "out" },
+ ],
+ },
+ },
+ {
+ id: "wiki",
+ type: "card",
+ position: { x: 900, y: 66 },
+ data: {
+ width: 320,
+ height: 220,
+ accent: C.emerald,
+ rows: [
+ { kind: "mono", text: "wiki/*.md", color: C.emerald },
+ { kind: "title", text: "Wiki" },
+ { kind: "chips", items: ["free-form", "auto-maintained"] },
+ { kind: "desc", text: "Definitions, caveats, policies," },
+ { kind: "desc", text: "and notes agents can search." },
+ ],
+ handles: [{ side: "left", type: "target", id: "in" }],
+ },
+ },
+ {
+ id: "sl",
+ type: "card",
+ position: { x: 900, y: 338 },
+ data: {
+ width: 320,
+ height: 220,
+ accent: C.teal,
+ rows: [
+ { kind: "mono", text: "semantic-layer/*.yaml", color: C.teal },
+ { kind: "title", text: "Semantic layer" },
+ { kind: "chips", items: ["executable", "auto-maintained"] },
+ { kind: "desc", text: "Metrics, joins, dimensions, and" },
+ { kind: "desc", text: "filters ktx compiles into SQL." },
+ ],
+ handles: [{ side: "left", type: "target", id: "in" }],
+ },
+ },
+];
+
+const ingestEdge = (source: string, target: string): Edge => ({
+ id: `${source}-${target}`,
+ source,
+ target,
+ sourceHandle: "out",
+ targetHandle: "in",
+ type: "default",
+ style: edgeStyle,
+ markerEnd: marker,
+});
+
+export const ingestionEdges: Edge[] = [
+ ingestEdge("db", "engine"),
+ ingestEdge("bi", "engine"),
+ ingestEdge("model", "engine"),
+ ingestEdge("docs", "engine"),
+ ingestEdge("engine", "wiki"),
+ ingestEdge("engine", "sl"),
+];
+
+/* =============================== RUNTIME ================================ */
+
+export const runtimeNodes: Node[] = [
+ {
+ id: "title",
+ type: "title",
+ position: { x: 0, y: -84 },
+ data: {
+ width: 560,
+ eyebrow: "2 · Serving",
+ title: "agents query it through MCP",
+ },
+ },
+ {
+ id: "agent",
+ type: "card",
+ position: { x: 0, y: 115 },
+ data: {
+ width: 280,
+ height: 190,
+ accent: C.neutral,
+ align: "center",
+ rows: [
+ { kind: "title", text: "Your agent" },
+ { kind: "muted", text: "Claude Code · Cursor" },
+ { kind: "muted", text: "Codex · OpenCode" },
+ ],
+ handles: [
+ { side: "right", type: "source", id: "ask", top: "42%" },
+ { side: "right", type: "target", id: "answer", top: "62%" },
+ ],
+ },
+ },
+ {
+ id: "hub",
+ type: "hub",
+ position: { x: 420, y: 85 },
+ data: {
+ width: 360,
+ height: 250,
+ rows: [
+ "Search wiki + semantic layer",
+ "Return approved metrics",
+ "Compile metrics → SQL",
+ ],
+ handles: [
+ { side: "left", type: "target", id: "ask", top: "42%" },
+ { side: "left", type: "source", id: "answer", top: "62%" },
+ { side: "right", type: "source", id: "to-context", top: "30%" },
+ { side: "right", type: "source", id: "to-warehouse", top: "72%" },
+ ],
+ },
+ },
+ {
+ id: "context",
+ type: "card",
+ position: { x: 920, y: 15 },
+ data: {
+ width: 300,
+ height: 150,
+ accent: C.teal,
+ rows: [
+ { kind: "title", text: "Context layer" },
+ { kind: "mono", text: "wiki/*.md", color: C.emerald },
+ { kind: "mono", text: "semantic-layer/*.yaml", color: C.teal },
+ ],
+ handles: [{ side: "left", type: "target", id: "in" }],
+ },
+ },
+ {
+ id: "warehouse",
+ type: "card",
+ position: { x: 920, y: 255 },
+ data: {
+ width: 300,
+ height: 150,
+ accent: C.slate,
+ rows: [
+ { kind: "title", text: "Warehouse" },
+ {
+ kind: "badge",
+ text: "read-only",
+ bg: "#ecf6f8",
+ border: "#bfe3ea",
+ color: C.teal,
+ },
+ { kind: "desc", text: "Runs the compiled SQL" },
+ ],
+ handles: [{ side: "left", type: "target", id: "in" }],
+ },
+ },
+];
+
+export const runtimeEdges: Edge[] = [
+ {
+ id: "ask",
+ source: "agent",
+ sourceHandle: "ask",
+ target: "hub",
+ targetHandle: "ask",
+ type: "default",
+ label: "ask",
+ ...labelBg,
+ style: edgeStyle,
+ markerEnd: marker,
+ },
+ {
+ id: "answer",
+ source: "hub",
+ sourceHandle: "answer",
+ target: "agent",
+ targetHandle: "answer",
+ type: "default",
+ label: "answer",
+ ...labelBg,
+ style: edgeStyle,
+ markerEnd: marker,
+ },
+ {
+ id: "search",
+ source: "hub",
+ sourceHandle: "to-context",
+ target: "context",
+ targetHandle: "in",
+ type: "default",
+ label: "search",
+ ...labelBg,
+ style: edgeStyle,
+ markerStart: marker,
+ markerEnd: marker,
+ },
+ {
+ id: "readonly",
+ source: "hub",
+ sourceHandle: "to-warehouse",
+ target: "warehouse",
+ targetHandle: "in",
+ type: "default",
+ label: "read-only",
+ ...labelBg,
+ style: edgeStyle,
+ markerStart: marker,
+ markerEnd: marker,
+ },
+];
diff --git a/docs-site/components/diagram-studio/mascot.tsx b/docs-site/components/diagram-studio/mascot.tsx
new file mode 100644
index 00000000..467f6ee5
--- /dev/null
+++ b/docs-site/components/diagram-studio/mascot.tsx
@@ -0,0 +1,57 @@
+/**
+ * Inlined ktx mascot, ported from assets/ktx-mascot.svg.
+ *
+ * - `light` renders the dark-bodied mascot for light surfaces.
+ * - `dark` renders the cream-bodied mascot for dark surfaces (e.g. the ktx
+ * hub panel), mirroring brand/ktx-mascot-dark.svg.
+ */
+export function KtxMascot({
+ variant = "light",
+ size = 56,
+}: {
+ variant?: "light" | "dark";
+ size?: number;
+}) {
+ const body = variant === "dark" ? "#F5F1EA" : "#1B3139";
+ const eye = variant === "dark" ? "#1B3139" : "#F5F1EA";
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/docs-site/components/diagram-studio/nodes.tsx b/docs-site/components/diagram-studio/nodes.tsx
new file mode 100644
index 00000000..f648a905
--- /dev/null
+++ b/docs-site/components/diagram-studio/nodes.tsx
@@ -0,0 +1,493 @@
+"use client";
+
+import { Handle, Position, type Node, type NodeProps } from "@xyflow/react";
+
+import { KtxMascot } from "./mascot";
+
+/** Fixed palette mirrored from the approved SVG diagrams so the exported PNG
+ * is theme-independent (one image that reads on light and dark GitHub). */
+export const C = {
+ ink: "#1b1b18",
+ inkSoft: "#57534e",
+ inkMuted: "#8c857f",
+ cardBorder: "#e2dfd9",
+ engineBg: "#15323a",
+ engineBorder: "#23474f",
+ cyan: "#55dced",
+ stepNum: "#06262c",
+ stepTitle: "#f3f1ec",
+ stepDesc: "#9fb6bc",
+ hubRow: "#eef4f5",
+ chipBg: "#faf9f6",
+ chipBorder: "#e7e5e4",
+ teal: "#0e7490",
+ emerald: "#059669",
+ orange: "#f97316",
+ amber: "#d97706",
+ slate: "#334155",
+ neutral: "#94a3b8",
+} as const;
+
+const DISPLAY = "var(--font-display), system-ui, sans-serif";
+const BODY = "var(--font-inter), system-ui, sans-serif";
+const MONO = "var(--font-mono), ui-monospace, monospace";
+
+const CARD_SHADOW = "0 3px 12px rgba(27, 49, 57, 0.10)";
+const ENGINE_SHADOW = "0 6px 22px rgba(2, 12, 15, 0.30)";
+
+/** ktx logo mascot size, shared by the engine and hub headers. */
+const LOGO_SIZE = 56;
+
+type HandleSpec = {
+ side: "left" | "right";
+ type: "source" | "target";
+ id: string;
+ top?: string;
+};
+
+function Handles({ specs }: { specs?: HandleSpec[] }) {
+ if (!specs) return null;
+ return (
+ <>
+ {specs.map((h) => (
+
+ ))}
+ >
+ );
+}
+
+/* ------------------------------- Card node ------------------------------- */
+
+type CardRow =
+ | { kind: "title"; text: string }
+ | { kind: "mono"; text: string; color: string }
+ | { kind: "desc"; text: string }
+ | { kind: "muted"; text: string }
+ | { kind: "chips"; items: string[] }
+ | { kind: "badge"; text: string; bg: string; border: string; color: string };
+
+type CardData = {
+ width: number;
+ height: number;
+ accent: string;
+ align?: "center";
+ rows: CardRow[];
+ handles?: HandleSpec[];
+};
+
+function gapFor(kind: CardRow["kind"], prev?: CardRow["kind"]): number {
+ if (!prev) return 0;
+ if (kind === "desc" && prev === "desc") return 3;
+ if (kind === "mono" && prev === "mono") return 2;
+ if (kind === "title") return 6;
+ return 10;
+}
+
+function CardRowView({ row }: { row: CardRow }) {
+ switch (row.kind) {
+ case "title":
+ return (
+
+ {row.text}
+
+ );
+ case "mono":
+ return (
+
+ {row.text}
+
+ );
+ case "desc":
+ return (
+
+ {row.text}
+
+ );
+ case "muted":
+ return (
+
+ {row.text}
+
+ );
+ case "chips":
+ return (
+
+ {row.items.map((c) => (
+
+ {c}
+
+ ))}
+
+ );
+ case "badge":
+ return (
+
+ {row.text}
+
+ );
+ }
+}
+
+function CardNode({ data }: NodeProps>) {
+ const center = data.align === "center";
+ return (
+
+
+
+ {data.rows.map((row, i) => (
+
+
+
+ ))}
+
+ );
+}
+
+/* ------------------------------ Engine node ------------------------------ */
+
+type EngineStep = { n: number; title: string; desc: string };
+
+type EngineData = {
+ width: number;
+ height: number;
+ steps: EngineStep[];
+ handles?: HandleSpec[];
+};
+
+function EngineNode({ data }: NodeProps>) {
+ return (
+
+
+
+
+
+
+ ktx
+
+
+
+ {data.steps.map((s) => (
+
+
+ {s.n}
+
+
+
+ {s.title}
+
+
+ {s.desc}
+
+
+
+ ))}
+
+
+ );
+}
+
+/* -------------------------------- Hub node ------------------------------- */
+
+type HubData = {
+ width: number;
+ height: number;
+ rows: string[];
+ handles?: HandleSpec[];
+};
+
+function HubNode({ data }: NodeProps>) {
+ return (
+
+
+
+
+
+
+ ktx
+
+
+
+ {data.rows.map((r) => (
+
+
+
+ {r}
+
+
+ ))}
+
+
+ );
+}
+
+/* ------------------------------- Title node ------------------------------ */
+
+type TitleData = { width: number; eyebrow: string; title: string };
+
+function TitleNode({ data }: NodeProps>) {
+ return (
+
+
+ {data.eyebrow}
+
+
+ {data.title}
+
+
+ );
+}
+
+export const nodeTypes = {
+ card: CardNode,
+ engine: EngineNode,
+ hub: HubNode,
+ title: TitleNode,
+};
diff --git a/docs-site/components/diagram-studio/studio.tsx b/docs-site/components/diagram-studio/studio.tsx
new file mode 100644
index 00000000..7b96ae7b
--- /dev/null
+++ b/docs-site/components/diagram-studio/studio.tsx
@@ -0,0 +1,242 @@
+"use client";
+
+import "@xyflow/react/dist/style.css";
+
+import { useCallback, useRef, useState } from "react";
+import {
+ Background,
+ BackgroundVariant,
+ type Edge,
+ getNodesBounds,
+ type Node,
+ ReactFlow,
+ ReactFlowProvider,
+ useEdgesState,
+ useNodesState,
+ useReactFlow,
+} from "@xyflow/react";
+import { toPng } from "html-to-image";
+
+import {
+ ingestionEdges,
+ ingestionNodes,
+ runtimeEdges,
+ runtimeNodes,
+} from "./flows";
+import { nodeTypes } from "./nodes";
+
+const EXPORT_PADDING = 48;
+const EXPORT_PIXEL_RATIO = 2;
+
+function DiagramCanvasInner({
+ initialNodes,
+ initialEdges,
+ fileName,
+ height,
+ dark,
+}: {
+ initialNodes: Node[];
+ initialEdges: Edge[];
+ fileName: string;
+ height: number;
+ dark: boolean;
+}) {
+ const wrapperRef = useRef(null);
+ const [nodes, , onNodesChange] = useNodesState(initialNodes);
+ const [edges, , onEdgesChange] = useEdgesState(initialEdges);
+ const { getNodes } = useReactFlow();
+ const [busy, setBusy] = useState(false);
+
+ const download = useCallback(async () => {
+ const viewport = wrapperRef.current?.querySelector(
+ ".react-flow__viewport",
+ );
+ if (!viewport) return;
+ setBusy(true);
+ try {
+ await document.fonts.ready;
+ const bounds = getNodesBounds(getNodes());
+ const outW = Math.ceil(bounds.width + EXPORT_PADDING * 2);
+ const outH = Math.ceil(bounds.height + EXPORT_PADDING * 2);
+ const tx = EXPORT_PADDING - bounds.x;
+ const ty = EXPORT_PADDING - bounds.y;
+ const dataUrl = await toPng(viewport, {
+ width: outW,
+ height: outH,
+ pixelRatio: EXPORT_PIXEL_RATIO,
+ // transparent background so one PNG works on light and dark GitHub
+ style: {
+ width: `${outW}px`,
+ height: `${outH}px`,
+ transform: `translate(${tx}px, ${ty}px) scale(1)`,
+ },
+ });
+ const link = document.createElement("a");
+ link.download = fileName;
+ link.href = dataUrl;
+ link.click();
+ } finally {
+ setBusy(false);
+ }
+ }, [fileName, getNodes]);
+
+ return (
+
+
+
+ {busy ? "Exporting…" : "Download PNG"}
+
+
+
+
+
+
+
+
+ );
+}
+
+function btnStyle(disabled: boolean): React.CSSProperties {
+ return {
+ fontFamily: "var(--font-inter), system-ui, sans-serif",
+ fontSize: 13,
+ fontWeight: 600,
+ padding: "7px 14px",
+ borderRadius: 8,
+ border: "1px solid #0e7490",
+ background: disabled ? "#9bbdc6" : "#0e7490",
+ color: "#ffffff",
+ cursor: disabled ? "default" : "pointer",
+ };
+}
+
+function DiagramCanvas(props: {
+ initialNodes: Node[];
+ initialEdges: Edge[];
+ fileName: string;
+ height: number;
+ dark: boolean;
+}) {
+ return (
+
+
+
+ );
+}
+
+export function DiagramStudio() {
+ const [dark, setDark] = useState(false);
+ return (
+
+
+
+
+ 1 · Ingestion — building the context layer
+
+
+
+
+ 2 · Serving — answering agents at runtime
+
+
+
+ );
+}
+
+const sectionTitle: React.CSSProperties = {
+ fontFamily: "var(--font-display), system-ui, sans-serif",
+ fontSize: 18,
+ fontWeight: 600,
+ color: "#1b1b18",
+ marginBottom: 12,
+};
diff --git a/docs-site/package.json b/docs-site/package.json
index 2af1c19d..f418c0ee 100644
--- a/docs-site/package.json
+++ b/docs-site/package.json
@@ -14,6 +14,7 @@
"fumadocs-core": "16.8.10",
"fumadocs-mdx": "15.0.7",
"fumadocs-ui": "16.8.10",
+ "html-to-image": "1.11.11",
"next": "^16",
"react": "19.2.6",
"react-dom": "19.2.6"
diff --git a/docs-site/public/images/ingestion-flow-transparent.svg b/docs-site/public/images/ingestion-flow-transparent.svg
deleted file mode 100644
index 86356d6b..00000000
--- a/docs-site/public/images/ingestion-flow-transparent.svg
+++ /dev/null
@@ -1,210 +0,0 @@
-
- ktx ingestion flow
- Source systems flow through source connectors, context builder, reconciliation, and validation to create wiki Markdown and semantic-layer YAML outputs.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Databases
- Schemas, columns, keys,
- row counts, and query
- history.
-
-
- PostgreSQL
-
- Snowflake
-
- BigQuery
-
- SQLite
-
-
-
-
-
-
- BI tools
- Dashboards, questions,
- explores, usage, and trusted
- examples.
-
-
- Metabase
-
- Looker
-
-
-
-
-
-
- Modeling code
- Existing metrics, dimensions,
- models, joins, and entities.
-
-
- dbt
-
- LookML
-
- MetricFlow
-
-
-
-
-
-
- Docs and notes
- Policies, caveats, team
- definitions, and analyst
- context.
-
-
- Notion
-
- Any text
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 1
- Source connectors
- Read each configured system in
- its native shape.
-
-
-
-
-
- 2
- Context builder
- Turn source evidence into
- proposed context updates.
-
-
-
-
-
- 3
- Reconciliation
- Merge new evidence with the
- context that already exists.
-
-
-
-
-
- 4
- Validation
- Check references and semantics
- before agents rely on them.
-
-
-
-
-
-
-
- wiki/*.md
- Wiki
-
-
- free-form
-
- auto-maintained
-
- Definitions, caveats, policies, analyst notes, and
- business language that agents can search.
-
-
-
-
-
- semantic-layer/*.yaml
- Semantic layer
-
-
- structured
-
- executable
-
- auto-maintained
-
- Metrics, joins, tables, dimensions, filters, and
- segments that ktx can validate and compile into
- SQL.
-
-
-
-
- references
-
-
-
diff --git a/docs-site/public/images/ingestion-flow.png b/docs-site/public/images/ingestion-flow.png
index 49bc544f..59f6ad17 100644
Binary files a/docs-site/public/images/ingestion-flow.png and b/docs-site/public/images/ingestion-flow.png differ
diff --git a/docs-site/public/images/mcp-runtime-flow.png b/docs-site/public/images/mcp-runtime-flow.png
new file mode 100644
index 00000000..ec56dff1
Binary files /dev/null and b/docs-site/public/images/mcp-runtime-flow.png differ
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index bf496066..15bc75f3 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -85,6 +85,9 @@ importers:
fumadocs-ui:
specifier: 16.8.10
version: 16.8.10(@tailwindcss/oxide@4.3.0)(@types/mdx@2.0.13)(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(fumadocs-core@16.8.10(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.15)(lucide-react@1.16.0(react@19.2.6))(next@16.2.6(@opentelemetry/api@1.9.1)(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(zod@4.4.3))(next@16.2.6(@opentelemetry/api@1.9.1)(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(tailwindcss@4.3.0)
+ html-to-image:
+ specifier: 1.11.11
+ version: 1.11.11
next:
specifier: ^16
version: 16.2.6(@opentelemetry/api@1.9.1)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
@@ -3722,6 +3725,9 @@ packages:
html-escaper@2.0.2:
resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==}
+ html-to-image@1.11.11:
+ resolution: {integrity: sha512-9gux8QhvjRO/erSnDPv28noDZcPZmYE7e1vFsBLKLlRlKDSqNJYebj6Qz1TGd5lsRV+X+xYyjCKjuZdABinWjA==}
+
html-void-elements@3.0.0:
resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==}
@@ -9623,6 +9629,8 @@ snapshots:
html-escaper@2.0.2: {}
+ html-to-image@1.11.11: {}
+
html-void-elements@3.0.0: {}
http-errors@2.0.1: