austere-axi 0.7.0 → 0.7.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.mjs CHANGED
@@ -618,7 +618,7 @@ var init_paths = __esm({
618
618
  HERE = path.dirname(fileURLToPath(import.meta.url));
619
619
  BUNDLED = true;
620
620
  ASSET_DIR = BUNDLED ? HERE : path.resolve(HERE, "../../dist");
621
- VERSION = true ? "0.7.0" : packageVersion();
621
+ VERSION = true ? "0.7.3" : packageVersion();
622
622
  CLI_ENTRY = BUNDLED ? fileURLToPath(import.meta.url) : path.resolve(HERE, "main.ts");
623
623
  securedDir = null;
624
624
  }
@@ -1907,38 +1907,177 @@ var init_sessions = __esm({
1907
1907
  }
1908
1908
  });
1909
1909
 
1910
- // src/cli/export.ts
1910
+ // src/cli/assets.ts
1911
1911
  import fs9 from "node:fs";
1912
1912
  import path8 from "node:path";
1913
+ function resolveSibling(dir, rel, maxBytes = MAX_ASSET_BYTES) {
1914
+ if (rel.includes("\0")) return { ok: false, reason: "outside" };
1915
+ const file3 = path8.resolve(dir, rel);
1916
+ if (!file3.startsWith(dir + path8.sep)) return { ok: false, reason: "outside" };
1917
+ if (rel.split(/[/\\]/).some((p) => p !== "." && p.startsWith("."))) return { ok: false, reason: "hidden" };
1918
+ try {
1919
+ const realDir = fs9.realpathSync(dir);
1920
+ const real = fs9.realpathSync(file3);
1921
+ if (!real.startsWith(realDir + path8.sep)) return { ok: false, reason: "outside" };
1922
+ if (path8.relative(realDir, real).split(path8.sep).some((p) => p.startsWith("."))) return { ok: false, reason: "hidden" };
1923
+ const st2 = fs9.statSync(real);
1924
+ if (!st2.isFile()) return { ok: false, reason: "missing" };
1925
+ if (st2.size > maxBytes) return { ok: false, reason: "too-large" };
1926
+ return { ok: true, file: real, size: st2.size };
1927
+ } catch {
1928
+ return { ok: false, reason: "missing" };
1929
+ }
1930
+ }
1931
+ var MAX_ASSET_BYTES;
1932
+ var init_assets = __esm({
1933
+ "src/cli/assets.ts"() {
1934
+ "use strict";
1935
+ MAX_ASSET_BYTES = 64 * 1024 * 1024;
1936
+ }
1937
+ });
1938
+
1939
+ // src/cli/export.ts
1940
+ import fs10 from "node:fs";
1941
+ import path9 from "node:path";
1913
1942
  function inlineScript(code) {
1914
1943
  return `<script>${code.replace(/<\/script/gi, "<\\/script")}</script>`;
1915
1944
  }
1916
- function exportHtml(html) {
1945
+ function stringAt(src, i) {
1946
+ const q2 = src[i];
1947
+ let value = "";
1948
+ for (let j = i + 1; j < src.length; j++) {
1949
+ const c = src[j];
1950
+ if (c === "\\" && j + 1 < src.length) value += src[++j];
1951
+ else if (c === q2) return { start: i, end: j + 1, value };
1952
+ else value += c;
1953
+ }
1954
+ return null;
1955
+ }
1956
+ function skipBlank(src, i) {
1957
+ for (; ; ) {
1958
+ while (i < src.length && /\s/.test(src[i])) i++;
1959
+ if (src[i] === "#" || src[i] === "/" && src[i + 1] === "/") while (i < src.length && src[i] !== "\n") i++;
1960
+ else return i;
1961
+ }
1962
+ }
1963
+ function imageSources(src) {
1964
+ const out = [];
1965
+ let i = 0;
1966
+ while (i < src.length) {
1967
+ const c = src[i];
1968
+ if (c === '"' || c === "'") {
1969
+ i = stringAt(src, i)?.end ?? src.length;
1970
+ } else if (c === "#" || c === "/" && src[i + 1] === "/") {
1971
+ while (i < src.length && src[i] !== "\n") i++;
1972
+ } else if (/[A-Za-z_$]/.test(c)) {
1973
+ let j = i + 1;
1974
+ while (j < src.length && /[\w$]/.test(src[j])) j++;
1975
+ const word = src.slice(i, j);
1976
+ i = j;
1977
+ if (word !== "Image") continue;
1978
+ j = skipBlank(src, j);
1979
+ if (src[j] !== "(") continue;
1980
+ let k = j + 1;
1981
+ let depth = 0;
1982
+ while (k < src.length) {
1983
+ const d = src[k];
1984
+ if (d === '"' || d === "'") k = stringAt(src, k)?.end ?? src.length;
1985
+ else if (d === "#" || d === "/" && src[k + 1] === "/") k = skipBlank(src, k);
1986
+ else if (d === "(" || d === "[" || d === "{") {
1987
+ depth++;
1988
+ k++;
1989
+ } else if (d === ")" || d === "]" || d === "}") {
1990
+ if (depth === 0) break;
1991
+ depth--;
1992
+ k++;
1993
+ } else if (d === "," && depth === 0) break;
1994
+ else k++;
1995
+ }
1996
+ if (src[k] !== ",") continue;
1997
+ k = skipBlank(src, k + 1);
1998
+ if (src[k] === '"' || src[k] === "'") {
1999
+ const lit = stringAt(src, k);
2000
+ if (lit) out.push(lit);
2001
+ }
2002
+ } else i++;
2003
+ }
2004
+ return out;
2005
+ }
2006
+ function inlineImages(program, dir, notices) {
2007
+ let out = "";
2008
+ let at2 = 0;
2009
+ const seen = /* @__PURE__ */ new Set();
2010
+ const warn = (src, why) => {
2011
+ if (!seen.has(src)) notices.push(`Image "${src}" is not inlined: ${why}. Keep it next to the exported file.`);
2012
+ seen.add(src);
2013
+ };
2014
+ for (const lit of imageSources(program)) {
2015
+ if (/^(?:[a-z][a-z0-9+.-]*:|\/\/)/i.test(lit.value)) continue;
2016
+ let rel = lit.value.replace(/[?#].*$/, "");
2017
+ try {
2018
+ rel = decodeURIComponent(rel);
2019
+ } catch {
2020
+ }
2021
+ const type = IMAGE_TYPES[path9.extname(rel).toLowerCase()];
2022
+ if (!type) {
2023
+ warn(lit.value, "not a png, jpg, gif, webp, svg or avif file");
2024
+ continue;
2025
+ }
2026
+ const sib = resolveSibling(dir, rel, MAX_INLINE_BYTES);
2027
+ if (!sib.ok) {
2028
+ const why = { missing: "no such file next to the page", outside: "it is outside the page's folder", hidden: "hidden files are never read", "too-large": "it is over 10 MB" }[sib.reason];
2029
+ warn(lit.value, why);
2030
+ continue;
2031
+ }
2032
+ let bytes;
2033
+ try {
2034
+ bytes = fs10.readFileSync(sib.file);
2035
+ } catch {
2036
+ warn(lit.value, "it cannot be read (permissions?)");
2037
+ continue;
2038
+ }
2039
+ out += program.slice(at2, lit.start) + JSON.stringify(`data:${type};base64,${bytes.toString("base64")}`);
2040
+ at2 = lit.end;
2041
+ }
2042
+ return out + program.slice(at2);
2043
+ }
2044
+ function exportHtml(html, dir) {
1917
2045
  const art = readArtifact(html);
1918
2046
  const notices = [];
1919
2047
  if (art.program == null) throw new Error('no <script type="text/openui"> block');
1920
2048
  let tags = "";
1921
2049
  if (/\bMermaid\s*\(/.test(art.program)) {
1922
- tags += inlineScript(fs9.readFileSync(assetPath("mermaid.min.js"), "utf8")) + "\n";
2050
+ tags += inlineScript(fs10.readFileSync(assetPath("mermaid.min.js"), "utf8")) + "\n";
1923
2051
  }
1924
- tags += inlineScript(fs9.readFileSync(assetPath("austere.js"), "utf8"));
1925
- for (const p of implicitPacks(art.program)) tags += "\n" + inlineScript(fs9.readFileSync(path8.join(p.dir, "pack.js"), "utf8"));
2052
+ tags += inlineScript(fs10.readFileSync(assetPath("austere.js"), "utf8"));
2053
+ for (const p of implicitPacks(art.program)) tags += "\n" + inlineScript(fs10.readFileSync(path9.join(p.dir, "pack.js"), "utf8"));
1926
2054
  const packs = pagePacks(html);
1927
- for (const p of packs.installed) tags += "\n" + inlineScript(fs9.readFileSync(path8.join(p.dir, "pack.js"), "utf8"));
2055
+ for (const p of packs.installed) tags += "\n" + inlineScript(fs10.readFileSync(path9.join(p.dir, "pack.js"), "utf8"));
1928
2056
  for (const p of [...packs.missing, ...packs.mismatched]) {
1929
2057
  notices.push(`Pack ${p.name}@${p.version} is not installed here, so the exported page loads it from the CDN (needs network).`);
1930
2058
  }
1931
- const out = rewriteRuntime(html, () => tags);
1932
- const rel = art.program.match(/Image\([^)]*?["'](?!https?:|data:)([^"']+\.(?:png|jpe?g|gif|webp|svg|avif))["']/gi);
1933
- if (rel) notices.push("Images referenced by relative path are not inlined; keep them next to the exported file.");
1934
- return { html: out, notices };
2059
+ const inlined = inlineImages(art.program, dir, notices);
2060
+ const withImages = inlined === art.program ? html : html.slice(0, art.programStart) + inlined + html.slice(art.programEnd);
2061
+ return { html: rewriteRuntime(withImages, () => tags), notices };
1935
2062
  }
2063
+ var IMAGE_TYPES, MAX_INLINE_BYTES;
1936
2064
  var init_export = __esm({
1937
2065
  "src/cli/export.ts"() {
1938
2066
  "use strict";
1939
2067
  init_artifact();
2068
+ init_assets();
1940
2069
  init_paths();
1941
2070
  init_packs();
2071
+ IMAGE_TYPES = {
2072
+ ".png": "image/png",
2073
+ ".jpg": "image/jpeg",
2074
+ ".jpeg": "image/jpeg",
2075
+ ".gif": "image/gif",
2076
+ ".webp": "image/webp",
2077
+ ".svg": "image/svg+xml",
2078
+ ".avif": "image/avif"
2079
+ };
2080
+ MAX_INLINE_BYTES = 10 * 1024 * 1024;
1942
2081
  }
1943
2082
  });
1944
2083
 
@@ -1953,8 +2092,8 @@ __export(install_exports, {
1953
2092
  removePack: () => removePack,
1954
2093
  untar: () => untar
1955
2094
  });
1956
- import fs11 from "node:fs";
1957
- import path10 from "node:path";
2095
+ import fs12 from "node:fs";
2096
+ import path11 from "node:path";
1958
2097
  import zlib from "node:zlib";
1959
2098
  import crypto4 from "node:crypto";
1960
2099
  function registryUrl() {
@@ -2091,15 +2230,15 @@ function readTarball(tgz, label) {
2091
2230
  return { packJs, catalog, packageJson: files.get("package.json") || null };
2092
2231
  }
2093
2232
  function fromTarball(file3) {
2094
- const abs = path10.resolve(file3.replace(/^~(?=[\\/])/, process.env.HOME || ""));
2095
- if (!fs11.existsSync(abs)) throw new Error(`no such file: ${abs}`);
2096
- return { ...readTarball(fs11.readFileSync(abs), path10.basename(abs)), source: "tarball", resolved: abs };
2233
+ const abs = path11.resolve(file3.replace(/^~(?=[\\/])/, process.env.HOME || ""));
2234
+ if (!fs12.existsSync(abs)) throw new Error(`no such file: ${abs}`);
2235
+ return { ...readTarball(fs12.readFileSync(abs), path11.basename(abs)), source: "tarball", resolved: abs };
2097
2236
  }
2098
2237
  function fromFolder(dir) {
2099
- const abs = path10.resolve(dir.replace(/^~(?=[\\/])/, process.env.HOME || ""));
2100
- if (!fs11.existsSync(abs)) throw new Error(`no such folder: ${abs}`);
2101
- if (fs11.statSync(abs).isFile()) return fromTarball(abs);
2102
- const read = (rel) => fs11.existsSync(path10.join(abs, rel)) ? fs11.readFileSync(path10.join(abs, rel)) : null;
2238
+ const abs = path11.resolve(dir.replace(/^~(?=[\\/])/, process.env.HOME || ""));
2239
+ if (!fs12.existsSync(abs)) throw new Error(`no such folder: ${abs}`);
2240
+ if (fs12.statSync(abs).isFile()) return fromTarball(abs);
2241
+ const read = (rel) => fs12.existsSync(path11.join(abs, rel)) ? fs12.readFileSync(path11.join(abs, rel)) : null;
2103
2242
  const packJs = read("dist/pack.js");
2104
2243
  const catalog = read("dist/catalog.json");
2105
2244
  if (!packJs || !catalog) throw new Error(`${abs} has no dist/pack.js and dist/catalog.json: run \`austere-axi components build ${dir}\` first`);
@@ -2139,10 +2278,10 @@ async function addPack(spec, { trust = false } = {}) {
2139
2278
  }
2140
2279
  const got = local ? fromFolder(spec) : await fromRegistry(spec);
2141
2280
  if (got.source === "tarball" && !trust) {
2142
- const peek = parseCatalog(got.catalog, path10.basename(got.resolved));
2281
+ const peek = parseCatalog(got.catalog, path11.basename(got.resolved));
2143
2282
  throw Object.assign(
2144
2283
  new Error(
2145
- `${path10.basename(got.resolved)} (${peek.pack}) comes from a file, not the registry: its code would run inside your review pages. Rerun with --trust once you have looked at it.`
2284
+ `${path11.basename(got.resolved)} (${peek.pack}) comes from a file, not the registry: its code would run inside your review pages. Rerun with --trust once you have looked at it.`
2146
2285
  ),
2147
2286
  { code: "untrusted" }
2148
2287
  );
@@ -2167,14 +2306,14 @@ async function addPack(spec, { trust = false } = {}) {
2167
2306
  }
2168
2307
  const lock = readLock();
2169
2308
  const previous = lock.packs[cat.pack];
2170
- const dir = path10.join(packsDir(), packDirName(cat.pack, cat.version));
2171
- fs11.rmSync(dir, { recursive: true, force: true });
2172
- fs11.mkdirSync(dir, { recursive: true });
2173
- fs11.writeFileSync(path10.join(dir, "pack.js"), got.packJs);
2174
- fs11.writeFileSync(path10.join(dir, "catalog.json"), got.catalog);
2175
- if (got.packageJson) fs11.writeFileSync(path10.join(dir, "package.json"), got.packageJson);
2309
+ const dir = path11.join(packsDir(), packDirName(cat.pack, cat.version));
2310
+ fs12.rmSync(dir, { recursive: true, force: true });
2311
+ fs12.mkdirSync(dir, { recursive: true });
2312
+ fs12.writeFileSync(path11.join(dir, "pack.js"), got.packJs);
2313
+ fs12.writeFileSync(path11.join(dir, "catalog.json"), got.catalog);
2314
+ if (got.packageJson) fs12.writeFileSync(path11.join(dir, "package.json"), got.packageJson);
2176
2315
  if (previous && previous.version !== cat.version) {
2177
- fs11.rmSync(path10.join(packsDir(), packDirName(cat.pack, previous.version)), { recursive: true, force: true });
2316
+ fs12.rmSync(path11.join(packsDir(), packDirName(cat.pack, previous.version)), { recursive: true, force: true });
2178
2317
  }
2179
2318
  lock.packs[cat.pack] = {
2180
2319
  version: cat.version,
@@ -2194,7 +2333,7 @@ function removePack(name) {
2194
2333
  if (!entry) return null;
2195
2334
  delete lock.packs[name];
2196
2335
  writeLock(lock);
2197
- fs11.rmSync(path10.join(packsDir(), packDirName(name, entry.version)), { recursive: true, force: true });
2336
+ fs12.rmSync(path11.join(packsDir(), packDirName(name, entry.version)), { recursive: true, force: true });
2198
2337
  return { name, version: entry.version };
2199
2338
  }
2200
2339
  var MAX_TARBALL, MAX_PACK_JS;
@@ -2215,8 +2354,8 @@ __export(pack_dev_exports, {
2215
2354
  buildPackDir: () => buildPackDir,
2216
2355
  newPack: () => newPack
2217
2356
  });
2218
- import fs12 from "node:fs";
2219
- import path11 from "node:path";
2357
+ import fs13 from "node:fs";
2358
+ import path12 from "node:path";
2220
2359
  import { createRequire } from "node:module";
2221
2360
  import { pathToFileURL } from "node:url";
2222
2361
  function packageJson(name) {
@@ -2253,9 +2392,9 @@ Publish with \`npm publish\` after \`npm run build\`. Others install it with
2253
2392
  }
2254
2393
  function newPack(dir, { name } = {}) {
2255
2394
  if (!dir) throw new Error("new needs a folder: austere-axi components new austere-pack-milestones");
2256
- const abs = path11.resolve(dir);
2257
- if (fs12.existsSync(abs) && fs12.readdirSync(abs).length) throw new Error(`${abs} is not empty`);
2258
- const pkgName = name || path11.basename(abs).toLowerCase().replace(/[^a-z0-9._-]+/g, "-");
2395
+ const abs = path12.resolve(dir);
2396
+ if (fs13.existsSync(abs) && fs13.readdirSync(abs).length) throw new Error(`${abs} is not empty`);
2397
+ const pkgName = name || path12.basename(abs).toLowerCase().replace(/[^a-z0-9._-]+/g, "-");
2259
2398
  if (!isValidPackName(pkgName)) throw new Error(`${JSON.stringify(pkgName)} is not an npm package name; pass --name`);
2260
2399
  const files = {
2261
2400
  "package.json": JSON.stringify(packageJson(pkgName), null, 2) + "\n",
@@ -2264,13 +2403,13 @@ function newPack(dir, { name } = {}) {
2264
2403
  ...TEMPLATE
2265
2404
  };
2266
2405
  for (const [rel, text] of Object.entries(files)) {
2267
- fs12.mkdirSync(path11.dirname(path11.join(abs, rel)), { recursive: true });
2268
- fs12.writeFileSync(path11.join(abs, rel), text);
2406
+ fs13.mkdirSync(path12.dirname(path12.join(abs, rel)), { recursive: true });
2407
+ fs13.writeFileSync(path12.join(abs, rel), text);
2269
2408
  }
2270
2409
  return { dir: abs, name: pkgName, files: Object.keys(files) };
2271
2410
  }
2272
2411
  async function findEsbuild(dir) {
2273
- for (const base of [path11.join(dir, "package.json"), path11.join(process.cwd(), "package.json"), import.meta.url]) {
2412
+ for (const base of [path12.join(dir, "package.json"), path12.join(process.cwd(), "package.json"), import.meta.url]) {
2274
2413
  try {
2275
2414
  const resolved = createRequire(base).resolve("esbuild");
2276
2415
  const esbuild = await import(pathToFileURL(resolved).href);
@@ -2281,8 +2420,8 @@ async function findEsbuild(dir) {
2281
2420
  return null;
2282
2421
  }
2283
2422
  async function buildPackDir(dir = ".") {
2284
- const abs = path11.resolve(dir);
2285
- if (!fs12.existsSync(path11.join(abs, "package.json"))) throw new Error(`${abs} has no package.json (\`austere-axi components new <dir>\` makes a pack)`);
2423
+ const abs = path12.resolve(dir);
2424
+ if (!fs13.existsSync(path12.join(abs, "package.json"))) throw new Error(`${abs} has no package.json (\`austere-axi components new <dir>\` makes a pack)`);
2286
2425
  const esbuild = await findEsbuild(abs);
2287
2426
  if (!esbuild) throw new Error(`components build needs esbuild: run \`npm install -D esbuild\` in ${abs}`);
2288
2427
  process.env.OPENUI_TELEMETRY_DISABLED ||= "1";
@@ -2406,7 +2545,7 @@ export default austereComponent({
2406
2545
 
2407
2546
  // src/cli/net.ts
2408
2547
  import { execFile } from "node:child_process";
2409
- import fs13 from "node:fs";
2548
+ import fs14 from "node:fs";
2410
2549
  function run(bin, args) {
2411
2550
  return new Promise((resolve) => {
2412
2551
  execFile(bin, args, { timeout: 2500, windowsHide: true }, (err, stdout) => resolve(err ? null : stdout));
@@ -2417,7 +2556,7 @@ async function detectTailscale({ fresh = false } = {}) {
2417
2556
  if (!fresh && Date.now() - cache.at < 3e4) return cache.value;
2418
2557
  let value = null;
2419
2558
  for (const bin of CANDIDATES) {
2420
- if (bin.startsWith("/") && !fs13.existsSync(bin)) continue;
2559
+ if (bin.startsWith("/") && !fs14.existsSync(bin)) continue;
2421
2560
  const out = await run(bin, ["status", "--json"]);
2422
2561
  if (!out) continue;
2423
2562
  try {
@@ -2515,7 +2654,7 @@ var init_items = __esm({
2515
2654
 
2516
2655
  // src/cli/hub-page.ts
2517
2656
  import os3 from "node:os";
2518
- import path12 from "node:path";
2657
+ import path13 from "node:path";
2519
2658
  function day(ms) {
2520
2659
  const d = new Date(ms);
2521
2660
  const today = /* @__PURE__ */ new Date();
@@ -2530,7 +2669,7 @@ function time3(ms) {
2530
2669
  }
2531
2670
  function tilde(p) {
2532
2671
  const home = os3.homedir();
2533
- return p === home || p.startsWith(home + path12.sep) ? "~" + p.slice(home.length) : p;
2672
+ return p === home || p.startsWith(home + path13.sep) ? "~" + p.slice(home.length) : p;
2534
2673
  }
2535
2674
  function counts(a) {
2536
2675
  const waiting = a.open + a.received;
@@ -2566,13 +2705,13 @@ function hubPage(rows, { version: version2, q: q2, project, stats }) {
2566
2705
  const body = [...days.entries()].map(
2567
2706
  ([d, items]) => `<h3>${escapeHtml(d)}</h3><ul>${items.map(
2568
2707
  (a) => `<li><a href="/hub/open/${a.id}" title="${escapeHtml(a.file)}">
2569
- <strong>${escapeHtml(a.title || path12.basename(a.file))}</strong>
2570
- <span class="meta">${escapeHtml(path12.relative(proj, a.file) || path12.basename(a.file))} \xB7 ${time3(a.updated_at)} \xB7 ${state(a)}${counts(a) ? ` \xB7 ${counts(a)}` : ""}</span>
2708
+ <strong>${escapeHtml(a.title || path13.basename(a.file))}</strong>
2709
+ <span class="meta">${escapeHtml(path13.relative(proj, a.file) || path13.basename(a.file))} \xB7 ${time3(a.updated_at)} \xB7 ${state(a)}${counts(a) ? ` \xB7 ${counts(a)}` : ""}</span>
2571
2710
  </a></li>`
2572
2711
  ).join("")}</ul>`
2573
2712
  ).join("");
2574
2713
  const unanswered = list.reduce((n, a) => n + a.open + a.received, 0);
2575
- return `<section><h2><a href="/?project=${encodeURIComponent(proj)}">${escapeHtml(path12.basename(proj) || proj)}</a> <small title="${escapeHtml(proj)}">${escapeHtml(
2714
+ return `<section><h2><a href="/?project=${encodeURIComponent(proj)}">${escapeHtml(path13.basename(proj) || proj)}</a> <small title="${escapeHtml(proj)}">${escapeHtml(
2576
2715
  shortPath(proj)
2577
2716
  )}</small>${unanswered ? ` <b class="open">${unanswered} unanswered</b>` : ""}</h2>${body}</section>`;
2578
2717
  }).join("");
@@ -2595,7 +2734,7 @@ function notesPage(rows, { version: version2, q: q2, project, status, author = "
2595
2734
  const when = `${day(n.at)} ${time3(n.at)}`;
2596
2735
  return `<li class="n"><a href="/hub/open/${escapeHtml(n.artifact_id)}" title="${escapeHtml(n.file)}">
2597
2736
  <p class="note">${escapeHtml(n.text || "(no text)")}</p>
2598
- <span class="note-t">${n.author ? `<b class="by">${escapeHtml(n.author)}</b> \xB7 ` : ""}${n.target ? `on ${escapeHtml(n.target)} \xB7 ` : ""}<strong>${escapeHtml(n.title || path12.basename(n.file))}</strong> \xB7 ${escapeHtml(path12.basename(n.project) || n.project)} \xB7 ${escapeHtml(when)} \xB7 <span class="st ${waiting ? "waiting" : "done"}">${STATUS_LABEL[n.status] || escapeHtml(n.status)}</span></span>
2737
+ <span class="note-t">${n.author ? `<b class="by">${escapeHtml(n.author)}</b> \xB7 ` : ""}${n.target ? `on ${escapeHtml(n.target)} \xB7 ` : ""}<strong>${escapeHtml(n.title || path13.basename(n.file))}</strong> \xB7 ${escapeHtml(path13.basename(n.project) || n.project)} \xB7 ${escapeHtml(when)} \xB7 <span class="st ${waiting ? "waiting" : "done"}">${STATUS_LABEL[n.status] || escapeHtml(n.status)}</span></span>
2599
2738
  </a></li>`;
2600
2739
  }).join("");
2601
2740
  const opt = (v, label) => `<option value="${v}"${status === v ? " selected" : ""}>${label}</option>`;
@@ -2650,9 +2789,9 @@ __export(server_exports, {
2650
2789
  persistEnv: () => persistEnv,
2651
2790
  runServer: () => runServer
2652
2791
  });
2653
- import fs14 from "node:fs";
2792
+ import fs15 from "node:fs";
2654
2793
  import http3 from "node:http";
2655
- import path13 from "node:path";
2794
+ import path14 from "node:path";
2656
2795
  import crypto5 from "node:crypto";
2657
2796
  function persistEnv() {
2658
2797
  return /^(1|true|yes)$/i.test(process.env.AUSTERE_AXI_PERSIST || "");
@@ -2667,7 +2806,7 @@ async function runServer({
2667
2806
  const line = `${(/* @__PURE__ */ new Date()).toISOString()} ${a.join(" ")}`;
2668
2807
  if (verbose) process.stderr.write(line + "\n");
2669
2808
  try {
2670
- fs14.appendFileSync(path13.join(stateDir(), "server.log"), line + "\n", { mode: 384 });
2809
+ fs15.appendFileSync(path14.join(stateDir(), "server.log"), line + "\n", { mode: 384 });
2671
2810
  } catch {
2672
2811
  }
2673
2812
  };
@@ -2849,7 +2988,7 @@ data: ${JSON.stringify({ state: "revoked" })}
2849
2988
  }
2850
2989
  function readFileSafe(file3) {
2851
2990
  try {
2852
- return fs14.readFileSync(file3, "utf8");
2991
+ return fs15.readFileSync(file3, "utf8");
2853
2992
  } catch {
2854
2993
  return null;
2855
2994
  }
@@ -2861,14 +3000,14 @@ data: ${JSON.stringify({ state: "revoked" })}
2861
3000
  const w = { html, program: art?.program ?? null, shell: art?.shell ?? null, timer: void 0 };
2862
3001
  watchers.set(s.id, w);
2863
3002
  if (art?.title) s.title = art.title;
2864
- fs14.watchFile(s.file, { interval: 300 }, () => {
3003
+ fs15.watchFile(s.file, { interval: 300 }, () => {
2865
3004
  clearTimeout(w.timer);
2866
3005
  w.timer = setTimeout(() => onFileChange(s, w), 120);
2867
3006
  });
2868
3007
  }
2869
3008
  function unwatch(id) {
2870
3009
  const s = store.get(id);
2871
- if (s) fs14.unwatchFile(s.file);
3010
+ if (s) fs15.unwatchFile(s.file);
2872
3011
  watchers.delete(id);
2873
3012
  }
2874
3013
  function onFileChange(s, w) {
@@ -3045,7 +3184,7 @@ data: ${JSON.stringify({ state: "revoked" })}
3045
3184
  const file3 = typeof body.file === "string" ? body.file : "";
3046
3185
  if (!file3) return send(res, 400, { error: "file is required" });
3047
3186
  if (route === "share") {
3048
- if (!fs14.existsSync(file3)) return send(res, 404, { error: `no such file: ${file3}` });
3187
+ if (!fs15.existsSync(file3)) return send(res, 404, { error: `no such file: ${file3}` });
3049
3188
  await ensureTailnetListener();
3050
3189
  if (!remoteBase()) {
3051
3190
  return send(res, 409, {
@@ -3065,7 +3204,7 @@ data: ${JSON.stringify({ state: "revoked" })}
3065
3204
  return send(res, 200, { id: inviteId(inv), name: inv.name, auto: inv.auto, link: inviteLink(inv), file: inv.file });
3066
3205
  }
3067
3206
  if (route === "open") {
3068
- if (!fs14.existsSync(file3)) return send(res, 404, { error: `no such file: ${file3}` });
3207
+ if (!fs15.existsSync(file3)) return send(res, 404, { error: `no such file: ${file3}` });
3069
3208
  await ensureTailnetListener();
3070
3209
  const s2 = store.ensure(file3);
3071
3210
  if (s2.closedAt && !body.reopen) {
@@ -3198,7 +3337,7 @@ data: ${JSON.stringify({ state: "revoked" })}
3198
3337
  }
3199
3338
  const file3 = installedPackFile(spec.name, spec.version);
3200
3339
  if (!file3) return send(res, 404, `pack ${spec.name}@${spec.version} is not installed (austere-axi components add ${spec.name}@${spec.version})`);
3201
- fs14.readFile(file3, (err, buf) => {
3340
+ fs15.readFile(file3, (err, buf) => {
3202
3341
  if (err) return send(res, 404, "not found");
3203
3342
  const actual = `sha384-${crypto5.createHash("sha384").update(buf).digest("base64")}`;
3204
3343
  const locked = readLock().packs[spec.name]?.integrity;
@@ -3222,13 +3361,13 @@ data: ${JSON.stringify({ state: "revoked" })}
3222
3361
  }
3223
3362
  function serveAsset(res, rel, url2) {
3224
3363
  if (rel.startsWith("installed-packs/")) return serveInstalledPack(res, rel, url2);
3225
- const clean = path13.normalize(rel).replace(/^(\.\.[/\\])+/, "");
3226
- const file3 = path13.join(ASSET_DIR, clean);
3227
- if (!file3.startsWith(ASSET_DIR + path13.sep)) return send(res, 404, "not found");
3228
- fs14.stat(file3, (err, st2) => {
3364
+ const clean = path14.normalize(rel).replace(/^(\.\.[/\\])+/, "");
3365
+ const file3 = path14.join(ASSET_DIR, clean);
3366
+ if (!file3.startsWith(ASSET_DIR + path14.sep)) return send(res, 404, "not found");
3367
+ fs15.stat(file3, (err, st2) => {
3229
3368
  if (err || !st2.isFile()) return send(res, 404, "not found");
3230
3369
  streamFile(res, file3, {
3231
- "content-type": MIME[path13.extname(file3)] || "application/octet-stream",
3370
+ "content-type": MIME[path14.extname(file3)] || "application/octet-stream",
3232
3371
  "content-length": st2.size,
3233
3372
  "cache-control": "no-cache",
3234
3373
  "x-content-type-options": "nosniff"
@@ -3237,7 +3376,7 @@ data: ${JSON.stringify({ state: "revoked" })}
3237
3376
  }
3238
3377
  const ARTIFACT_CSP = "sandbox allow-scripts allow-popups allow-popups-to-escape-sandbox allow-forms";
3239
3378
  function streamFile(res, file3, headers) {
3240
- const rs = fs14.createReadStream(file3);
3379
+ const rs = fs15.createReadStream(file3);
3241
3380
  rs.once("open", () => {
3242
3381
  res.writeHead(200, headers);
3243
3382
  rs.pipe(res);
@@ -3249,9 +3388,20 @@ data: ${JSON.stringify({ state: "revoked" })}
3249
3388
  });
3250
3389
  res.once("close", () => rs.destroy());
3251
3390
  }
3391
+ function assetToken(s, viewer) {
3392
+ const who = viewer.role === "owner" ? "owner" : `invite:${viewer.token}`;
3393
+ return crypto5.createHmac("sha256", ownerToken).update(`austere-axi asset\0${s.id}\0${who}`).digest("base64url");
3394
+ }
3395
+ function assetTokenValid(s, token) {
3396
+ let ok2 = safeEqual(token, assetToken(s, OWNER));
3397
+ for (const inv of invites.list()) {
3398
+ if (inv.sessionId === s.id && safeEqual(token, assetToken(s, { role: "reviewer", name: inv.name, auto: inv.auto, token: inv.token }))) ok2 = true;
3399
+ }
3400
+ return ok2;
3401
+ }
3252
3402
  function serveArtifact(req, res, s, rest) {
3253
- const dir = path13.dirname(s.file);
3254
- const base = path13.basename(s.file);
3403
+ const dir = path14.dirname(s.file);
3404
+ const base = path14.basename(s.file);
3255
3405
  if (rest === base || rest === "") {
3256
3406
  const html = readFileSafe(s.file);
3257
3407
  if (html == null) return send(res, 404, "artifact file is missing");
@@ -3264,27 +3414,14 @@ data: ${JSON.stringify({ state: "revoked" })}
3264
3414
  } catch {
3265
3415
  return send(res, 400, "bad path");
3266
3416
  }
3267
- if (decoded.includes("\0")) return send(res, 404, "not found");
3268
- const file3 = path13.resolve(dir, decoded);
3269
- if (!file3.startsWith(dir + path13.sep) || decoded.split(/[/\\]/).some((p) => p.startsWith("."))) {
3270
- return send(res, 404, "not found");
3271
- }
3272
- fs14.realpath(dir, (e1, realDir) => {
3273
- if (e1) return send(res, 404, "not found");
3274
- fs14.realpath(file3, (e2, real) => {
3275
- if (e2 || !real.startsWith(realDir + path13.sep)) return send(res, 404, "not found");
3276
- if (path13.relative(realDir, real).split(path13.sep).some((p) => p.startsWith("."))) return send(res, 404, "not found");
3277
- fs14.stat(real, (err, st2) => {
3278
- if (err || !st2.isFile() || st2.size > 64 * 1024 * 1024) return send(res, 404, "not found");
3279
- streamFile(res, real, {
3280
- "content-type": MIME[path13.extname(real).toLowerCase()] || "application/octet-stream",
3281
- "content-length": st2.size,
3282
- "cache-control": "no-cache",
3283
- "x-content-type-options": "nosniff",
3284
- "content-security-policy": ARTIFACT_CSP
3285
- });
3286
- });
3287
- });
3417
+ const sib = resolveSibling(dir, decoded);
3418
+ if (!sib.ok) return send(res, 404, "not found");
3419
+ streamFile(res, sib.file, {
3420
+ "content-type": MIME[path14.extname(sib.file).toLowerCase()] || "application/octet-stream",
3421
+ "content-length": sib.size,
3422
+ "cache-control": "no-cache",
3423
+ "x-content-type-options": "nosniff",
3424
+ "content-security-policy": ARTIFACT_CSP
3288
3425
  });
3289
3426
  }
3290
3427
  function chromePage(s, viewer) {
@@ -3293,8 +3430,8 @@ data: ${JSON.stringify({ state: "revoked" })}
3293
3430
  title: s.title,
3294
3431
  file: viewer.role === "owner" ? s.file : "",
3295
3432
  viewer: { role: viewer.role, name: viewer.name, auto: viewer.role === "reviewer" ? viewer.auto : void 0 },
3296
- name: path13.basename(s.file),
3297
- artifactUrl: `/s/${s.id}/a/${encodeURIComponent(path13.basename(s.file))}`,
3433
+ name: path14.basename(s.file),
3434
+ artifactUrl: `/s/${s.id}/a/${assetToken(s, viewer)}/${encodeURIComponent(path14.basename(s.file))}`,
3298
3435
  version: VERSION
3299
3436
  };
3300
3437
  return `<!doctype html>
@@ -3328,7 +3465,7 @@ data: ${JSON.stringify({ state: "revoked" })}
3328
3465
  function hubOpen(res, id) {
3329
3466
  const a = /^[a-f0-9]{12}$/.test(id) ? hub.artifact(id) : null;
3330
3467
  if (!a) return send(res, 404, "not in the hub");
3331
- if (!fs14.existsSync(a.file)) return send(res, 410, `${a.file} no longer exists`);
3468
+ if (!fs15.existsSync(a.file)) return send(res, 410, `${a.file} no longer exists`);
3332
3469
  const existed = store.byFile(a.file);
3333
3470
  const s = store.ensure(a.file);
3334
3471
  if (!existed) store.touch(s);
@@ -3406,6 +3543,10 @@ data: ${JSON.stringify({ state: "revoked" })}
3406
3543
  });
3407
3544
  return res.end();
3408
3545
  }
3546
+ const tokened = /^\/a\/([A-Za-z0-9_-]{43})\/(.*)$/.exec(sub);
3547
+ if (tokened && (req.method === "GET" || req.method === "HEAD") && assetTokenValid(s, tokened[1])) {
3548
+ return serveArtifact(req, res, s, tokened[2]);
3549
+ }
3409
3550
  const who = identify(req, s);
3410
3551
  if (!who) {
3411
3552
  if (tokenFromCookie(req.headers.cookie) == null) return send(res, 403, notOwner(req, "Ask the owner for a link to this page."));
@@ -3567,8 +3708,8 @@ ${message}`;
3567
3708
  const html = readFileSafe(s.file);
3568
3709
  if (html == null) return send(res, 404, "artifact file is missing");
3569
3710
  try {
3570
- const out = exportHtml(html);
3571
- const name = path13.basename(s.file).replace(/\.html?$/i, "") + ".export.html";
3711
+ const out = exportHtml(html, path14.dirname(s.file));
3712
+ const name = path14.basename(s.file).replace(/\.html?$/i, "") + ".export.html";
3572
3713
  return send(res, 200, out.html, {
3573
3714
  "content-type": "text/html; charset=utf-8",
3574
3715
  "content-disposition": `attachment; filename="${name.replace(/"/g, "")}"`,
@@ -3585,7 +3726,7 @@ ${message}`;
3585
3726
  if (listeners.has(host)) return resolve(true);
3586
3727
  const server = http3.createServer((req, res) => {
3587
3728
  handle(req, res).catch((err) => {
3588
- log("error", req.method, String(req.url).split("?")[0], String(err?.stack || err));
3729
+ log("error", req.method, String(req.url).split("?")[0].replace(/\/a\/[\w-]{43}\//, "/a/\u2026/"), String(err?.stack || err));
3589
3730
  if (!res.headersSent) send(res, err.status || 500, { error: err.status ? String(err.message) : "internal error" });
3590
3731
  else res.end();
3591
3732
  });
@@ -3664,6 +3805,7 @@ var MIME, SHORT_GRACE_MS, LOGIN_TTL_MS, OWNER, REVIEWER_KINDS, RENDER_CHECK_AUTH
3664
3805
  var init_server = __esm({
3665
3806
  "src/cli/server.ts"() {
3666
3807
  "use strict";
3808
+ init_assets();
3667
3809
  init_artifact();
3668
3810
  init_export();
3669
3811
  init_net();
@@ -3724,7 +3866,7 @@ __export(mcp_exports, {
3724
3866
  runMcp: () => runMcp
3725
3867
  });
3726
3868
  import { spawn as spawn3 } from "node:child_process";
3727
- import path14 from "node:path";
3869
+ import path15 from "node:path";
3728
3870
  import readline from "node:readline";
3729
3871
  function runCli({ args, stdin }, signal) {
3730
3872
  return new Promise((resolve) => {
@@ -3767,7 +3909,7 @@ async function callTool(name, a, signal) {
3767
3909
  const bad = validate2(tool, a);
3768
3910
  if (bad) return { content: [{ type: "text", text: `status: error
3769
3911
  error: ${bad}` }], isError: true };
3770
- if (typeof a.file === "string") a = { ...a, file: path14.resolve(a.file) };
3912
+ if (typeof a.file === "string") a = { ...a, file: path15.resolve(a.file) };
3771
3913
  const args = a;
3772
3914
  let text = "";
3773
3915
  let isError = false;
@@ -3869,7 +4011,7 @@ var init_mcp = __esm({
3869
4011
  isRecord2 = (v) => !!v && typeof v === "object";
3870
4012
  PROTOCOLS = ["2025-11-25", "2025-06-18", "2024-11-05"];
3871
4013
  POLL_TIMEOUT_MS = 5e4;
3872
- INSTRUCTIONS = `austere-axi turns plans, comparisons, decisions, code reviews and reports into review pages the user annotates in the browser. Call \`spec\` once per session for the workflow, the file skeleton and the component reference. Results are the CLI's output: where a \`next:\` hint names an \`austere-axi <command>\`, call the tool of that name instead. Pass absolute file paths. The reviewer judges how a page looks: do not open, screenshot or inspect it yourself unless the user asks.`;
4014
+ INSTRUCTIONS = `austere-axi turns plans, comparisons, decisions, code reviews and reports into review pages the user annotates in the browser. Call \`spec\` once per session for the workflow, the file skeleton and the component reference. Results are the CLI's output: where a \`next:\` hint names an \`austere-axi <command>\`, call the tool of that name instead. Pass absolute file paths. The reviewer judges how a page looks: do not open, screenshot or inspect it yourself unless the user asks. Revise a page only with the \`patch\` tool: send the changed statements (every statement that shows a changed value), never the whole file.`;
3873
4015
  file2 = { type: "string", description: "Path of the .html page. Use an absolute path; a relative one resolves against the server's working directory." };
3874
4016
  TOOLS = {
3875
4017
  spec: {
@@ -3952,8 +4094,8 @@ var init_mcp = __esm({
3952
4094
  });
3953
4095
 
3954
4096
  // src/cli/main.ts
3955
- import fs15 from "node:fs";
3956
- import path15 from "node:path";
4097
+ import fs16 from "node:fs";
4098
+ import path16 from "node:path";
3957
4099
 
3958
4100
  // node_modules/@openuidev/lang-core/dist/shared-Dzgc28Wo.mjs
3959
4101
  var TELEMETRY_REQUEST_TIMEOUT_MS = 2e3;
@@ -4814,10 +4956,10 @@ function mergeDefs(...defs) {
4814
4956
  function cloneDef(schema) {
4815
4957
  return mergeDefs(schema._zod.def);
4816
4958
  }
4817
- function getElementAtPath(obj, path16) {
4818
- if (!path16)
4959
+ function getElementAtPath(obj, path17) {
4960
+ if (!path17)
4819
4961
  return obj;
4820
- return path16.reduce((acc, key2) => acc?.[key2], obj);
4962
+ return path17.reduce((acc, key2) => acc?.[key2], obj);
4821
4963
  }
4822
4964
  function promiseAllObject(promisesObj) {
4823
4965
  const keys = Object.keys(promisesObj);
@@ -5157,11 +5299,11 @@ function explicitlyAborted(x, startIndex = 0) {
5157
5299
  }
5158
5300
  return false;
5159
5301
  }
5160
- function prefixIssues(path16, issues) {
5302
+ function prefixIssues(path17, issues) {
5161
5303
  return issues.map((iss) => {
5162
5304
  var _a3;
5163
5305
  (_a3 = iss).path ?? (_a3.path = []);
5164
- iss.path.unshift(path16);
5306
+ iss.path.unshift(path17);
5165
5307
  return iss;
5166
5308
  });
5167
5309
  }
@@ -5623,16 +5765,16 @@ function flattenError(error62, mapper = (issue2) => issue2.message) {
5623
5765
  }
5624
5766
  function formatError(error62, mapper = (issue2) => issue2.message) {
5625
5767
  const fieldErrors = { _errors: [] };
5626
- const processError = (error63, path16 = []) => {
5768
+ const processError = (error63, path17 = []) => {
5627
5769
  for (const issue2 of error63.issues) {
5628
5770
  if (issue2.code === "invalid_union" && issue2.errors.length) {
5629
- issue2.errors.map((issues) => processError({ issues }, [...path16, ...issue2.path]));
5771
+ issue2.errors.map((issues) => processError({ issues }, [...path17, ...issue2.path]));
5630
5772
  } else if (issue2.code === "invalid_key") {
5631
- processError({ issues: issue2.issues }, [...path16, ...issue2.path]);
5773
+ processError({ issues: issue2.issues }, [...path17, ...issue2.path]);
5632
5774
  } else if (issue2.code === "invalid_element") {
5633
- processError({ issues: issue2.issues }, [...path16, ...issue2.path]);
5775
+ processError({ issues: issue2.issues }, [...path17, ...issue2.path]);
5634
5776
  } else {
5635
- const fullpath = [...path16, ...issue2.path];
5777
+ const fullpath = [...path17, ...issue2.path];
5636
5778
  if (fullpath.length === 0) {
5637
5779
  fieldErrors._errors.push(mapper(issue2));
5638
5780
  } else {
@@ -5671,17 +5813,17 @@ function formatError(error62, mapper = (issue2) => issue2.message) {
5671
5813
  }
5672
5814
  function treeifyError(error62, mapper = (issue2) => issue2.message) {
5673
5815
  const result = { errors: [] };
5674
- const processError = (error63, path16 = []) => {
5816
+ const processError = (error63, path17 = []) => {
5675
5817
  var _a3;
5676
5818
  for (const issue2 of error63.issues) {
5677
5819
  if (issue2.code === "invalid_union" && issue2.errors.length) {
5678
- issue2.errors.map((issues) => processError({ issues }, [...path16, ...issue2.path]));
5820
+ issue2.errors.map((issues) => processError({ issues }, [...path17, ...issue2.path]));
5679
5821
  } else if (issue2.code === "invalid_key") {
5680
- processError({ issues: issue2.issues }, [...path16, ...issue2.path]);
5822
+ processError({ issues: issue2.issues }, [...path17, ...issue2.path]);
5681
5823
  } else if (issue2.code === "invalid_element") {
5682
- processError({ issues: issue2.issues }, [...path16, ...issue2.path]);
5824
+ processError({ issues: issue2.issues }, [...path17, ...issue2.path]);
5683
5825
  } else {
5684
- const fullpath = [...path16, ...issue2.path];
5826
+ const fullpath = [...path17, ...issue2.path];
5685
5827
  if (fullpath.length === 0) {
5686
5828
  result.errors.push(mapper(issue2));
5687
5829
  continue;
@@ -5720,8 +5862,8 @@ function treeifyError(error62, mapper = (issue2) => issue2.message) {
5720
5862
  }
5721
5863
  function toDotPath(_path) {
5722
5864
  const segs = [];
5723
- const path16 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
5724
- for (const seg of path16) {
5865
+ const path17 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
5866
+ for (const seg of path17) {
5725
5867
  if (typeof seg === "number")
5726
5868
  segs.push(`[${seg}]`);
5727
5869
  else if (typeof seg === "symbol")
@@ -22859,13 +23001,13 @@ function resolveRef(ref, ctx) {
22859
23001
  if (!ref.startsWith("#")) {
22860
23002
  throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
22861
23003
  }
22862
- const path16 = ref.slice(1).split("/").filter(Boolean);
22863
- if (path16.length === 0) {
23004
+ const path17 = ref.slice(1).split("/").filter(Boolean);
23005
+ if (path17.length === 0) {
22864
23006
  return ctx.rootSchema;
22865
23007
  }
22866
23008
  const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
22867
- if (path16[0] === defsKey) {
22868
- const key2 = path16[1] === void 0 ? void 0 : decodeJSONPointerSegment(path16[1]);
23009
+ if (path17[0] === defsKey) {
23010
+ const key2 = path17[1] === void 0 ? void 0 : decodeJSONPointerSegment(path17[1]);
22869
23011
  if (!key2 || !ctx.defs[key2]) {
22870
23012
  throw new Error(`Reference not found: ${ref}`);
22871
23013
  }
@@ -23747,11 +23889,11 @@ function defineAction(config2) {
23747
23889
  ref: actionRef(config2.name, data)
23748
23890
  };
23749
23891
  }
23750
- function resolveField(obj, path16) {
23751
- if (!path16 || obj == null) return void 0;
23752
- if (!path16.includes(".")) return obj[path16];
23892
+ function resolveField(obj, path17) {
23893
+ if (!path17 || obj == null) return void 0;
23894
+ if (!path17.includes(".")) return obj[path17];
23753
23895
  let cur = obj;
23754
- for (const p of path16.split(".")) {
23896
+ for (const p of path17.split(".")) {
23755
23897
  if (cur == null) return void 0;
23756
23898
  cur = cur[p];
23757
23899
  }
@@ -25127,14 +25269,14 @@ function checkTypeMismatch(value, info) {
25127
25269
  };
25128
25270
  return null;
25129
25271
  }
25130
- function validationMessage(component, path16, issue2) {
25272
+ function validationMessage(component, path17, issue2) {
25131
25273
  switch (issue2.code) {
25132
25274
  case "type-mismatch":
25133
- return `field "${path16}" expects ${issue2.expected} but got ${issue2.actual}`;
25275
+ return `field "${path17}" expects ${issue2.expected} but got ${issue2.actual}`;
25134
25276
  case "missing-required":
25135
- return `missing required field "${path16}"${issue2.signature ? ` \u2014 signature: ${issue2.signature}` : ""}`;
25277
+ return `missing required field "${path17}"${issue2.signature ? ` \u2014 signature: ${issue2.signature}` : ""}`;
25136
25278
  case "null-required":
25137
- return `required field "${path16}" cannot be null${issue2.signature ? ` \u2014 signature: ${issue2.signature}` : ""}`;
25279
+ return `required field "${path17}" cannot be null${issue2.signature ? ` \u2014 signature: ${issue2.signature}` : ""}`;
25138
25280
  case "unknown-component":
25139
25281
  return `Unknown component "${component}" \u2014 not found in catalog or builtins${issue2.available?.length ? `. Available components: ${issue2.available.join(", ")}` : ""}`;
25140
25282
  case "inline-reserved":
@@ -25149,12 +25291,12 @@ function validationMessage(component, path16, issue2) {
25149
25291
  return component ? `No root statement, and no statement calls the root component ${component}` : "No root statement";
25150
25292
  }
25151
25293
  }
25152
- function pushValidationIssue(ctx, component, path16, issue2) {
25294
+ function pushValidationIssue(ctx, component, path17, issue2) {
25153
25295
  ctx.errors.push({
25154
25296
  code: issue2.code,
25155
25297
  component,
25156
- path: path16,
25157
- message: validationMessage(component, path16, issue2),
25298
+ path: path17,
25299
+ message: validationMessage(component, path17, issue2),
25158
25300
  statementId: ctx.currentStatementId,
25159
25301
  ...issue2.code === "no-root" && issue2.reason === "fallback" && { severity: "warning" }
25160
25302
  });
@@ -25164,10 +25306,10 @@ function resolveInvalidValue(required2, defaultValue) {
25164
25306
  if (defaultValue !== void 0) return defaultValue;
25165
25307
  return required2 ? INVALID2 : void 0;
25166
25308
  }
25167
- function validateElementPosition(element, s, component, path16, ctx) {
25309
+ function validateElementPosition(element, s, component, path17, ctx) {
25168
25310
  if (isCompositeSchema(s)) return false;
25169
25311
  if (typeof s["type"] === "string" || Array.isArray(s["enum"]) || "const" in s) {
25170
- pushValidationIssue(ctx, component, path16, {
25312
+ pushValidationIssue(ctx, component, path17, {
25171
25313
  code: "type-mismatch",
25172
25314
  expected: typeof s["type"] === "string" ? s["type"] : "a literal value",
25173
25315
  actual: `component "${element.typeName}"`
@@ -25183,9 +25325,9 @@ function isOnlyComponentSlot(s, ctx) {
25183
25325
  const names = slotRefNames(s);
25184
25326
  return names.length > 0 && names.every((n) => n !== void 0 && ctx.cat?.has(n));
25185
25327
  }
25186
- function validateObjectValue(value, s, component, path16, ctx) {
25328
+ function validateObjectValue(value, s, component, path17, ctx) {
25187
25329
  if (typeof value !== "object" || Array.isArray(value)) {
25188
- pushValidationIssue(ctx, component, path16, {
25330
+ pushValidationIssue(ctx, component, path17, {
25189
25331
  code: "type-mismatch",
25190
25332
  expected: "object",
25191
25333
  actual: jsType(value)
@@ -25205,14 +25347,14 @@ function validateObjectValue(value, s, component, path16, ctx) {
25205
25347
  (out ??= { ...obj })[key2] = fallback;
25206
25348
  continue;
25207
25349
  }
25208
- pushValidationIssue(ctx, component, `${path16}/${key2}`, { code: present ? "null-required" : "missing-required" });
25350
+ pushValidationIssue(ctx, component, `${path17}/${key2}`, { code: present ? "null-required" : "missing-required" });
25209
25351
  invalid = true;
25210
25352
  }
25211
25353
  }
25212
25354
  for (const key2 of Object.keys(props)) {
25213
25355
  if (!(key2 in obj)) continue;
25214
25356
  const sub = props[key2];
25215
- let next = validateSchemaValue(obj[key2], sub, component, `${path16}/${key2}`, ctx);
25357
+ let next = validateSchemaValue(obj[key2], sub, component, `${path17}/${key2}`, ctx);
25216
25358
  if (next === INVALID2) {
25217
25359
  next = resolveInvalidValue(required2.includes(key2), getSchemaDefaultValue(sub));
25218
25360
  if (next === INVALID2) {
@@ -25227,9 +25369,9 @@ function validateObjectValue(value, s, component, path16, ctx) {
25227
25369
  }
25228
25370
  return invalid ? INVALID2 : out ?? obj;
25229
25371
  }
25230
- function validateArrayValue(value, s, component, path16, ctx) {
25372
+ function validateArrayValue(value, s, component, path17, ctx) {
25231
25373
  if (!Array.isArray(value)) {
25232
- pushValidationIssue(ctx, component, path16, {
25374
+ pushValidationIssue(ctx, component, path17, {
25233
25375
  code: "type-mismatch",
25234
25376
  expected: "array",
25235
25377
  actual: jsType(value)
@@ -25240,19 +25382,19 @@ function validateArrayValue(value, s, component, path16, ctx) {
25240
25382
  if (!items || typeof items !== "object" || Array.isArray(items)) return value;
25241
25383
  let out;
25242
25384
  for (let i = 0; i < value.length; i++) {
25243
- const next = validateSchemaValue(value[i], items, component, `${path16}/${i}`, ctx);
25385
+ const next = validateSchemaValue(value[i], items, component, `${path17}/${i}`, ctx);
25244
25386
  if (next !== value[i] && !out) out = value.slice(0, i);
25245
25387
  if (out && next !== INVALID2) out.push(next);
25246
25388
  }
25247
25389
  return out ?? value;
25248
25390
  }
25249
- function validateLeafValue(value, s, component, path16, ctx) {
25391
+ function validateLeafValue(value, s, component, path17, ctx) {
25250
25392
  const leaf = getScalarTypeInfo(s);
25251
25393
  if (leaf.expectedType == null && leaf.enumValues == null) return false;
25252
25394
  if (leaf.enumValues && ctx.partial) return false;
25253
25395
  const mismatch = checkTypeMismatch(value, leaf);
25254
25396
  if (mismatch) {
25255
- pushValidationIssue(ctx, component, path16, {
25397
+ pushValidationIssue(ctx, component, path17, {
25256
25398
  code: "type-mismatch",
25257
25399
  ...mismatch
25258
25400
  });
@@ -25260,15 +25402,15 @@ function validateLeafValue(value, s, component, path16, ctx) {
25260
25402
  }
25261
25403
  return false;
25262
25404
  }
25263
- function validateSchemaValue(value, schema, component, path16, ctx) {
25405
+ function validateSchemaValue(value, schema, component, path17, ctx) {
25264
25406
  if (!schema || typeof schema !== "object" || Array.isArray(schema)) return value;
25265
25407
  const s = schema;
25266
25408
  if (value == null) return value;
25267
25409
  if (isASTNode(value)) return value;
25268
- if (isElementNode(value)) return validateElementPosition(value, s, component, path16, ctx) ? INVALID2 : value;
25410
+ if (isElementNode(value)) return validateElementPosition(value, s, component, path17, ctx) ? INVALID2 : value;
25269
25411
  if (isCompositeSchema(s)) {
25270
25412
  if (!isOnlyComponentSlot(s, ctx)) return value;
25271
- pushValidationIssue(ctx, component, path16, {
25413
+ pushValidationIssue(ctx, component, path17, {
25272
25414
  code: "type-mismatch",
25273
25415
  expected: slotRefNames(s).join(" | "),
25274
25416
  actual: Array.isArray(value) ? "array" : typeof value === "object" ? "plain object" : typeof value
@@ -25276,9 +25418,9 @@ function validateSchemaValue(value, schema, component, path16, ctx) {
25276
25418
  return INVALID2;
25277
25419
  }
25278
25420
  const type = s["type"];
25279
- if (type === "object") return validateObjectValue(value, s, component, path16, ctx);
25280
- if (type === "array") return validateArrayValue(value, s, component, path16, ctx);
25281
- return validateLeafValue(value, s, component, path16, ctx) ? INVALID2 : value;
25421
+ if (type === "object") return validateObjectValue(value, s, component, path17, ctx);
25422
+ if (type === "array") return validateArrayValue(value, s, component, path17, ctx);
25423
+ return validateLeafValue(value, s, component, path17, ctx) ? INVALID2 : value;
25282
25424
  }
25283
25425
  function containsDynamicValue(v) {
25284
25426
  if (v == null || typeof v !== "object") return false;
@@ -28904,11 +29046,11 @@ function routeOrthogonal({ boxes, edges: input2, xLines = [], yLines = [], avoid
28904
29046
  const blocked = solid.some(([id, r]) => id !== e.from && id !== e.to && seg.x0 < r.x + r.width && r.x < seg.x1 && seg.y0 < r.y + r.height && r.y < seg.y1);
28905
29047
  if (!blocked) {
28906
29048
  routes.set(e.id, [a, b]);
28907
- const path17 = [];
29049
+ const path18 = [];
28908
29050
  const [i0, j0, i1, j1] = [ix(a.x), iy(a.y), ix(b.x), iy(b.y)];
28909
- if (j0 === j1) for (let i = Math.min(i0, i1); i <= Math.max(i0, i1); i++) path17.push([i, j0]);
28910
- else for (let j = Math.min(j0, j1); j <= Math.max(j0, j1); j++) path17.push([i0, j]);
28911
- if (path17.every((pt2) => pt2[0] !== void 0 && pt2[1] !== void 0)) mark(path17);
29051
+ if (j0 === j1) for (let i = Math.min(i0, i1); i <= Math.max(i0, i1); i++) path18.push([i, j0]);
29052
+ else for (let j = Math.min(j0, j1); j <= Math.max(j0, j1); j++) path18.push([i0, j]);
29053
+ if (path18.every((pt2) => pt2[0] !== void 0 && pt2[1] !== void 0)) mark(path18);
28912
29054
  continue;
28913
29055
  }
28914
29056
  }
@@ -28918,12 +29060,12 @@ function routeOrthogonal({ boxes, edges: input2, xLines = [], yLines = [], avoid
28918
29060
  const sj = iy(st2.y);
28919
29061
  const ti = ix(tt2.x);
28920
29062
  const tj = iy(tt2.y);
28921
- let path16 = null;
29063
+ let path17 = null;
28922
29064
  if (si !== void 0 && sj !== void 0 && ti !== void 0 && tj !== void 0) {
28923
- path16 = search(si, sj, dirOf(e.fromSide), ti, tj, reverse[dirOf(e.toSide)]);
29065
+ path17 = search(si, sj, dirOf(e.fromSide), ti, tj, reverse[dirOf(e.toSide)]);
28924
29066
  }
28925
- const pts = path16 ? path16.map(([i, j]) => ({ x: xs[i], y: ys[j] })) : elbow(st2, tt2, e.fromSide);
28926
- if (path16) mark(path16);
29067
+ const pts = path17 ? path17.map(([i, j]) => ({ x: xs[i], y: ys[j] })) : elbow(st2, tt2, e.fromSide);
29068
+ if (path17) mark(path17);
28927
29069
  routes.set(e.id, simplify([e.fromPort, ...pts, e.toPort]));
28928
29070
  }
28929
29071
  return routes;
@@ -28992,10 +29134,10 @@ function routeOrthogonal({ boxes, edges: input2, xLines = [], yLines = [], avoid
28992
29134
  }
28993
29135
  return out.reverse();
28994
29136
  }
28995
- function mark(path16) {
28996
- for (let k = 1; k < path16.length; k++) {
28997
- const [i0, j0] = path16[k - 1];
28998
- const [i1, j1] = path16[k];
29137
+ function mark(path17) {
29138
+ for (let k = 1; k < path17.length; k++) {
29139
+ const [i0, j0] = path17[k - 1];
29140
+ const [i1, j1] = path17[k];
28999
29141
  if (j0 === j1) {
29000
29142
  hUse[j0 * nx + Math.min(i0, i1)]++;
29001
29143
  hPass[j0 * nx + i0]++;
@@ -29701,53 +29843,52 @@ init_sessions();
29701
29843
  init_export();
29702
29844
 
29703
29845
  // src/cli/render-check.ts
29846
+ init_artifact();
29704
29847
  init_export();
29705
29848
  import { spawn as spawn2 } from "node:child_process";
29706
- import fs10 from "node:fs";
29849
+ import fs11 from "node:fs";
29707
29850
  import http2 from "node:http";
29708
29851
  import os2 from "node:os";
29709
- import path9 from "node:path";
29852
+ import path10 from "node:path";
29710
29853
  var WIDTHS = [1280, 390];
29711
29854
  var LAUNCH_TIMEOUT_MS = 15e3;
29712
29855
  var RENDER_TIMEOUT_MS = 3e4;
29713
29856
  var FRAME_SANDBOX = "allow-scripts";
29714
29857
  function findChrome() {
29715
29858
  const fromEnv = process.env.CHROME_PATH;
29716
- if (fromEnv) return fs10.existsSync(fromEnv) ? fromEnv : null;
29859
+ if (fromEnv) return fs11.existsSync(fromEnv) ? fromEnv : null;
29717
29860
  const candidates = [];
29718
29861
  if (process.platform === "darwin") {
29719
29862
  for (const p of ["Google Chrome.app/Contents/MacOS/Google Chrome", "Chromium.app/Contents/MacOS/Chromium", "Microsoft Edge.app/Contents/MacOS/Microsoft Edge"]) {
29720
- candidates.push(path9.join("/Applications", p));
29863
+ candidates.push(path10.join("/Applications", p));
29721
29864
  }
29722
29865
  } else if (process.platform === "win32") {
29723
29866
  const roots = [process.env.PROGRAMFILES, process.env["PROGRAMFILES(X86)"], process.env.LOCALAPPDATA].filter((x) => typeof x === "string");
29724
- for (const r of roots) candidates.push(path9.join(r, "Google/Chrome/Application/chrome.exe"), path9.join(r, "Microsoft/Edge/Application/msedge.exe"));
29867
+ for (const r of roots) candidates.push(path10.join(r, "Google/Chrome/Application/chrome.exe"), path10.join(r, "Microsoft/Edge/Application/msedge.exe"));
29725
29868
  } else {
29726
- const dirs = (process.env.PATH || "").split(path9.delimiter).filter(Boolean);
29869
+ const dirs = (process.env.PATH || "").split(path10.delimiter).filter(Boolean);
29727
29870
  for (const name of ["google-chrome", "google-chrome-stable", "chromium", "chromium-browser", "microsoft-edge"]) {
29728
- for (const d of dirs) candidates.push(path9.join(d, name));
29871
+ for (const d of dirs) candidates.push(path10.join(d, name));
29729
29872
  }
29730
29873
  }
29731
- return candidates.find((c) => fs10.existsSync(c)) ?? null;
29874
+ return candidates.find((c) => fs11.existsSync(c)) ?? null;
29732
29875
  }
29733
29876
  var MEASURE = `<script>(() => {
29734
29877
  const exceptions = [];
29735
29878
  addEventListener("error", (e) => exceptions.push(String((e.error && e.error.message) || e.message || "error")));
29736
29879
  addEventListener("unhandledrejection", (e) => exceptions.push(String((e.reason && e.reason.message) || e.reason)));
29737
29880
  const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
29738
- addEventListener("message", async (e) => {
29739
- const d = e.data;
29740
- if (e.source !== parent || !d || d.austereMeasure !== 1) return;
29881
+ const idOf = (el) => (el.closest("[data-oui-id]") && el.closest("[data-oui-id]").getAttribute("data-oui-id")) || "";
29882
+ const settle = async () => {
29741
29883
  for (let i = 0; i < 100; i++) {
29742
29884
  const pending = [...document.querySelectorAll(".austere-mermaid")].filter((m) => !m.querySelector("svg, .austere-render-error"));
29743
29885
  if (!pending.length) break;
29744
29886
  await sleep(100);
29745
29887
  }
29746
29888
  await sleep(200);
29747
- const shown = [...document.querySelectorAll(".austere-render-error")].map((el) => ({
29748
- statement: (el.closest("[data-oui-id]") && el.closest("[data-oui-id]").getAttribute("data-oui-id")) || "",
29749
- error: el.textContent.trim().slice(0, 300),
29750
- }));
29889
+ };
29890
+ const snapshot = () => {
29891
+ const shown = [...document.querySelectorAll(".austere-render-error")].map((el) => ({ statement: idOf(el), error: el.textContent.trim().slice(0, 300) }));
29751
29892
  const se = document.scrollingElement;
29752
29893
  let past = null;
29753
29894
  if (se.scrollWidth > se.clientWidth + 1) {
@@ -29765,11 +29906,58 @@ var MEASURE = `<script>(() => {
29765
29906
  const out = [...document.body.querySelectorAll("*")].slice(0, 20000).filter(sticksOut);
29766
29907
  const outSet = new Set(out);
29767
29908
  const leaves = out.filter((el) => ![...el.children].some((c) => outSet.has(c)));
29768
- const ids = leaves.map((el) => el.closest("[data-oui-id]") && el.closest("[data-oui-id]").getAttribute("data-oui-id")).filter(Boolean);
29769
- past = [...new Set(ids)].slice(0, 8);
29909
+ past = leaves.map(idOf).filter(Boolean);
29910
+ }
29911
+ // The sideways check above skips these: the diagram scrolls in its own box, so only a
29912
+ // reader who notices the scroll sees all of it.
29913
+ const wide = [...document.querySelectorAll(".austere-mermaid-svg")]
29914
+ .filter((box) => box.scrollWidth > box.clientWidth + 1)
29915
+ .map((box) => ({ statement: idOf(box), width: box.scrollWidth, box: box.clientWidth }));
29916
+ const ids = [...document.querySelectorAll("[data-oui-id]")].map((el) => el.getAttribute("data-oui-id"));
29917
+ return { shown, past, wide, ids };
29918
+ };
29919
+ // Radix tabs switch on mousedown with the main button.
29920
+ const press = (tab) => tab.dispatchEvent(new MouseEvent("mousedown", { bubbles: true, cancelable: true, button: 0 }));
29921
+ // Opens every tab under \`scope\` (the page, or an open tab panel), measuring each, then walks
29922
+ // the tabs inside each panel while it is open: an inner Tabs mounts only with its panel.
29923
+ // Restores each tab list's open tab. Capped so a huge page cannot stall the check.
29924
+ let presses = 0;
29925
+ const walk = async (scope, snaps) => {
29926
+ const lists = [...scope.querySelectorAll('[role="tablist"]')].filter((l) => (l.parentElement.closest('[role="tabpanel"]') || document) === scope);
29927
+ for (const list of lists) {
29928
+ const count = list.querySelectorAll('[role="tab"]').length;
29929
+ const opened = [...list.querySelectorAll('[role="tab"]')].findIndex((t) => t.getAttribute("aria-selected") === "true");
29930
+ for (let i = 0; i < count && presses < 200; i++) {
29931
+ const tab = list.querySelectorAll('[role="tab"]')[i];
29932
+ if (!tab) break;
29933
+ if (tab.getAttribute("aria-selected") !== "true") {
29934
+ press(tab);
29935
+ presses++;
29936
+ await settle();
29937
+ snaps.push(snapshot());
29938
+ }
29939
+ const panel = document.getElementById(tab.getAttribute("aria-controls") || "");
29940
+ if (panel) await walk(panel, snaps);
29941
+ }
29942
+ const back = list.querySelectorAll('[role="tab"]')[opened];
29943
+ if (back && back.getAttribute("aria-selected") !== "true") press(back);
29770
29944
  }
29771
- const statements = new Set([...document.querySelectorAll("[data-oui-id]")].map((el) => el.getAttribute("data-oui-id"))).size;
29772
- parent.postMessage({ austereMeasured: 1, id: d.id, shown, exceptions: exceptions.slice(0, 20), past, statements }, "*");
29945
+ };
29946
+ addEventListener("message", async (e) => {
29947
+ const d = e.data;
29948
+ if (e.source !== parent || !d || d.austereMeasure !== 1) return;
29949
+ await settle();
29950
+ presses = 0;
29951
+ const snaps = [snapshot()];
29952
+ await walk(document, snaps);
29953
+ const overflowed = snaps.filter((x) => x.past);
29954
+ const past = overflowed.length ? [...new Set(overflowed.flatMap((x) => x.past))].slice(0, 8) : null;
29955
+ const shown = [];
29956
+ for (const s of snaps.flatMap((x) => x.shown)) if (!shown.some((y) => y.statement === s.statement && y.error === s.error)) shown.push(s);
29957
+ const wide = [];
29958
+ for (const w of snaps.flatMap((x) => x.wide)) if (!wide.some((y) => y.statement === w.statement)) wide.push(w);
29959
+ const statements = new Set(snaps.flatMap((x) => x.ids)).size;
29960
+ parent.postMessage({ austereMeasured: 1, id: d.id, shown, exceptions: exceptions.slice(0, 20), past, wide, statements }, "*");
29773
29961
  });
29774
29962
  })();</script>`;
29775
29963
  var HARNESS = `<!doctype html><html><head><meta charset="utf-8"></head><body style="margin:0">
@@ -29800,16 +29988,19 @@ window.__austereReport = async (widths) => {
29800
29988
  for (let i = 0; i < 150 && !ready; i++) await sleep(100);
29801
29989
  const overflow = [];
29802
29990
  let last = null;
29991
+ let wide = null;
29803
29992
  for (const w of widths) {
29804
29993
  f.style.width = w + "px";
29805
29994
  await sleep(300);
29806
29995
  last = await measure();
29807
29996
  if (last.past) overflow.push({ width: w, statements: last.past });
29997
+ // Only the desktop width: on a phone most diagrams scroll, and the page shows that they do.
29998
+ if (!wide) wide = last.wide.map((x) => ({ ...x, at: w }));
29808
29999
  }
29809
30000
  const all = [...errors];
29810
30001
  for (const s of last.shown) if (!all.some((x) => x.statement === s.statement)) all.push(s);
29811
30002
  for (const x of last.exceptions) if (!all.some((y) => y.error === x)) all.push({ statement: "", error: x.slice(0, 300) });
29812
- return { rendered: ready, statements: last.statements, errors: all, overflow };
30003
+ return { rendered: ready, statements: last.statements, errors: all, overflow, wide: wide || [] };
29813
30004
  };
29814
30005
  </script></body></html>`;
29815
30006
  function connect(url2) {
@@ -29914,14 +30105,23 @@ function withMeasure(html) {
29914
30105
  const at2 = html.toLowerCase().lastIndexOf("</body>");
29915
30106
  return at2 < 0 ? html + MEASURE : html.slice(0, at2) + MEASURE + html.slice(at2);
29916
30107
  }
29917
- async function renderCheck(html) {
30108
+ function wideMessages(html, wide) {
30109
+ const sources = new Map(splitStatements(readArtifact(html).program).map((s) => [s.id, s.text]));
30110
+ return wide.map(({ statement, width, box, at: at2 }) => {
30111
+ const sideways = /\b(?:flowchart|graph)\s+(?:LR|RL)\b/.test(sources.get(statement) ?? "");
30112
+ const fix = sideways ? "use `flowchart TD` or split it" : "split it or shorten its labels";
30113
+ const name = statement || "(not one statement)";
30114
+ return { statement: name, message: `Mermaid ${name} is ${width}px wide at its smallest legible size, more than its ${box}px box at ${at2}px, so it scrolls sideways; ${fix}.` };
30115
+ });
30116
+ }
30117
+ async function renderCheck(html, dir) {
29918
30118
  const bin = findChrome();
29919
30119
  if (!bin) {
29920
30120
  const reason = process.env.CHROME_PATH ? `CHROME_PATH does not exist: ${process.env.CHROME_PATH}` : "no Chrome, Chromium or Edge found; install one or set CHROME_PATH";
29921
30121
  return { status: "unavailable", reason };
29922
30122
  }
29923
- const { html: page, notices } = exportHtml(html);
29924
- const profile = fs10.mkdtempSync(path9.join(os2.tmpdir(), "austere-render-"));
30123
+ const { html: page, notices } = exportHtml(html, dir);
30124
+ const profile = fs11.mkdtempSync(path10.join(os2.tmpdir(), "austere-render-"));
29925
30125
  const site = await serve(withMeasure(page));
29926
30126
  const { child, endpoint, exited } = launch(bin, profile);
29927
30127
  let cdp = null;
@@ -29945,7 +30145,7 @@ async function renderCheck(html) {
29945
30145
  if (r.exceptionDetails) throw new Error(`render report failed: ${r.exceptionDetails.text}`);
29946
30146
  const report = r.result.value;
29947
30147
  if (!report.rendered && !report.errors.length) report.errors.push({ statement: "", error: "the page did not finish rendering" });
29948
- return { status: "ok", statements: report.statements, errors: report.errors, overflow: report.overflow, notices };
30148
+ return { status: "ok", statements: report.statements, errors: report.errors, overflow: report.overflow, wide: wideMessages(html, report.wide), notices };
29949
30149
  })();
29950
30150
  const timeout = new Promise((_, reject) => {
29951
30151
  timer = setTimeout(() => reject(new Error(`rendering took longer than ${RENDER_TIMEOUT_MS / 1e3} s`)), RENDER_TIMEOUT_MS);
@@ -29968,7 +30168,7 @@ async function renderCheck(html) {
29968
30168
  await Promise.race([exited, new Promise((r) => killGrace = setTimeout(r, 3e3))]);
29969
30169
  clearTimeout(killGrace);
29970
30170
  try {
29971
- fs10.rmSync(profile, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
30171
+ fs11.rmSync(profile, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
29972
30172
  } catch {
29973
30173
  }
29974
30174
  }
@@ -30261,7 +30461,8 @@ NOTES
30261
30461
  The CDN script tag lets the file open on its own (needs network); review and export work
30262
30462
  offline. A browser the CLI did not open gets 403 until it signs in with \`${BIN2} login\`.
30263
30463
  Mermaid syntax is only checked when it renders. check warns when an Architecture diagram
30264
- will not fit the page. check --render needs Chrome, Chromium or Edge (or CHROME_PATH).
30464
+ will not fit the page; check --render, when a Mermaid diagram scrolls sideways in its box.
30465
+ check --render needs Chrome, Chromium or Edge (or CHROME_PATH).
30265
30466
 
30266
30467
  ENV
30267
30468
  AUSTERE_AXI_PORT (4388) AUSTERE_AXI_STATE_DIR (~/.austere-axi) AUSTERE_AXI_NO_OPEN=1
@@ -30427,7 +30628,7 @@ function needFile(file3, cmd) {
30427
30628
  return null;
30428
30629
  }
30429
30630
  const abs = canonicalFile(file3);
30430
- if (!fs15.existsSync(abs)) {
30631
+ if (!fs16.existsSync(abs)) {
30431
30632
  fail(`no such file: ${abs}`);
30432
30633
  return null;
30433
30634
  }
@@ -30444,7 +30645,7 @@ function readStdin() {
30444
30645
  async function replyText(flags) {
30445
30646
  if (typeof flags["agent-reply-file"] === "string") {
30446
30647
  const f = flags["agent-reply-file"];
30447
- return (f === "-" ? await readStdin() : fs15.readFileSync(f, "utf8")).trim();
30648
+ return (f === "-" ? await readStdin() : fs16.readFileSync(f, "utf8")).trim();
30448
30649
  }
30449
30650
  if (typeof flags["agent-reply"] === "string") return flags["agent-reply"].trim();
30450
30651
  return "";
@@ -30483,7 +30684,7 @@ async function cmdHome() {
30483
30684
  async function cmdOpen(file3, flags) {
30484
30685
  const abs = needFile(file3, "open");
30485
30686
  if (!abs || file3 === void 0) return;
30486
- const html = fs15.readFileSync(abs, "utf8");
30687
+ const html = fs16.readFileSync(abs, "utf8");
30487
30688
  const check2 = checkHtml(html, { file: quote(file3) });
30488
30689
  await ensureServer();
30489
30690
  const r = await request("POST", "/api/open", { file: abs, reopen: !!flags.reopen });
@@ -30522,7 +30723,7 @@ function quote(file3) {
30522
30723
  function describeItems(abs, items) {
30523
30724
  let lines = /* @__PURE__ */ new Map();
30524
30725
  try {
30525
- lines = statementLines(fs15.readFileSync(abs, "utf8"));
30726
+ lines = statementLines(fs16.readFileSync(abs, "utf8"));
30526
30727
  } catch {
30527
30728
  }
30528
30729
  const at2 = locator(lines);
@@ -30539,7 +30740,7 @@ function describeItems(abs, items) {
30539
30740
  }
30540
30741
  function thirdPartyOf(abs) {
30541
30742
  try {
30542
- return pagePacks(fs15.readFileSync(abs, "utf8")).thirdParty.map((p) => `${p.name}@${p.version}`);
30743
+ return pagePacks(fs16.readFileSync(abs, "utf8")).thirdParty.map((p) => `${p.name}@${p.version}`);
30543
30744
  } catch {
30544
30745
  return [];
30545
30746
  }
@@ -30565,7 +30766,7 @@ async function cmdPoll(file3, flags) {
30565
30766
  if (!reply && (flags["agent-reply"] !== void 0 || flags["agent-reply-file"] !== void 0)) {
30566
30767
  return fail("the agent reply is empty");
30567
30768
  }
30568
- process.stderr.write(`[${BIN2}] waiting for feedback on ${path15.basename(abs)} (interrupting is safe: feedback stays queued)
30769
+ process.stderr.write(`[${BIN2}] waiting for feedback on ${path16.basename(abs)} (interrupting is safe: feedback stays queued)
30569
30770
  `);
30570
30771
  const ticker = process.stderr.isTTY ? setInterval(() => process.stderr.write(`[${BIN2}] still waiting\u2026
30571
30772
  `), 6e4) : null;
@@ -30662,13 +30863,13 @@ function fixPacks(abs, html) {
30662
30863
  return { html, changes };
30663
30864
  }
30664
30865
  const out = setPacksMeta(html, next);
30665
- if (out !== html) fs15.writeFileSync(abs, out);
30866
+ if (out !== html) fs16.writeFileSync(abs, out);
30666
30867
  return { html: out, changes: changes.length ? changes : ["updated integrity hashes"] };
30667
30868
  }
30668
30869
  async function cmdCheck(file3, flags = {}) {
30669
30870
  const abs = needFile(file3, "check");
30670
30871
  if (!abs || file3 === void 0) return;
30671
- let html = fs15.readFileSync(abs, "utf8");
30872
+ let html = fs16.readFileSync(abs, "utf8");
30672
30873
  let fixed = [];
30673
30874
  if (flags.fix) ({ html, changes: fixed } = fixPacks(abs, html));
30674
30875
  const result = checkHtml(html, { file: quote(file3) });
@@ -30680,7 +30881,7 @@ async function cmdCheck(file3, flags = {}) {
30680
30881
  if (flags.render) {
30681
30882
  if (!result.ok) out.render = "skipped: fix the errors above first";
30682
30883
  else {
30683
- const render = await renderOut(html);
30884
+ const render = await renderOut(html, path16.dirname(abs));
30684
30885
  out.render = render;
30685
30886
  if (render.errors) out.status = "errors";
30686
30887
  else if (render.status === "unavailable") out.status = "render_unavailable";
@@ -30691,14 +30892,16 @@ async function cmdCheck(file3, flags = {}) {
30691
30892
  print(out);
30692
30893
  if (out.status !== "ok") process.exitCode = 1;
30693
30894
  }
30694
- async function renderOut(html) {
30695
- const r = await renderCheck(html);
30895
+ async function renderOut(html, dir) {
30896
+ const r = await renderCheck(html, dir);
30696
30897
  if (r.status === "unavailable") return { status: "unavailable", reason: r.reason };
30697
- const out = { status: r.errors.length ? "errors" : "clean", widths: "1280, 390", statements: r.statements };
30898
+ const status = r.errors.length ? "errors" : r.overflow.length || r.wide.length ? "warnings" : "clean";
30899
+ const out = { status, widths: "1280, 390", statements: r.statements };
30698
30900
  if (r.errors.length) out.errors = r.errors.map((e) => ({ statement: e.statement || "(page)", message: e.error }));
30699
30901
  if (r.overflow.length) {
30700
30902
  out.scrolls_sideways = r.overflow.map((o) => ({ width: o.width, statements: o.statements.length ? o.statements.join(", ") : "(not one statement)" }));
30701
30903
  }
30904
+ if (r.wide.length) out.warnings = r.wide;
30702
30905
  if (r.notices.length) out.notices = r.notices;
30703
30906
  return out;
30704
30907
  }
@@ -30708,7 +30911,7 @@ async function cmdPatch(file3) {
30708
30911
  if (process.stdin.isTTY) return usage(`pipe the changed statements on stdin: ${BIN2} patch ${quote(file3)} <<'EOF' ... EOF`, "patch");
30709
30912
  const patch = await readStdin();
30710
30913
  if (!patch.trim()) return fail("stdin was empty");
30711
- const html = fs15.readFileSync(abs, "utf8");
30914
+ const html = fs16.readFileSync(abs, "utf8");
30712
30915
  const art = readArtifact(html);
30713
30916
  if (art.program == null) return fail('no <script type="text/openui"> block to patch');
30714
30917
  const merged = mergeStatements(art.program.trim(), patch.trim(), "root");
@@ -30716,7 +30919,7 @@ async function cmdPatch(file3) {
30716
30919
  const patched = new Set(splitStatements(patch).filter((s) => !/=\s*null\s*;?$/.test(s.text)).map((s) => s.id));
30717
30920
  const dropped = [...patched].filter((id) => !kept.has(id));
30718
30921
  const next = replaceProgram(html, merged);
30719
- fs15.writeFileSync(abs, next);
30922
+ fs16.writeFileSync(abs, next);
30720
30923
  const result = checkHtml(next, { file: quote(file3) });
30721
30924
  await registerWithHub(abs, result.title, sessionId(abs));
30722
30925
  const out = { status: "patched", file: abs };
@@ -30731,7 +30934,7 @@ function packSpec(p) {
30731
30934
  return lines.join("\n") + "\n";
30732
30935
  }
30733
30936
  function cmdSpec(groups) {
30734
- let text = fs15.readFileSync(assetPath("prompt.md"), "utf8");
30937
+ let text = fs16.readFileSync(assetPath("prompt.md"), "utf8");
30735
30938
  const packs = installedPacks();
30736
30939
  const packArgs = groups.filter((g) => packs.some((p) => p.name === g));
30737
30940
  if (packArgs.length) {
@@ -30765,9 +30968,9 @@ ${trimmed.join("\n")}
30765
30968
  function cmdExport(file3, flags) {
30766
30969
  const abs = needFile(file3, "export");
30767
30970
  if (!abs) return;
30768
- const out = typeof flags.out === "string" ? path15.resolve(flags.out) : abs.replace(/\.html?$/i, "") + ".export.html";
30769
- const { html, notices } = exportHtml(fs15.readFileSync(abs, "utf8"));
30770
- fs15.writeFileSync(out, html);
30971
+ const out = typeof flags.out === "string" ? path16.resolve(flags.out) : abs.replace(/\.html?$/i, "") + ".export.html";
30972
+ const { html, notices } = exportHtml(fs16.readFileSync(abs, "utf8"), path16.dirname(abs));
30973
+ fs16.writeFileSync(out, html);
30771
30974
  print({ status: "exported", file: out, bytes: html.length, notices: notices.length ? notices : void 0 });
30772
30975
  }
30773
30976
  function printResult(out) {
@@ -30827,7 +31030,7 @@ async function cmdComponents(sub, args, flags) {
30827
31030
  pack: r.name,
30828
31031
  dir: r.dir,
30829
31032
  files: r.files,
30830
- next: `cd ${quote(path15.relative(process.cwd(), r.dir) || ".")} && npm install && npm run build, then \`${BIN2} components add .\``
31033
+ next: `cd ${quote(path16.relative(process.cwd(), r.dir) || ".")} && npm install && npm run build, then \`${BIN2} components add .\``
30831
31034
  });
30832
31035
  } catch (err) {
30833
31036
  return fail(messageOf(err));
@@ -30938,7 +31141,7 @@ async function cmdHub(sub, args, flags) {
30938
31141
  server: h ? `running on ${port()}${h.persist ? " (persist)" : ""}` : "not running"
30939
31142
  };
30940
31143
  if (hubEnabled() && h) out.url = url2;
30941
- if (fs15.existsSync(hubDbFile())) {
31144
+ if (fs16.existsSync(hubDbFile())) {
30942
31145
  try {
30943
31146
  const hub = await Hub.open();
30944
31147
  try {
@@ -30954,8 +31157,8 @@ async function cmdHub(sub, args, flags) {
30954
31157
  return print(out);
30955
31158
  }
30956
31159
  case "list": {
30957
- if (!fs15.existsSync(hubDbFile())) return print({ artifacts: "none", next: `\`${BIN2} hub on\` starts indexing.` });
30958
- const project = typeof flags.project === "string" ? path15.resolve(flags.project) : "";
31160
+ if (!fs16.existsSync(hubDbFile())) return print({ artifacts: "none", next: `\`${BIN2} hub on\` starts indexing.` });
31161
+ const project = typeof flags.project === "string" ? path16.resolve(flags.project) : "";
30959
31162
  let hub;
30960
31163
  try {
30961
31164
  hub = await Hub.open();
@@ -30980,8 +31183,8 @@ async function cmdHub(sub, args, flags) {
30980
31183
  });
30981
31184
  }
30982
31185
  case "notes": {
30983
- if (!fs15.existsSync(hubDbFile())) return print({ notes: "none", next: `\`${BIN2} hub on\` starts indexing.` });
30984
- const project = typeof flags.project === "string" ? path15.resolve(flags.project) : "";
31186
+ if (!fs16.existsSync(hubDbFile())) return print({ notes: "none", next: `\`${BIN2} hub on\` starts indexing.` });
31187
+ const project = typeof flags.project === "string" ? path16.resolve(flags.project) : "";
30985
31188
  let hub;
30986
31189
  try {
30987
31190
  hub = await Hub.open();
@@ -31001,7 +31204,7 @@ async function cmdHub(sub, args, flags) {
31001
31204
  text: r.text || void 0,
31002
31205
  target: r.target || void 0,
31003
31206
  status: r.status === "addressed" ? "answered" : r.status === "held" ? "held" : "unanswered",
31004
- page: r.title || path15.basename(r.file),
31207
+ page: r.title || path16.basename(r.file),
31005
31208
  file: r.file,
31006
31209
  sent: shortDate(r.at)
31007
31210
  })),
@@ -31125,7 +31328,7 @@ async function main() {
31125
31328
  let name = null;
31126
31329
  if (cmd !== void 0) {
31127
31330
  if (Object.hasOwn(COMMANDS, cmd)) name = cmd;
31128
- else if (/\.html?$/i.test(cmd) || fs15.existsSync(cmd)) name = "open";
31331
+ else if (/\.html?$/i.test(cmd) || fs16.existsSync(cmd)) name = "open";
31129
31332
  else if (flags.help) return process.stdout.write(HELP);
31130
31333
  else return usage(`unknown command: ${cmd}. One of: ${Object.keys(COMMANDS).join(" ")}, or a page file`);
31131
31334
  }