refactor: decompose render pipeline into decorate() + source value

Replace the one branchy bootstrapTemplateWithHTMXetc (six positional
args, five jobs) with small pure functions: headChrome, blissAttrs,
decoratePage, decorateFragment, and a decorate() entry point. The
route/clone/copy provenance triple becomes a single `source` value
({structureId, routeId, copyUrl}) threaded through render — the same
data the inspector roadmap needs.

Collapse the four copy-pasted render sites (res.render, ws.render,
template preview, scaffold preview) onto renderTemplate + decorate,
and dedupe the per-structure route() closure into makeRoute().

Emitted HTML (head chrome + data-bliss-* attributes) is unchanged;
verified full-page and htmx-fragment output.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Your Name 2026-08-02 22:22:00 -04:00
parent ae51fc763c
commit 14f9ec3160

211
index.js
View file

@ -165,23 +165,15 @@ function bootstrapWebsocketHandler(route) {
}
})
}
ws.render = (template, context) => {
context = context || {};
const eta = model.getTemplater(route.structure_id);
ws.render = (template, context = {}) => {
const structure = model.getStructure(route.structure_id);
context.route = function (url) {
return withPrefix(structure.route_prefix, url);
};
context.route = makeRoute(structure);
ws.send(
bootstrapTemplateWithHTMXetc(
eta.render(template, context),
`/workshop/${route.structure_id}/route/${route.id}`,
`/workshop/${route.structure_id}/clone_modal/`,
route.verb == "GET" ? embedHTML(req.originalUrl) : null,
structure.head_injection,
htmxRequest = true,
),
decorate(renderTemplate(route.structure_id, template, context), {
headInjection: structure.head_injection,
source: sourceFor(route, req),
fragment: true,
}),
);
};
@ -297,30 +289,10 @@ app.all("/logout", async (req, res) => {
// | _ || || | | || _ | _____| || _ || || |
// |__| |__||_______||___| |_||___| |_||_______||__| |__||_______||___|
//
function bootstrapTemplateWithHTMXetc(
htmlString,
blissRoute,
blissClone,
blissCopy,
headInjection,
htmxRequest = false,
) {
if (
htmlString.toLowerCase().startsWith("<html>") ||
htmlString.toLowerCase().startsWith("<!doctype") ||
!htmxRequest
) {
const $ = cheerio.load(htmlString);
let head = $("head");
if (head.length === 0) {
$("html").prepend("<head></head>");
head = $("head");
}
head.attr("id", "head");
head.append(`
// The chrome injected into the <head> of every rendered page: the client-side
// stack (htmx/hyperscript/tailwind) plus the in-page editor overlay.
function headChrome(headInjection) {
return `
<script src="/js/hyperscript.js"></script>
<script src="/js/tailwind.js"></script>
<script src="/js/htmx.js"></script>
@ -335,48 +307,96 @@ function bootstrapTemplateWithHTMXetc(
</script>
<style> body { margin: 0; }</style>
${headInjection || ""}
`);
`;
}
if (blissRoute) {
$("body").attr("data-bliss-route", blissRoute);
$("body").attr("data-bliss-clone", blissClone);
if (blissCopy) $("body").attr("data-bliss-copy", blissCopy);
// A `source` is the provenance of a rendered fragment: which structure/route
// produced it, and (for GET routes) the snippet that re-embeds it. It becomes
// the data-bliss-* attributes the inspector reads.
function blissAttrs(source) {
if (!source) return null;
const attrs = {
"data-bliss-route": `/workshop/${source.structureId}/route/${source.routeId}`,
"data-bliss-clone": `/workshop/${source.structureId}/clone_modal/`,
};
if (source.copyUrl) attrs["data-bliss-copy"] = source.copyUrl;
return attrs;
}
// Full HTML document: inject the head chrome and stamp provenance on <body>.
function decoratePage(html, { headInjection, source } = {}) {
const $ = cheerio.load(html);
let head = $("head");
if (head.length === 0) {
$("html").prepend("<head></head>");
head = $("head");
}
head.attr("id", "head");
head.append(headChrome(headInjection));
htmlString = $.html();
} else if (htmxRequest && blissRoute) {
const $ = cheerio.load(htmlString, null, false);
const attrs = blissAttrs(source);
if (attrs) {
for (const [k, v] of Object.entries(attrs)) $("body").attr(k, v);
}
return $.html();
}
// HTMX partial: stamp provenance on the swapped-in elements (an oob swap loses
// its own attrs, so we descend into its children) and append an out-of-band
// <head> so head injection still reaches the page.
function decorateFragment(html, { headInjection, source } = {}) {
const $ = cheerio.load(html, null, false);
const attrs = blissAttrs(source);
if (attrs) {
const targets = [];
// when we return an oob thing from htmx, we'll lose all the attrs when it swaps
// so this adds those attrs to the children of the swap if possible
for (let child of $.root().children()) {
for (const child of $.root().children()) {
if (child.attribs && child.attribs["hx-swap-oob"]) {
for (let childchild of child.children) {
if (childchild.attribs) {
targets.push(childchild);
}
for (const grandchild of child.children) {
if (grandchild.attribs) targets.push(grandchild);
}
} else {
targets.push(child);
}
}
// Directly update attributes without wrapping in Cheerio
for (let target of targets) {
target.attribs["data-bliss-route"] = blissRoute;
target.attribs["data-bliss-clone"] = blissClone;
if (blissCopy) target.attribs["data-bliss-copy"] = blissCopy;
for (const target of targets) Object.assign(target.attribs, attrs);
}
return (
$.html() +
`<head id="head" hx-oob-swap="beforeend">${headInjection || ""}</head>`
);
}
htmlString = $.html();
if (htmxRequest) {
htmlString += `<head id="head" hx-oob-swap="beforeend">${headInjection}</head>`;
}
// Single entry point for turning rendered HTML into a response body. A full
// document (or any non-htmx request) becomes a decorated page; an htmx partial
// carrying provenance becomes a decorated fragment; anything else passes through.
function decorate(html, { headInjection, source, fragment } = {}) {
const lower = html.toLowerCase();
const isFullDoc =
lower.startsWith("<html>") || lower.startsWith("<!doctype");
if (isFullDoc || !fragment) {
return decoratePage(html, { headInjection, source });
}
if (source) return decorateFragment(html, { headInjection, source });
return html;
}
return htmlString;
// One prefix-bound route() helper per structure, shared by every render site.
function makeRoute(structure) {
return (url) => withPrefix(structure.route_prefix, url);
}
function renderTemplate(structureId, templateName, context) {
return model.getTemplater(structureId).render(templateName, context);
}
// Provenance for a rendered response: GET routes are re-embeddable, so they
// carry the copy-embed snippet; other verbs don't.
function sourceFor(route, req) {
return {
structureId: route.structure_id,
routeId: route.id,
copyUrl: route.verb === "GET" ? embedHTML(req.originalUrl) : null,
};
}
app.post("/workshop", (req, res) => {
@ -391,7 +411,7 @@ app.get("/workshop", (req, res) => {
});
function renderWorkshop(res, template, context) {
res.send(bootstrapTemplateWithHTMXetc(eta.render(template, context)));
res.send(decorate(eta.render(template, context)));
}
function smartRedirect(req, res, redirectUrl) {
@ -599,24 +619,16 @@ app.get("/workshop/:structure_id/template/:template_id", (req, res) => {
app.get("/workshop/:structure_id/template/:template_id/preview", (req, res) => {
const template = model.getTemplate(req.params.template_id);
const struct = model.getStructure(req.params.structure_id);
const eta = model.getTemplater(req.params.structure_id);
const context = vm.createContext({ it: null });
vm.runInContext(template.test_object, context);
context.it.route = function (url) {
return withPrefix(struct.route_prefix, url);
};
context.it.route = makeRoute(struct);
return res.send(
bootstrapTemplateWithHTMXetc(
eta.render(template.name, context.it),
null,
null,
null,
struct.head_injection,
false,
),
decorate(renderTemplate(req.params.structure_id, template.name, context.it), {
headInjection: struct.head_injection,
}),
);
});
@ -647,21 +659,12 @@ app.get("/workshop/:structure_id/route/:route_id/preview", (req, res) => {
const route = model.getRoute(req.params.route_id);
const struct = model.getStructure(req.params.structure_id);
const it = {
route: function (url) {
return withPrefix(struct.route_prefix, url);
}
}
const it = { route: makeRoute(struct) };
return res.send(
bootstrapTemplateWithHTMXetc(
eta.renderString(route.scaffold_page_content, it),
null,
null,
null,
struct.head_injection,
false,
),
decorate(eta.renderString(route.scaffold_page_content, it), {
headInjection: struct.head_injection,
}),
);
});
@ -806,7 +809,6 @@ app.all("*", async (req, res) => {
.get(routeMatch.id);
const structure = model.getStructure(route.structure_id);
const __urlPrefix = structure.route_prefix;
try {
// todo: only add req res to contexst when running the handler()
@ -817,21 +819,14 @@ app.all("*", async (req, res) => {
eta,
});
res.render = (template, context) => {
context = context || {};
context.route = function (url) {
return withPrefix(__urlPrefix, url);
};
const eta = model.getTemplater(route.structure_id);
res.render = (template, context = {}) => {
context.route = makeRoute(structure);
res.send(
bootstrapTemplateWithHTMXetc(
eta.render(template, context),
`/workshop/${route.structure_id}/route/${route.id}`,
`/workshop/${route.structure_id}/clone_modal/`,
verb == "GET" ? embedHTML(req.originalUrl) : null,
structure.head_injection,
req.headers["hx-request"],
),
decorate(renderTemplate(route.structure_id, template, context), {
headInjection: structure.head_injection,
source: sourceFor(route, req),
fragment: req.headers["hx-request"],
}),
);
};
const handlerScript = new vm.Script(route.handler, { filename: `handler_${route.id}.js` });