${dev ? LADDER_JS : ""}
`;
}
function main() {
const CFG = readBuildConfig();
const manifestPath = join(ROOT2, "public", CFG.name, "manifest.json");
if (!existsSync2(manifestPath)) {
console.error(`[generate-gallery] missing ${manifestPath} \u2014 run \`bun run tree\` first`);
process.exit(1);
}
const manifest = JSON.parse(readFileSync3(manifestPath, "utf8"));
const tierMap = readSourceTiers();
const html = renderGallery(manifest, { mode: "build", title: CFG.title, tree: "../" + CFG.name, tokenUrl: CFG.tokenUrl, token: CFG.token, apiBaseUrl: CFG.apiBaseUrl, assetsBaseUrl: CFG.assetsBaseUrl, qaWidget: CFG.qaWidget, tierMap });
const baked = compact(manifest, true, tierMap);
const nProfiles = baked.reduce((n, b) => n + b.p.length, 0);
const nInstances = baked.reduce((n, b) => n + b.p.reduce((k, p) => k + (p.in ? p.in.length : 0), 0), 0);
const nOverrides = baked.reduce((n, b) => n + b.p.reduce((k, p) => k + (p.em ? 1 : 0) + (p.in || []).filter((x) => x.em).length, 0), 0);
const oi = process.argv.indexOf("--out");
const outDir = oi >= 0 && process.argv[oi + 1] ? resolve3(process.argv[oi + 1]) : null;
if (!outDir) {
console.log("[generate-gallery] ARCHIVED \u2014 not written. The gallery is no longer part of the build or of public/;");
console.log(" the Plugin Studio (/index.html) is the front page. To render it anyway: bun run gallery -- --out ");
return;
}
if (outDir === resolve3(ROOT2, "public") || outDir.startsWith(resolve3(ROOT2, "public") + "/")) {
console.error("[generate-gallery] refusing to write into public/ \u2014 it holds only the output folder.");
process.exit(1);
}
const dir = outDir;
mkdirSync(dir, { recursive: true });
writeFileSync(join(dir, "index.html"), html);
console.log(`[generate-gallery] ${dir}/index.html (ARCHIVED rendering) \u2014 ${(html.length / 1024).toFixed(1)} KB \xB7 ${baked.length} bundle(s) \xB7 ${nProfiles} profile(s) \xB7 ${nInstances} instance(s) \xB7 ${nOverrides} embed snippet(s) baked verbatim (the rest re-derive)`);
}
if (process.argv[1] && __vite_injected_original_import_meta_url3 === pathToFileURL(process.argv[1]).href) main();
// internal/profile-meta.mjs
import { readFileSync as readFileSync4 } from "node:fs";
import { join as join2 } from "node:path";
var CORNERS = ["bottom-right", "bottom-left", "top-right", "top-left"];
var VISIBILITY = ["team", "public"];
var MOBILE = ["desktop-only", "larger-screens"];
function readProfileMeta(dir, profileName) {
try {
const meta = JSON.parse(readFileSync4(join2(dir, `${profileName}.meta.json`), "utf8"));
const corner = meta?.widget?.corner;
const name = [meta?.label, meta?.title, meta?.displayName].find((v) => typeof v === "string" && v.trim());
return {
stage: meta?.stage === "production" ? "production" : "experimental",
heavy: meta?.heavy === true,
widget: CORNERS.includes(corner) ? { corner } : null,
label: name ? name.trim() : "",
// Where the plugin runs ("web", "desktop"…); absent = web. A desktop-only plugin is never a gallery tab.
targets: Array.isArray(meta?.targets) ? meta.targets.filter((t) => typeof t === "string") : null,
visibility: VISIBILITY.includes(meta?.visibility) ? meta.visibility : "team",
// The declared-but-unrecognised value, kept so the generator can name it in the failure.
// null when the key is absent (the supported way to say "team") or already valid.
visibilityInvalid: meta?.visibility === void 0 || VISIBILITY.includes(meta?.visibility) ? null : String(meta.visibility),
mobile: MOBILE.includes(meta?.mobile) ? meta.mobile : null
};
} catch {
return { stage: "experimental", heavy: false, widget: null, label: "", targets: null, visibility: "team", visibilityInvalid: null, mobile: null };
}
}
function profileMetaFields(dir, profileName) {
const m = readProfileMeta(dir, profileName);
return { stage: m.stage, ...m.visibility === "public" ? { visibility: "public" } : {}, ...m.label ? { label: m.label } : {}, ...m.targets ? { targets: m.targets } : {}, ...m.heavy ? { heavy: true } : {}, ...m.widget ? { widget: m.widget } : {}, ...m.mobile ? { mobile: m.mobile } : {} };
}
var bundleStage = (profiles) => profiles.some((p) => p.stage === "production") ? "production" : "experimental";
var bundleVisibility = (profiles) => profiles.some((p) => p.visibility === "public") ? "public" : "team";
// internal/plugin-gallery/dev-plugin.mjs
var DEV_ASSET_MIME = {
".json": "application/json; charset=utf-8",
".geojson": "application/geo+json; charset=utf-8",
".js": "text/javascript; charset=utf-8",
".css": "text/css; charset=utf-8",
".csv": "text/csv; charset=utf-8",
".txt": "text/plain; charset=utf-8",
".html": "text/html; charset=utf-8",
".svg": "image/svg+xml",
".png": "image/png",
".webp": "image/webp",
".jpg": "image/jpeg",
".jpeg": "image/jpeg",
".gif": "image/gif",
".woff2": "font/woff2",
".woff": "font/woff",
".pdf": "application/pdf"
};
function resolveDevAsset(rel, root = REPO_ROOT) {
const isFile = (p) => existsSync3(p) && statSync2(p).isFile();
const source = resolve4(root, "data", rel);
if (isFile(source)) return source;
const shipped = resolve4(root, "public", TREE_NAME, "assets", rel);
if (isFile(shipped)) return shipped;
const profiles = resolve4(root, "plugin-designs");
for (const name of readdirSync3(profiles)) {
if (name.startsWith("_") || name.startsWith(".")) continue;
const cand = resolve4(profiles, name, "data", rel);
if (isFile(cand)) return cand;
}
return null;
}
function readComposes(bundle, profile) {
try {
const meta = JSON.parse(readFileSync5(resolve4(REPO_ROOT, "plugin-designs", bundle, `${profile}.meta.json`), "utf8"));
return Array.isArray(meta?.composes) ? meta.composes.map(String).filter(Boolean) : [];
} catch {
return [];
}
}
function galleryModel(reg) {
const iframe = (u) => ``;
const link = (...parts) => "/__leaf/" + parts.map(encodeURIComponent).join("/");
let gated = /* @__PURE__ */ new Set();
try {
const cfg = JSON.parse(readFileSync5(resolve4(REPO_ROOT, "vite.doxa-maps-build-config.json"), "utf8"));
if (Array.isArray(cfg.gatedProfiles)) gated = new Set(cfg.gatedProfiles.map(String));
} catch {
}
const bundles = [...reg].filter((b) => !gated.has(b.bundle)).sort((a, b) => a.bundle.localeCompare(b.bundle)).map((b) => {
const scriptUrl = `/plugin-designs/${b.bundle}/index.js`;
const presets = /* @__PURE__ */ new Map();
const typeOf = /* @__PURE__ */ new Map();
for (const it of b.items) {
if (!presets.has(it.profile)) presets.set(it.profile, []);
if (!typeOf.has(it.profile)) typeOf.set(it.profile, it.type);
if (it.type === "parameterized") presets.get(it.profile).push(it);
}
const profiles = [...presets.keys()].sort().map((profile) => {
const p = {
profile,
file: `${profile}.vue`,
description: "",
element: b.tag,
type: typeOf.get(profile) || "single",
...profileMetaFields(resolve4(REPO_ROOT, "plugin-designs", b.bundle), profile),
// same as the built manifest
iframeUrl: link(b.bundle, profile),
scriptUrl,
embed: {
iframe: iframe(`./${b.bundle}/${profile}/index.html`),
script: `
<${b.tag} profile-config='${JSON.stringify({ profile, tk: "YOUR_MAPBOX_TOKEN", utmSource: "YOUR_SOURCE" })}'>${b.tag}>`
}
};
const instances = presets.get(profile).map((it) => ({
id: it.id,
title: it.label,
apiBaseUrl: it.apiBaseUrl || "",
props: { profile, ...it.props || {} },
element: b.tag,
iframeUrl: link(b.bundle, profile, it.id),
embed: { iframe: iframe(`./${b.bundle}/${profile}/${it.id}/index.html`) }
}));
if (instances.length) p.instances = instances;
return p;
});
const composes = [...new Set([...presets.keys()].flatMap((profile) => readComposes(b.bundle, profile)))];
return { bundle: b.bundle, scriptUrl, integrity: "", elements: [b.tag], stage: bundleStage(profiles), ...bundleVisibility(profiles) === "public" ? { visibility: "public" } : {}, ...composes.length ? { composes } : {}, profiles };
});
return {
schema: "doxa-maps-manifest@1",
generatedFrom: "plugin-designs/*/*.vue (dev discovery)",
embedBase: ".",
bundleCount: bundles.length,
profileCount: bundles.reduce((n, b) => n + b.profiles.length, 0),
bundles
};
}
function leafRewrite(path, treeName = TREE_NAME) {
if (!path.endsWith("/index.html")) return "";
let segs = path.slice(0, -"/index.html".length).split("/").filter(Boolean);
if (segs[0] === treeName) segs = segs.slice(1);
if (segs.length < 2 || segs.length > 3) return "";
for (const s of segs) {
let d = s;
try {
d = decodeURIComponent(s);
} catch {
return "";
}
if (!d || d === "." || d === ".." || d.includes("/") || d.includes("\\")) return "";
}
return "/__leaf/" + segs.join("/");
}
function autoSpaDevPlugin(token = "") {
const page = () => {
const cfg = readBuildConfig();
return renderGallery(galleryModel(discoverSpaRegistry()), {
// ★ `/__plugin-gallery`, ABSOLUTE — the SAME move, and the same fix, as assetsBaseUrl below.
// `tree` is the folder the live manifest is fetched from and that relative refs resolve
// against. It was '__plugin-gallery' (relative), correct only while the gallery WAS the root
// page. The gallery now lives at /__gallery, so the relative form resolves one level deeper.
// MEASURED 2026-09-22 on the running dev server:
// /__plugin-gallery/manifest.json ............. 200
// /__gallery/__plugin-gallery/manifest.json ... 404
// Naming it absolutely makes the value independent of the page depth the gallery sits at —
// which is the whole reason assetsBaseUrl was made absolute in the same move. The built tree
// passes '../' for the same reason; both now resolve to the real tree root.
mode: "dev",
title: cfg.title,
tree: "/__plugin-gallery",
tokenUrl: cfg.tokenUrl,
token: token || cfg.token,
// ★ ABSOLUTE, NOT RELATIVE — the gallery no longer lives at the root.
// This was '' (relative), which was correct while the gallery WAS the root page: a map asked
// for assets/tables/x.json and got /assets/tables/x.json. The gallery has since moved to
// /__gallery so the studio can own /, and the same relative path now resolves one level deeper
// to /__gallery/assets/... — which 404s. MEASURED 2026-09-22 on the running dev server:
// /assets/tables/internet-by-country.json ............. 200
// /__gallery/assets/tables/internet-by-country.json ... 404
// Nothing failed at move time; the breakage only appears when a map FETCHES, and it surfaces as
// "JSON.parse: unexpected end of data" rather than as a 404, because Vite answers an unknown
// path with HTML. The dev server serves the asset tree at /assets/ (see resolveDevAsset), so
// naming it absolutely makes the value independent of whatever page depth the gallery sits at.
apiBaseUrl: cfg.apiBaseUrl,
assetsBaseUrl: "/assets",
qaWidget: cfg.qaWidget
});
};
const json = (res, body, status = 200) => {
res.statusCode = status;
res.setHeader("Content-Type", "application/json; charset=utf-8");
res.setHeader("Cache-Control", "no-cache");
res.end(JSON.stringify(body));
};
return {
name: "doxa-auto-spa",
apply: "serve",
configureServer(server) {
server.middlewares.use((req, res, next) => {
const rawPath = (req.url || "").split("?")[0];
let _decoded = rawPath;
try {
_decoded = decodeURIComponent(rawPath);
} catch {
}
if (/(^|\/)\.(git|env)(\/|$)|(^|\/)\.(merge-queue|main-capture)(\/|$)/i.test(_decoded)) {
res.statusCode = 404;
return res.end();
}
let treeName = TREE_NAME;
try {
treeName = readBuildConfig()?.name || treeName;
} catch {
}
const rewritten = leafRewrite(rawPath, treeName);
const path = rewritten || rawPath;
if (rewritten) req.url = rewritten + (req.url || "").slice(rawPath.length);
const iife = /^\/(?:([^/]+)\/)?([a-z0-9][a-z0-9-]*)\/([a-z0-9][a-z0-9-]*)\.js$/.exec(rawPath);
if (iife && iife[2] === iife[3] && (!iife[1] || iife[1] === treeName)) {
const file = resolve4(REPO_ROOT, "public", treeName, iife[2], iife[2] + ".js");
if (existsSync3(file)) {
res.setHeader("Content-Type", "text/javascript; charset=utf-8");
res.setHeader("Cache-Control", "no-cache");
return res.end(readFileSync5(file));
}
if (existsSync3(resolve4(REPO_ROOT, "plugin-designs", iife[2]))) {
res.statusCode = 404;
res.setHeader("Content-Type", "text/plain; charset=utf-8");
return res.end(`${iife[2]}.js is only produced by a build: run \`bun run build\` (or BUNDLE=${iife[2]} bun x vite build), then reload.`);
}
}
const leafAsset = /^\/__leaf\/(?:[^/]+\/){0,3}assets\//.exec(path);
const assetPrefix = path.startsWith("/assets/") ? "/assets/" : path.startsWith("/__plugin-gallery/assets/") ? "/__plugin-gallery/assets/" : path.startsWith(`/${treeName}/assets/`) ? `/${treeName}/assets/` : leafAsset ? leafAsset[0] : "";
if (assetPrefix) {
let rel = "";
try {
rel = decodeURIComponent(path.slice(assetPrefix.length));
} catch {
rel = "";
}
if (!rel || rel.split("/").includes("..")) {
res.statusCode = 400;
return res.end("bad asset path");
}
const file = resolveDevAsset(rel);
if (!file) return json(res, { error: "asset not found", asset: rel, looked: [`public/${TREE_NAME}/assets/`, "data/", "plugin-designs/*/data/"] }, 404);
res.statusCode = 200;
res.setHeader("Content-Type", DEV_ASSET_MIME[extname(file).toLowerCase()] || "application/octet-stream");
res.setHeader("Cache-Control", "no-cache");
return res.end(readFileSync5(file));
}
if (path === "/__dev-sha") {
const git2 = (...a) => spawnSync("git", a, { cwd: REPO_ROOT, encoding: "utf8" }).stdout.trim();
const sha = git2("rev-parse", "HEAD");
const dirty = git2("status", "--porcelain", "--untracked-files=no", "--", "library", "plugin-designs", "internal", "vite.config.js", "package.json") ? "+dirty" : "";
res.statusCode = sha ? 200 : 500;
res.setHeader("Content-Type", "text/plain; charset=utf-8");
res.setHeader("Cache-Control", "no-store");
return res.end((sha || "unknown") + dirty + "\n");
}
if (path === "/__plugin-gallery/registry.json" || path === "/__plugin-gallery/manifest.json") {
let reg;
try {
reg = discoverSpaRegistry();
} catch (e) {
return json(res, { error: String(e) }, 500);
}
return json(res, path.endsWith("/manifest.json") ? galleryModel(reg) : reg);
}
if (path.startsWith("/__leaf/")) {
const parts = path.slice("/__leaf/".length).split("/").filter(Boolean).map((s) => {
try {
return decodeURIComponent(s);
} catch {
return "";
}
});
const [bundle, profile, instance] = parts;
if (!bundle || !profile || parts.some((s) => !s || s.includes("..") || s.includes("/"))) {
res.statusCode = 400;
return res.end("bad leaf path");
}
let reg;
try {
reg = discoverSpaRegistry();
} catch (e) {
return json(res, { error: String(e) }, 500);
}
const entry = reg.find((b) => b.bundle === bundle);
if (!entry) {
const named = reg.find((b) => b.bundle === profile);
const hint = named ? `this plugin's own page is /__leaf/${named.bundle}/${named.items?.[0]?.profile || named.bundle}` : "a plugin's own page is /__leaf//; every plugin is listed at /__plugin-gallery/manifest.json";
return json(res, { error: "unknown bundle", bundle, hint }, 404);
}
const tag = entry.tag;
let leafCfg = {};
try {
leafCfg = readBuildConfig() || {};
} catch {
leafCfg = {};
}
const leafTok = token || leafCfg.token || "";
const apiBase = leafCfg.apiBaseUrl || "";
const pc = JSON.stringify(instance ? { profile, instanceId: instance, assetsBase: "/assets" } : { profile, assetsBase: "/assets" });
const scrollPage = (entry.items || []).some(
(it) => it.profile === profile && it.type === "nested-elements-mfe"
);
res.statusCode = 200;
res.setHeader("Content-Type", "text/html; charset=utf-8");
res.setHeader("Cache-Control", "no-cache");
return res.end([
"",
`${bundle} \xB7 ${profile}`,
'',
'',
leafTok ? `` : "",
// ★ MAP_APP_API_URL IS NOT OPTIONAL — without it every dashboard renders EMPTY in dev.
// The BUILT leaves bake `window.MAP_APP_API_URL = "https://pray.doxa.life"`; these dev
// pages baked only the Mapbox token, so the dashboards fell back to a RELATIVE
// `/api/people-groups/list?...`. Vite answers an unknown path with SPA-fallback HTML at
// HTTP 200, so the fetch "succeeds", JSON.parse fails or yields nothing, and the facet
// rail renders ZERO rows with no console error and no error card. Measured 2026-09-22:
// 8 such 404/fallback requests during one studio sweep. That reads as a broken component
// and is really a missing global — the worst kind of dev-only divergence, and exactly the
// four-surface class (see REFERENCE-the-four-surface-contract.md).
apiBase ? `` : "",
// ── FIXED-VIEWPORT LEAF vs SCROLL-PAGE LEAF ────────────────────────────────────────
// The BUILT tree makes exactly this distinction (internal/generate-tree.mjs, `scrollPage`)
// and this dev leaf did NOT — it served the fixed-viewport style to EVERY bundle. That is
// the whole of BUG-CANNOT-SCROLL-DOWN-TO-THE-GRID-UNDER-THE-DASHBOARD: the three-file fix
// for BUG-DASHBOARD-CANT-SCROLL-DOWN landed in the built tree only, so the dev URL that
// actually gets tested was never covered and still traps the composite.
//
// WHY `height:100%` ON BODY IS THE TRAP, and why scrollHeight LIES about it. A definite
// height + `overflow:visible` lets content SPILL (so scrollHeight inflates — measured 3531
// against a 900 viewport) WITHOUT creating a scrollport, so the viewport has nothing to
// scroll. The research-dashboard's own inner `overflow:auto` regions then swallow every
// wheel event. MEASURED 2026-09-22 on this leaf: 12 wheel events of 600px moved the page
// 0px while 2,631px of content sat below the fold. A geometry check calls that "scrollable";
// only a real wheel exposes it.
//
// A cap-4 composite (nested-elements-mfe) is a StackLayout scroll-stack, so the DOCUMENT
// must own the scroll: `min-height` (a floor, not a cage) + explicit `overflow-y:auto`,
// and the element takes its NATURAL height so the page grows and the grid below is
// reachable. Every other tier keeps the fixed-viewport leaf — a map IS the page.
scrollPage ? `` : ``,
"",
// tree-base ON THE LEAF TOO — cap-5 shells nested here fetch a manifest of their own.
// doxa-maps-spa (like plugin-studio) discovers its tabs from the tree's manifest.json and
// resolves it RELATIVE to its own page when no base is given. On a /__leaf/ URL that
// resolved to /__leaf/manifest.json and 400'd. Harmless (both shells carry a baked
// fallback list) but it silently degrades the tab list to the fallback instead of the
// live 19-bundle tree. Passing the base makes a nested shell see exactly what the top
// level sees. Elements that ignore tree-base simply drop an unknown attribute.
`<${tag} tree-base="/__plugin-gallery" profile-config='${pc}'>${tag}>`,
``,
""
].filter(Boolean).join("\n"));
}
if (path === "/" || path === "/index.html") {
let devCfg = {};
try {
devCfg = readBuildConfig() || {};
} catch {
devCfg = {};
}
const devTok = token || devCfg.token || "";
const devApiBase = devCfg.apiBaseUrl || "";
res.statusCode = 200;
res.setHeader("Content-Type", "text/html; charset=utf-8");
res.setHeader("Cache-Control", "no-cache");
return res.end([
"",
'',
'',
`${TREE_NAME} plugin studio`,
'',
// The token ladder's baked rung, exactly as the built leaves receive it, so maps paint
// in dev without anyone appending ?tk=.
devTok ? `` : "",
// Same reason as the leaf route above: without MAP_APP_API_URL the dashboards nested in
// the studio fetch a relative /api/... that Vite answers with HTML at 200, so they mount
// and then show nothing. The value comes from the SAME build config the built leaves use,
// so dev and built agree by construction rather than by a second hard-coded copy.
devApiBase ? `` : "",
// No chrome, no prose: the element fills the viewport. "Without a lot of text garb
// that takes up so much space" is a layout requirement, so the host page contributes
// ZERO text — every pixel belongs to the studio element.
"",
"",
// tree-base IS REQUIRED IN DEV — without it the studio mounts EMPTY.
// The studio builds its tab list at runtime by fetching `manifest.json`, and with
// no base it falls back to '../manifest.json' RELATIVE TO ITS SCRIPT URL. In dev the
// script is /plugin-designs/plugin-studio/index.js, so that resolves to
// /plugin-designs/manifest.json — which does not exist. The shell then renders with zero
// rows: 31 shadow descendants, no nested plugin tags. (Measured exactly that before this
// line existed; the symptom reads as "no elements in the studio", not as a failed fetch.)
// The dev server's manifest lives on its own route instead, serving the SAME
// manifest-shaped model the built tree bakes (19 bundles / 30 profiles), so pointing the
// studio at it gives dev the same tab list production has.
'',
'',
""
].filter(Boolean).join("\n"));
}
if (path !== "/__gallery" && path !== "/__gallery/") return next();
let html;
try {
html = page();
} catch (e) {
console.error(`[plugin-gallery] ${e && e.stack ? e.stack : e}`);
return next();
}
res.statusCode = 200;
res.setHeader("Content-Type", "text/html; charset=utf-8");
res.end(html);
});
}
};
}
// internal/vite-plugin-strip-shadow-css-comments.mjs
import { resolve as resolve5, dirname as dirname4, sep } from "node:path";
import { fileURLToPath as fileURLToPath4 } from "node:url";
var __vite_injected_original_import_meta_url4 = "file:///home/lk10x/Workspace/eco-dx/root-doxa-maps/wt-hf/internal/vite-plugin-strip-shadow-css-comments.mjs";
var ROOT3 = resolve5(dirname4(fileURLToPath4(__vite_injected_original_import_meta_url4)), "..");
var SOURCE_ROOTS = ["library", "plugin-designs"].map((d) => resolve5(ROOT3, d) + sep);
var TAG = "[strip-shadow-css-comments]";
var CALL = /\buseShadowStyles\(\s*(?:[A-Za-z_$][\w$]*\s*\+\s*)*`|\b(?:export\s+)?const\s+[A-Z][A-Z0-9_]*(?:_CSS|_STYLES)\s*=\s*`/g;
function findTemplateEnd(code, from) {
for (let i = from; i < code.length; i++) {
const c = code[i];
if (c === "\\") {
i++;
continue;
}
if (c === "`") return { end: i };
if (c === "$" && code[i + 1] === "{") return { reason: "contains a ${\u2026} substitution", at: i };
}
return { reason: "template literal never closes", at: code.length };
}
function stripCssComments(css) {
const n = css.length;
let out = "";
let comments = 0;
let i = 0;
while (i < n) {
const c = css[i];
if (c === '"' || c === "'") {
let j = i + 1;
while (j < n && css[j] !== c && css[j] !== "\n") {
if (css[j] === "\\") j++;
j++;
}
if (j >= n) return { reason: "a CSS string never closes" };
if (css[j] === "\n") {
out += css.slice(i, j);
i = j;
continue;
}
out += css.slice(i, j + 1);
i = j + 1;
continue;
}
if (c === "/" && css[i + 1] === "*") {
const j = css.indexOf("*/", i + 2);
if (j < 0) return { reason: "a CSS comment never closes" };
comments++;
i = j + 2;
continue;
}
if ((c === "u" || c === "U") && /^url\(/i.test(css.slice(i, i + 4)) && (i === 0 || !/[\w-]/.test(css[i - 1]))) {
let j = i + 4;
while (j < n && (css[j] === " " || css[j] === " ")) j++;
if (css[j] === '"' || css[j] === "'") {
out += css.slice(i, j);
i = j;
continue;
}
const k = css.indexOf(")", j);
if (k < 0) return { reason: "an unquoted url() never closes" };
out += css.slice(i, k + 1);
i = k + 1;
continue;
}
out += c;
i++;
}
if (comments > 0) {
out = out.replace(/[ \t]+(?=\r?\n|$)/g, "");
out = out.replace(/\r?\n(?:[ \t]*\r?\n)+/g, "\n");
}
return { css: out, comments };
}
function stripShadowStyleComments(code) {
const result = { code, changed: false, literals: 0, comments: 0, bytes: 0, skipped: [] };
let out = "";
let last = 0;
CALL.lastIndex = 0;
let m;
while (m = CALL.exec(code)) {
const open = m.index + m[0].length;
const lit = findTemplateEnd(code, open);
if (lit.reason) {
result.skipped.push({ line: lineOf(code, m.index), reason: lit.reason });
CALL.lastIndex = lit.at;
continue;
}
result.literals++;
const original = code.slice(open, lit.end);
const stripped = stripCssComments(original);
if (stripped.reason) {
result.skipped.push({ line: lineOf(code, m.index), reason: stripped.reason });
} else if (stripped.css !== original) {
out += code.slice(last, open) + stripped.css;
last = lit.end;
result.comments += stripped.comments;
result.bytes += original.length - stripped.css.length;
result.changed = true;
}
CALL.lastIndex = lit.end + 1;
}
if (result.changed) result.code = out + code.slice(last);
return result;
}
function lineOf(code, index) {
let line = 1;
for (let i = 0; i < index; i++) if (code[i] === "\n") line++;
return line;
}
function isSourceModule(id) {
const q = id.indexOf("?");
if (q >= 0) return false;
if (!(id.endsWith(".vue") || id.endsWith(".js"))) return false;
return SOURCE_ROOTS.some((root) => id.startsWith(root));
}
function stripShadowCssCommentsPlugin() {
let logger = console;
const totals = { files: 0, literals: 0, comments: 0, bytes: 0, skipped: 0 };
return {
name: "doxa:strip-shadow-css-comments",
apply: "build",
enforce: "pre",
configResolved(config) {
logger = config.logger;
},
transform(code, id) {
if (!isSourceModule(id)) return null;
const r = stripShadowStyleComments(code);
for (const s of r.skipped) {
totals.skipped++;
logger.warn(`${TAG} left untouched ${id.slice(ROOT3.length + 1)}:${s.line} \u2014 ${s.reason}`);
}
if (!r.changed) return null;
totals.files++;
totals.literals += r.literals;
totals.comments += r.comments;
totals.bytes += r.bytes;
return { code: r.code, map: null };
},
buildEnd() {
logger.info(`${TAG} removed ${totals.comments} CSS comments (${totals.bytes} bytes) from ${totals.literals} shadow-style literals in ${totals.files} files` + (totals.skipped ? `; ${totals.skipped} literal(s) left untouched` : ""));
}
};
}
// internal/backend-dev-plugin.mjs
import { spawn } from "node:child_process";
import { existsSync as existsSync4, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2, rmSync, chmodSync, renameSync, createWriteStream } from "node:fs";
import { createHash } from "node:crypto";
import { connect } from "node:net";
import { inflateRawSync } from "node:zlib";
import { resolve as resolve6 } from "node:path";
var PB_VERSION = "0.40.4";
var PB_SHA256 = {
linux_amd64: "9042ec818570e79c3628dadcd0a756c1496d9e1173918ec409d133c02f82e5fa",
linux_arm64: "86095bf8ed9345954f0d2bf0a5fb9b57584ae60b77ebf3b6cd23a8003a3fd418",
darwin_amd64: "052906521f09f6f23405cd804c930d2b7a1f8eee06d39b85723b5054b2acb4e4",
darwin_arm64: "eeb619ea4f8a06421daedb946d133bed269fea334a760941d147f76befc25ebc"
};
function devBackends(env = process.env) {
const pbPort = Number(env.DX_PB_PORT || 8090);
return [
{
name: "rudder-engine",
bin: "backend/bin/rudder-engine",
build: "cd backend/rudder-engine && go build -o ../bin/rudder-engine .",
// What a fresh clone is told at startup when this backend is not built: it is OPTIONAL (desktop voice).
optional: "optional: Rudder (desktop voice-to-text) is not built, which is fine for the maps. It needs Go 1.22+; see documentation/HOWTO-build-the-desktop-app.md.",
route: "/api/rudder",
port: Number(process.env.RUDDER_PORT || 5193),
env: { RUDDER_PORT: String(process.env.RUDDER_PORT || 5193), RUDDER_NEMO_PORT: String(process.env.RUDDER_NEMO_PORT || 5194) },
// This server is SUPPOSED to run the engine (a server deployment with the engine switched on): while it
// is down, the plugin says "not running yet" instead of "runs on your own computer".
expected: () => process.env.RUDDER_ENGINE === "1" || process.env.RUDDER_AUDIO === "browser",
// RUDDER_ENGINE=0 (a server where Rudder is laptop-only, 2026-09-27): never start it, even if a
// binary built by an earlier engine-on deploy is still there — it would offer to record a server's mic.
disabled: () => process.env.RUDDER_ENGINE === "0"
},
{
// Hooks off (a hooks dir that never exists): only the migrations in git shape the schema, as in the cloud.
// DX_PB_URL points this laptop at a shared PocketBase instead (e.g. the cloud pb-dev); none starts here.
name: "pocketbase",
bin: "backend/bin/pocketbase",
args: ["serve", "--http", `127.0.0.1:${pbPort}`, "--dir", ".pb/dev", "--migrationsDir", "backend/pocketbase/pb_migrations", "--hooksDir", ".pb/hooks-off"],
build: "it downloads on the next `bun run dev` (or set DX_PB_URL)",
fetch: fetchPocketBase,
portEnv: "DX_PB_PORT",
route: "/api/pb",
rewrite: (p) => p.replace(/^\/api\/pb/, "") || "/",
port: pbPort,
remote: env.DX_PB_URL || "",
offline: (res) => {
res.writeHead(503, { "Content-Type": "application/json" });
res.end(JSON.stringify({ status: 503, message: "PocketBase is not running on this computer (yet). See the dev server log." }));
}
}
];
}
async function fetchPocketBase(root, logger) {
const plat = { "linux-x64": "linux_amd64", "linux-arm64": "linux_arm64", "darwin-x64": "darwin_amd64", "darwin-arm64": "darwin_arm64" }[`${process.platform}-${process.arch}`];
if (!plat) {
logger.warn(`[pocketbase] no pinned build for ${process.platform}-${process.arch}: put PocketBase ${PB_VERSION} at backend/bin/pocketbase, or set DX_PB_URL`);
return false;
}
const name = `pocketbase_${PB_VERSION}_${plat}.zip`;
const dir = resolve6(root, "backend/bin");
const part = resolve6(dir, `.pocketbase.part`);
logger.info(`[pocketbase] downloading ${name} (first run only)`);
try {
mkdirSync2(dir, { recursive: true });
const r = await fetch(`https://github.com/pocketbase/pocketbase/releases/download/v${PB_VERSION}/${name}`);
if (!r.ok) throw new Error(`HTTP ${r.status}`);
const buf = Buffer.from(await r.arrayBuffer());
const sha = createHash("sha256").update(buf).digest("hex");
if (sha !== PB_SHA256[plat]) throw new Error(`checksum mismatch (${sha}): refusing to use it`);
writeFileSync2(part, unzipOne(buf, "pocketbase"));
chmodSync(part, 493);
renameSync(part, resolve6(dir, "pocketbase"));
return true;
} catch (e) {
logger.warn(`[pocketbase] download failed (${e.message}); /api/pb answers 503 until the next \`bun run dev\``);
return false;
} finally {
rmSync(part, { force: true });
}
}
function unzipOne(buf, want) {
const end = buf.lastIndexOf(Buffer.from([80, 75, 5, 6]));
if (end < 0) throw new Error("not a zip");
for (let i = 0, p = buf.readUInt32LE(end + 16); i < buf.readUInt16LE(end + 10); i++) {
const method = buf.readUInt16LE(p + 10), size = buf.readUInt32LE(p + 20);
const nameLen = buf.readUInt16LE(p + 28), local = buf.readUInt32LE(p + 42);
if (buf.toString("utf8", p + 46, p + 46 + nameLen) === want) {
const start2 = local + 30 + buf.readUInt16LE(local + 26) + buf.readUInt16LE(local + 28);
const data = buf.subarray(start2, start2 + size);
if (method === 8) return inflateRawSync(data);
if (method === 0) return data;
throw new Error(`unsupported zip method ${method}`);
}
p += 46 + nameLen + buf.readUInt16LE(p + 30) + buf.readUInt16LE(p + 32);
}
throw new Error(`${want} not in the zip`);
}
var portInUse = (port) => new Promise((ok) => {
const s = connect({ host: "127.0.0.1", port }, () => {
s.destroy();
ok(true);
});
s.on("error", () => ok(false));
});
function devBackendProxy(env = process.env) {
return Object.fromEntries(devBackends(env).map((b) => [b.route, {
target: b.remote || `http://127.0.0.1:${b.port}`,
changeOrigin: !!b.remote,
ws: true,
...b.rewrite ? { rewrite: b.rewrite } : {}
}]));
}
var REG = globalThis.__dxDevBackends ||= { procs: /* @__PURE__ */ new Map(), exitHooked: false, stopping: false };
var isAlive = (name) => {
const e = REG.procs.get(name);
return !!e && e.child.exitCode === null && e.child.signalCode === null;
};
function stopAll() {
REG.stopping = true;
for (const { child } of REG.procs.values()) {
try {
child.kill("SIGTERM");
} catch {
}
}
}
function start(root, b, logger, restarts = 0) {
const tag = `[${b.name}]`;
const child = spawn(resolve6(root, b.bin), b.args || [], { cwd: root, env: { ...process.env, ...b.env }, stdio: ["ignore", "pipe", "pipe"] });
const entry = { child, restarts, logger };
REG.procs.set(b.name, entry);
const logDir = resolve6(root, ".pb", "logs");
mkdirSync2(logDir, { recursive: true });
const sink = createWriteStream(resolve6(logDir, `${b.name}.log`), { flags: "a" });
const LOUD = /\b(error|fail(ed|ure)?|panic|fatal|warn(ing)?|refused|denied|in use|cannot|unable|superuser|pbinstall?)\b/i;
const out = (d) => {
sink.write(d);
String(d).trim().split("\n").forEach((l) => l && LOUD.test(l) && entry.logger.info(`${tag} ${l}`));
};
child.stdout.on("data", out);
child.stderr.on("data", out);
child.on("exit", () => sink.end());
child.on("exit", (code) => {
if (REG.procs.get(b.name) !== entry) return;
REG.procs.delete(b.name);
if (REG.stopping) return;
entry.logger.warn(`${tag} exited (${code})`);
if (entry.restarts < 5) {
setTimeout(() => {
if (REG.stopping || REG.procs.has(b.name)) return;
entry.logger.warn(`${tag} restarting (${entry.restarts + 1}/5)`);
start(root, b, entry.logger, entry.restarts + 1);
}, 2e3);
}
});
logger.info(`${tag} started \u2192 ${b.route} on 127.0.0.1:${b.port} (full log: .pb/logs/${b.name}.log)`);
}
function backendDevPlugin(root, env = process.env) {
const backends = devBackends(env);
return {
name: "dx-backend-dev",
apply: "serve",
configureServer(server) {
for (const b of backends) {
server.middlewares.use(b.route, (req, res, next) => {
if (b.remote || isAlive(b.name)) return next();
if (b.offline) return b.offline(res);
const status = { engine: "offline", expected: !!(b.expected && b.expected()), error: "The desktop engine is not running." };
if ((req.url || "").startsWith("/events")) {
res.writeHead(200, { "Content-Type": "text/event-stream", "Cache-Control": "no-cache", Connection: "keep-alive" });
res.write(`data: ${JSON.stringify({ type: "status", status })}
`);
return;
}
if ((req.url || "").startsWith("/status")) {
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify(status));
return;
}
res.writeHead(200, { "Content-Type": "application/json" });
res.end("[]");
});
}
const logger = server.config.logger;
if (!REG.exitHooked) {
REG.exitHooked = true;
process.once("exit", stopAll);
process.once("SIGINT", () => {
stopAll();
process.exit(0);
});
process.once("SIGTERM", () => {
stopAll();
process.exit(0);
});
}
for (const b of backends) {
if (b.remote) {
logger.info(`[${b.name}] not started here: ${b.route} \u2192 ${b.remote}`);
continue;
}
if (isAlive(b.name)) {
REG.procs.get(b.name).logger = logger;
logger.info(`[${b.name}] still running \u2192 ${b.route} (kept across the dev-server restart)`);
continue;
}
if (b.disabled && b.disabled()) {
logger.info(`[backend] ${b.name} switched off here (${b.name === "rudder-engine" ? "RUDDER_ENGINE=0" : "disabled"})`);
continue;
}
const launch = async () => {
if (b.portEnv && await portInUse(b.port)) {
logger.warn(`[${b.name}] port ${b.port} is already in use (another \`bun run dev\`?): not started. Set ${b.portEnv} to use another port.`);
return;
}
if (!REG.stopping && !isAlive(b.name)) start(root, b, logger);
};
if (!existsSync4(resolve6(root, b.bin))) {
if (b.fetch) {
REG.fetching ||= /* @__PURE__ */ new Set();
if (REG.fetching.has(b.name)) continue;
REG.fetching.add(b.name);
b.fetch(root, logger).then((ok) => {
REG.fetching.delete(b.name);
if (ok) launch();
});
continue;
}
if (b.optional) logger.info(`[backend] ${b.optional}`);
else logger.warn(`[backend] ${b.name} not built \u2014 its plugin will show "engine offline". Build: ${b.build}`);
continue;
}
launch();
}
}
};
}
// internal/dev-sync-plugin.mjs
import { execFile } from "node:child_process";
var git = (root, args) => new Promise((ok) => execFile(
"git",
args,
{ cwd: root, timeout: 6e4, env: { ...process.env, GIT_TERMINAL_PROMPT: "0" } },
(e, out, err) => {
const all = String(err || e?.message || "").trim();
ok({ ok: !e, out: String(out || "").trim(), err: all.split("\n")[0], errAll: all });
}
));
var AUTH_RE = /unauthori[sz]ed|authentication failed|could not read (username|password)|terminal prompts disabled|permission denied \(publickey|\b40[13]\b/i;
var shown = (remote) => remote.replace(/\/\/[^/@]*@/, "//");
var label = (remote, branch) => remote.includes("/") ? `${branch} of ${shown(remote)}` : `${remote}/${branch}`;
var REG2 = globalThis.__dxDevSync ||= { timer: null, running: false, status: { enabled: false } };
async function syncOnce(root, remote, branch, log = { info() {
}, warn() {
} }) {
if (REG2.running) return REG2.status;
REG2.running = true;
try {
const src2 = label(remote, branch);
const s = { enabled: true, remote: shown(remote), branch, checkedAt: (/* @__PURE__ */ new Date()).toISOString() };
const f = await git(root, ["fetch", "-q", remote, branch]);
if (!f.ok) {
const auth = AUTH_RE.test(f.errAll);
const state = auth ? "needs-credentials" : "fetch-failed";
const message = auth ? `dev-sync needs git credentials for ${shown(remote)} (a credential helper, or an SSH remote your key can read); git said: ${shown(f.err)}` : `could not fetch ${src2}: ${shown(f.err)}`;
if (REG2.status.state !== state || REG2.status.message !== message) log.warn(`[dev-sync] ${message}`);
return REG2.status = { ...s, state, message };
}
const up = (await git(root, ["rev-parse", "FETCH_HEAD"])).out;
const head = (await git(root, ["rev-parse", "HEAD"])).out;
const cur = (await git(root, ["symbolic-ref", "-q", "--short", "HEAD"])).out;
const behind = Number((await git(root, ["rev-list", "--count", `HEAD..${up}`])).out || 0);
const ahead = Number((await git(root, ["rev-list", "--count", `${up}..HEAD`])).out || 0);
const dirty = (await git(root, ["status", "--porcelain", "--untracked-files=no"])).out !== "";
Object.assign(s, { upstream: up, behind, ahead, dirty, currentBranch: cur || "(detached)" });
if (behind === 0) return REG2.status = { ...s, state: "up-to-date", message: `up to date with ${src2}` };
const why = cur !== branch ? `this checkout is on ${cur || "a detached HEAD"}, not ${branch}` : dirty ? "you have local edits" : ahead ? `you have ${ahead} local commit(s) that are not on ${src2}` : "";
const m = why ? null : await git(root, ["merge", "--ff-only", "-q", up]);
if (why || !m.ok) {
const message = `${branch} is ${behind} commit(s) ahead; ${why || `fast-forward refused (${m.err})`}, so nothing was merged. Pull when ready.`;
if (REG2.status.upstream !== up || REG2.status.state !== "behind") log.warn(`[dev-sync] ${message}`);
return REG2.status = { ...s, state: "behind", message };
}
log.info(`[dev-sync] fast-forwarded ${head.slice(0, 8)} \u2192 ${up.slice(0, 8)} (${behind} commit(s) from ${src2})`);
return REG2.status = { ...s, behind: 0, state: "up-to-date", lastMerge: s.checkedAt, message: `fast-forwarded ${behind} commit(s) from ${src2}` };
} finally {
REG2.running = false;
}
}
function devSyncPlugin(root, env = process.env) {
const remote = env.DX_SYNC_REMOTE || "";
const branch = env.DX_SYNC_BRANCH || "development";
const every = Math.max(2, Number(env.DX_SYNC_INTERVAL) || 5);
return {
name: "dx-dev-sync",
apply: "serve",
configureServer(server) {
server.middlewares.use("/api/dev-sync", (req, res) => {
res.writeHead(200, { "Content-Type": "application/json", "Cache-Control": "no-store" });
res.end(JSON.stringify(REG2.status));
});
clearInterval(REG2.timer);
if (!remote) {
REG2.status = { enabled: false, message: "off: set DX_SYNC_REMOTE (a remote name like origin, or a repo URL) in .env.local to follow a shared branch" };
return;
}
const log = server.config.logger;
log.info(`[dev-sync] following ${label(remote, branch)} every ${every}s (fast-forward only, never over local edits)`);
const tick = () => syncOnce(root, remote, branch, log);
REG2.timer = setInterval(tick, every * 1e3);
REG2.timer.unref?.();
tick();
}
};
}
// vite.config.js
import { fileURLToPath as fileURLToPath5 } from "node:url";
import { resolve as resolve8, dirname as dirname6, join as join4 } from "node:path";
import { readdirSync as readdirSync5, statSync as statSync4, existsSync as existsSync6, readFileSync as readFileSync7 } from "node:fs";
import { createHash as createHash2 } from "node:crypto";
var __vite_injected_original_import_meta_url5 = "file:///home/lk10x/Workspace/eco-dx/root-doxa-maps/wt-hf/vite.config.js";
var __dirname = dirname6(fileURLToPath5(__vite_injected_original_import_meta_url5));
var SHIP_ONE_LOCALE = process.env.DX_SHIP_ONE_LOCALE === "1";
var profilesDir = resolve8(__dirname, "plugin-designs");
var MFE_NAME = TREE_NAME;
var OUT_ROOT = process.env.DOXA_OUT_ROOT ? resolve8(process.env.DOXA_OUT_ROOT) : GATED_PROFILES.includes(process.env.BUNDLE) ? resolve8(__dirname, ".gated", MFE_NAME) : resolve8(__dirname, "public", MFE_NAME);
function discoverBundles() {
if (!existsSync6(profilesDir)) return {};
const entries = {};
for (const name of readdirSync5(profilesDir)) {
if (name.startsWith("_") || name.startsWith(".")) continue;
const dir = resolve8(profilesDir, name);
if (!statSync4(dir).isDirectory()) continue;
const html = resolve8(dir, "index.html");
const js = resolve8(dir, "index.js");
if (!existsSync6(html) || !existsSync6(js)) continue;
entries[name] = { html, js };
}
return entries;
}
var localeDirs = () => readdirSync5(join4(__dirname, "library/i18n/locales"), { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name).sort();
function localeTree(areas, lc) {
const root = join4(__dirname, "library/i18n/locales");
const tree = {};
for (const a of areas) {
const p = join4(root, lc, `${a}.json`);
if (!existsSync6(p)) continue;
try {
tree[a] = JSON.parse(readFileSync7(p, "utf8"));
} catch {
}
}
return Object.keys(tree).length ? JSON.stringify(tree) : null;
}
function i18nAreasPlugin(bundle) {
let areas = null;
return {
name: "doxa-i18n-areas",
enforce: "pre",
async buildStart() {
const { areasForBundle: areasForBundle2 } = await Promise.resolve().then(() => (init_i18n_areas(), i18n_areas_exports));
areas = areasForBundle2(__dirname, bundle).areas;
this.info?.(`i18n areas for ${bundle}: ${areas.join(", ") || "(none)"}`);
},
transform(code, id) {
if (!areas || !id.replace(/\\/g, "/").endsWith("/library/i18n/index.js")) return null;
const GLOB = "import.meta.glob('./locales/*/*.json'";
if (!code.includes(GLOB)) this.error("i18n catalogue glob not found in library/i18n/index.js; cannot narrow it");
const loc = SHIP_ONE_LOCALE ? "en" : "*";
const list = areas.length ? "[" + areas.map((a) => `'./locales/${loc}/${a}.json'`).join(", ") + "]" : "['./locales/*/__none__.json']";
let out = code.replace(GLOB, "import.meta.glob(" + list);
if (SHIP_ONE_LOCALE) {
const sri = {};
for (const lc of localeDirs()) {
if (lc === "en") continue;
const json = localeTree(areas, lc);
if (json) sri[lc] = "sha384-" + createHash2("sha384").update(json).digest("base64");
}
out = `const __DX_LOCALE_SRI__ = ${JSON.stringify(sri)};
` + out;
}
return { code: out, map: null };
},
// Emit /locales/.json for every locale the IIFE no longer carries, holding exactly
// the areas this bundle uses — the same areasForBundle() answer the glob above was narrowed
// with, so the two dimensions cannot drift apart. Emitted from THIS plugin on purpose: the
// composite-freshness, landing-consumers, fix:sri and .gated/ steps already walk the build's
// own output, so assets that appear here are covered with no new pipeline step.
generateBundle() {
if (!SHIP_ONE_LOCALE || !areas?.length) return;
for (const lc of localeDirs()) {
if (lc === "en") continue;
const json = localeTree(areas, lc);
if (json) this.emitFile({ type: "asset", fileName: `locales/${lc}.json`, source: json });
}
}
};
}
var vite_config_default = defineConfig(({ command, mode }) => {
const env = loadEnv(mode, __dirname, "");
const all = discoverBundles();
const allNames = Object.keys(all);
const target = process.env.BUNDLE;
const isBuild = command === "build";
if (isBuild && !target) {
throw new Error(
`[${MFE_NAME}] Set BUNDLE= for build, or use \`npm run build\` (loops all).
Available: ${allNames.join(", ") || "(none)"}`
);
}
if (isBuild && !all[target]) {
throw new Error(
`[${MFE_NAME}] Bundle "${target}" is not buildable: plugin-designs/${target}/ is missing index.html and/or index.js (the custom-element entry pair every bundle needs).
Buildable bundles: ${allNames.join(", ") || "(none)"}`
);
}
const buildEntries = isBuild ? { [target]: all[target].js } : Object.fromEntries(allNames.map((n) => [n, all[n].html]));
return {
// COMPILE-TIME QA GATE (release governance): the QA corner widget references
// the maintainer's internal platform and must NOT exist in public
// artifacts. Default FALSE strips it from every bundle (dead-branch +
// treeshake); an explicitly-flagged platform build sets DOXA_QA=1.
// The dev server listens on THIS MACHINE ONLY. A reachable one serves the source tree, node_modules
// and the token baked into /; a remote viewer reaches it through an ssh tunnel (ssh -L), never a public
// port. `--host` on the command line still overrides this for a deliberate LAN session.
// Local plugin backends (backend/, e.g. rudder-engine) are started by backendDevPlugin and reached
// through these same-origin routes, so a plugin calls /api//… in dev exactly as on desktop.
// NOT WATCHING public/ (the first laptop run with DX_SYNC_REMOTE, 2026-09-28): a landing rewrites
// dozens of built leaf index.html files there, and Vite answers each changed .html with a FULL page reload
// (measured: one edited leaf page -> one full-reload broadcast), so a pull fired a burst of reloads while
// git was still writing and the page went white. The dev server never serves public/ through its module
// graph (the script-embed route reads it on request), so nothing there needs watching.
server: { host: "127.0.0.1", proxy: devBackendProxy(env), watch: { ignored: ["**/public/**"] } },
define: {
// ★ NO 'unsafe-eval' (CRITIC-4, 2026-09-26). vue-i18n's default message compiler builds each message
// with `new Function`, so a partner with a strict Content-Security-Policy saw every translated string
// fail. The JIT compiler (vue-i18n >= 9.3) compiles messages to an AST and evaluates it without eval;
// defining the flag lets the bundler drop the eval path entirely (check:no-eval asserts it).
__INTLIFY_JIT_COMPILATION__: "true",
__INTLIFY_DROP_MESSAGE_COMPILER__: "false",
__DOXA_QA__: JSON.stringify(process.env.DOXA_QA === "1"),
// The output tree's name (vite.doxa-maps-build-config.json `name`, the same value OUT_ROOT uses),
// for the few runtime paths that must name the tree — so renaming the tree needs no source edit.
__DOXA_TREE_NAME__: JSON.stringify(MFE_NAME)
},
// Build only: shadow-DOM CSS is injected from useShadowStyles(`…`) literals that
// ship verbatim, so their block comments are stripped before the Vue compiler
// runs (enforce: 'pre'). Dev keeps the comments.
plugins: [
// ★ ONLY THE TRANSLATION AREAS THIS BUNDLE USES (CRITIC-10). library/i18n/index.js globs every
// locales/*/*.json; in a BUNDLE build the glob is narrowed to the areas internal/i18n-areas.mjs finds
// in this bundle's source graph. Dev (no BUNDLE) keeps every area. check:i18n-treeshake verifies it.
isBuild && target ? i18nAreasPlugin(target) : null,
stripShadowCssCommentsPlugin(),
// ─── STRUCTURAL shadow-DOM CSS fix (customElement mode) ──────────────────
// WHY: every bundle here is a `defineCustomElement(...)` that renders inside a
// SHADOW ROOT. In @vitejs/plugin-vue's DEFAULT mode, each SFC