Problem
Standalone HTML artifacts served from .pi/web/artifacts/ (via serveArtifact) cannot use the Mermaid library that pi-web already bundles. The bundled copy in node_modules/mermaid is only wired into the chat markdown renderer (src/markdown/render.ts, import("mermaid") via Vite) and is not exposed at any stable URL.
As a result, a self-contained explainer/design artifact that wants Mermaid diagrams must load it from a public CDN:
import mermaid from "https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs";
That breaks in offline / air-gapped / locked-down-network environments, and pins the artifact to an external CDN's availability and version.
Proposal
Add a tiny read-only vendor route that streams the already-installed Mermaid ESM bundle from node_modules, so artifacts can load it locally:
GET /api/vendor/mermaid.esm.min.mjs -> node_modules/mermaid/dist/mermaid.esm.min.mjs
Artifacts then import from the same origin (no CDN dependency):
import mermaid from "/api/vendor/mermaid.esm.min.mjs";
Sketch
server.ts already has the streaming primitives (serveStatic streams from staticDir; contentTypes maps extensions). A minimal handler:
// Allowlist of vendored browser libs we are willing to serve from node_modules.
const VENDOR_FILES: Record<string, string> = {
"mermaid.esm.min.mjs": "mermaid/dist/mermaid.esm.min.mjs",
// mermaid ships sub-chunks it imports lazily; may need to serve the dir.
};
function serveVendor(req: IncomingMessage, res: ServerResponse) {
const url = new URL(req.url || "/", `http://${req.headers.host || "localhost"}`);
const name = decodeURIComponent(url.pathname.slice("/api/vendor/".length));
const rel = VENDOR_FILES[name];
if (!rel) return sendJson(res, 404, { ok: false, error: "Unknown vendor file" });
const file = require.resolve(rel, { paths: [process.cwd()] }); // or resolve against pkg root
res.writeHead(200, {
"content-type": contentTypes[extname(file)] || "application/javascript",
"cache-control": "public, max-age=86400, immutable",
});
createReadStream(file).pipe(res);
}
// in the request router:
if (method === "GET" && url.pathname.startsWith("/api/vendor/")) return serveVendor(req, res);
Caveats / things to verify
- Lazy chunks: Mermaid 11's ESM bundle dynamically imports diagram-specific chunks (e.g.
flowDiagram-*.mjs) relative to its own URL. Serving a single file may not be enough — the route likely needs to serve the whole mermaid/dist/ directory (path-safe, allowlisted to that dir) so relative chunk imports resolve. Worth confirming with a flowchart + sequence + state diagram.
- ESM MIME: must be served as
text/javascript / application/javascript for the browser to accept the import.
- Auth: this is a static asset import from
<script type="module">; it can't send the bearer header. Either keep /api/vendor/* unauthenticated (it's read-only public library code) or document that.
- Security: strictly allowlist filenames/dir and reject
../absolute paths, mirroring safeArtifactName in serveArtifact.
Why
Enables fully self-contained, offline-capable HTML artifacts (explainers, design docs) with first-class diagrams, without depending on an external CDN. This pairs with a "visual-explainer" skill that generates diagram-first HTML artifacts instead of verbose Markdown.
Acceptance
Problem
Standalone HTML artifacts served from
.pi/web/artifacts/(viaserveArtifact) cannot use the Mermaid library that pi-web already bundles. The bundled copy innode_modules/mermaidis only wired into the chat markdown renderer (src/markdown/render.ts,import("mermaid")via Vite) and is not exposed at any stable URL.As a result, a self-contained explainer/design artifact that wants Mermaid diagrams must load it from a public CDN:
That breaks in offline / air-gapped / locked-down-network environments, and pins the artifact to an external CDN's availability and version.
Proposal
Add a tiny read-only vendor route that streams the already-installed Mermaid ESM bundle from
node_modules, so artifacts can load it locally:Artifacts then import from the same origin (no CDN dependency):
Sketch
server.tsalready has the streaming primitives (serveStaticstreams fromstaticDir;contentTypesmaps extensions). A minimal handler:Caveats / things to verify
flowDiagram-*.mjs) relative to its own URL. Serving a single file may not be enough — the route likely needs to serve the wholemermaid/dist/directory (path-safe, allowlisted to that dir) so relative chunk imports resolve. Worth confirming with a flowchart + sequence + state diagram.text/javascript/application/javascriptfor the browser to accept theimport.<script type="module">; it can't send the bearer header. Either keep/api/vendor/*unauthenticated (it's read-only public library code) or document that.../absolute paths, mirroringsafeArtifactNameinserveArtifact.Why
Enables fully self-contained, offline-capable HTML artifacts (explainers, design docs) with first-class diagrams, without depending on an external CDN. This pairs with a "visual-explainer" skill that generates diagram-first HTML artifacts instead of verbose Markdown.
Acceptance
GET /api/vendor/mermaid.esm.min.mjsreturns the bundle with a JS MIME type..pi/web/artifacts/foo.htmlthatimports from/api/vendor/...renders flowchart, sequence, and state diagrams with no network access.