// MulmoClaude's host wiring for the presentHtml plugin's dispatch channel.
// The extracted @mulmoclaude/html-plugin View reaches host storage through
// `useRuntime().dispatch({ kind: "loadHtml" | "saveHtml", … })`; this registers
// the built-in "html" dispatch handler that routes those calls to the package's
// `executeHtmlDispatch` against the GENERIC `files.artifacts` capability, then
// publishes a file-change event after a save so subscribed View tabs refresh.
// Imported for side effect at boot (server/index.ts) so the dispatch resolves.
import { executeHtmlDispatch, isHtmlDispatchArgs, isPackHtmlArgs } from "@mulmoclaude/html-plugin";
import type { PackHtmlArgs, PackHtmlResult } from "@mulmoclaude/html-plugin";
import { makeArtifactsFileOps } from "./runtime.js";
import { publishFileChange } from "../events/file-change.js";
import { describeKind, registerBuiltinDispatch } from "./builtin-dispatch.js";
import { packHtmlZip } from "../utils/share/packHtml.js";
import { isHtmlPath } from "../utils/files/html-store.js";
import { makeByPathFileOps } from "../utils/files/by-path.js";
/** Scope name — matches `wrapWithScope("html", …)` in
* `src/plugins/presentHtml/index.ts`, which is what the View's
* `useRuntime().dispatch` uses as the `:pkg` path segment. */
const HTML_SCOPE = "html";
// `packHtml` bundles the page + its local assets into a self-contained
// zip. It lives host-side (binary reads + zip via server/utils/share),
// so it's intercepted here rather than in the package's pure router.
async function packHtmlForDownload(args: PackHtmlArgs): Promise {
if (!isHtmlPath(args.path)) throw new Error("path must be a canonical artifacts/html/*.html file");
const { filename, zip } = await packHtmlZip(args.path);
return { filename, zipBase64: zip.toString("base64") };
}
// `args` is whatever the View put on the wire — untyped data, not a value the
// compiler has vouched for. The package exports guards for its own shapes, so
// the boundary narrows instead of asserting.
registerBuiltinDispatch(HTML_SCOPE, async (args) => {
if (isPackHtmlArgs(args)) {
return packHtmlForDownload(args);
}
if (!isHtmlDispatchArgs(args)) {
throw new Error(`html plugin: unrecognised dispatch payload (kind=${describeKind(args)})`);
}
const dispatchArgs = args;
const result = await executeHtmlDispatch({ files: { artifacts: makeArtifactsFileOps(), byPath: makeByPathFileOps([".html", ".htm"]) } }, dispatchArgs);
// saveHtml changed bytes on disk → nudge subscribed View tabs (load is read-only).
if (dispatchArgs.kind === "saveHtml") {
void publishFileChange(dispatchArgs.path);
}
return result;
});