@wix/himalaya-cli 0.1059.0 → 0.1061.0

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
@@ -2699,7 +2699,7 @@ function isAttestationV2(value) {
2699
2699
  const a = value;
2700
2700
  const coverage = a.coverage;
2701
2701
  const report2 = a.report;
2702
- return a.attestation === 2 && typeof a.runId === "string" && ["himi-test", "himi-render", "himi-shots"].includes(a.tool) && [a.app, a.contentDir, a.gitDiffHash, a.gitUntrackedHash, a.releaseId, a.releaseHash, a.selfHash].every((item) => typeof item === "string" && item.length > 0) && stringRecord(a.descriptorHashes) && stringRecord(a.screenshots) && !!coverage && [coverage.screens, coverage.actionsDispatched, coverage.uncoveredFns, coverage.gaps].every((count) => typeof count === "number" && Number.isFinite(count) && count >= 0) && typeof report2?.ok === "boolean" && !!report2.counts && [report2.counts.errors, report2.counts.warnings, report2.counts.gaps].every((count) => typeof count === "number" && Number.isFinite(count) && count >= 0) && (a.prev === null || typeof a.prev === "string");
2702
+ return a.attestation === 2 && typeof a.runId === "string" && ["himi-test", "himi-render", "himi-shots"].includes(a.tool) && [a.app, a.contentDir, a.gitDiffHash, a.gitUntrackedHash, a.releaseId, a.releaseHash, a.selfHash].every((item) => typeof item === "string" && item.length > 0) && stringRecord(a.descriptorHashes) && stringRecord(a.screenshots) && !!coverage && [coverage.screens, coverage.actionsDispatched, coverage.uncoveredFns, coverage.gaps].every((count2) => typeof count2 === "number" && Number.isFinite(count2) && count2 >= 0) && typeof report2?.ok === "boolean" && !!report2.counts && [report2.counts.errors, report2.counts.warnings, report2.counts.gaps].every((count2) => typeof count2 === "number" && Number.isFinite(count2) && count2 >= 0) && (a.prev === null || typeof a.prev === "string");
2703
2703
  }
2704
2704
  function readLatestAttestation(contentDir2) {
2705
2705
  try {
@@ -6657,6 +6657,7 @@ function walk(node, registry, path2, out) {
6657
6657
  var SYSTEM_FONT_FAMILY, TONAL_ELEVATION_ALPHA;
6658
6658
  var init_tokens = __esm({
6659
6659
  "../../core/ts/src/tokens.ts"() {
6660
+ "use strict";
6660
6661
  SYSTEM_FONT_FAMILY = "$system";
6661
6662
  TONAL_ELEVATION_ALPHA = [0, 0.05, 0.08, 0.11, 0.12, 0.14];
6662
6663
  }
@@ -12326,8 +12327,8 @@ function canonicalHex(value) {
12326
12327
  function tokenColorProblems(colors) {
12327
12328
  const problems = [];
12328
12329
  for (const [name, value] of Object.entries(colors ?? {})) {
12329
- const leaves = typeof value === "object" && value !== null && !Array.isArray(value) ? Object.entries(value).map(([variant, raw]) => [`${name}.${variant}`, raw]) : [[name, value]];
12330
- for (const [path2, raw] of leaves) {
12330
+ const leaves2 = typeof value === "object" && value !== null && !Array.isArray(value) ? Object.entries(value).map(([variant, raw]) => [`${name}.${variant}`, raw]) : [[name, value]];
12331
+ for (const [path2, raw] of leaves2) {
12331
12332
  if (typeof raw !== "string") {
12332
12333
  problems.push({ path: path2, value: JSON.stringify(raw) ?? String(raw) });
12333
12334
  continue;
@@ -15099,6 +15100,20 @@ async function tabRootScreens(dir) {
15099
15100
  (nav?.tabBar?.items ?? []).map((i) => i?.screenId).filter((s) => typeof s === "string" && !!s)
15100
15101
  );
15101
15102
  }
15103
+ async function navEntryScreens(dir, homeScreenId) {
15104
+ const nav = (await loadContentConfigQuietly(dir))?.navigation;
15105
+ const out = /* @__PURE__ */ new Set();
15106
+ if (homeScreenId) out.add(homeScreenId);
15107
+ const walk2 = (v, depth) => {
15108
+ if (depth > 6 || typeof v !== "object" || v === null) return;
15109
+ for (const [k, x] of Object.entries(v)) {
15110
+ if ((k === "screenId" || k === "contentScreenId") && typeof x === "string" && x) out.add(x);
15111
+ else walk2(x, depth + 1);
15112
+ }
15113
+ };
15114
+ for (const chrome of ["tabBar", "drawer", "sidebar", "topBarDefault"]) walk2(nav?.[chrome], 0);
15115
+ return out;
15116
+ }
15102
15117
  async function localDbEntities(dir) {
15103
15118
  const schema = (await loadContentConfigQuietly(dir))?.dataSchema;
15104
15119
  const names = (schema?.entities ?? []).map((e) => e?.name).filter((n) => typeof n === "string" && n.length > 0);
@@ -16834,9 +16849,9 @@ function hostFromProject(project) {
16834
16849
  const blocks = project.split(/(?=^ [A-Za-z0-9_]+:\s*$)/m);
16835
16850
  const host = blocks.find((block) => /^ type: application\s*$/m.test(block));
16836
16851
  if (!host) throw new Error("Live Activity generation requires an iOS application target");
16837
- const scalar3 = (key2, indent) => new RegExp(`^ {${indent}}${key2}:\\s*(.+)$`, "m").exec(host)?.[1]?.trim().replace(/^['"]|['"]$/g, "");
16852
+ const scalar4 = (key2, indent) => new RegExp(`^ {${indent}}${key2}:\\s*(.+)$`, "m").exec(host)?.[1]?.trim().replace(/^['"]|['"]$/g, "");
16838
16853
  const name = /^ ([A-Za-z0-9_]+):/m.exec(host)?.[1];
16839
- const bundleId = scalar3("PRODUCT_BUNDLE_IDENTIFIER", 8) ?? scalar3("bundleId", 4);
16854
+ const bundleId = scalar4("PRODUCT_BUNDLE_IDENTIFIER", 8) ?? scalar4("bundleId", 4);
16840
16855
  const scheme = /CFBundleURLSchemes:\s*\n\s*-\s*(.+)/.exec(host)?.[1]?.trim().replace(/^['"]|['"]$/g, "");
16841
16856
  if (!name || !bundleId || !scheme) throw new Error("Live Activity generation requires host name, bundleId and URL scheme");
16842
16857
  const configs = /^ configFiles:\s*\n((?: .+\n)+)/m.exec(host)?.[1]?.trimEnd().split("\n").map((line) => line.trim()) ?? [];
@@ -16845,9 +16860,9 @@ function hostFromProject(project) {
16845
16860
  bundleId,
16846
16861
  scheme,
16847
16862
  configs,
16848
- displayName: scalar3("CFBundleDisplayName", 8) ?? name,
16849
- version: scalar3("CFBundleShortVersionString", 8) ?? scalar3("MARKETING_VERSION", 8) ?? "1.0",
16850
- build: scalar3("CFBundleVersion", 8) ?? scalar3("CURRENT_PROJECT_VERSION", 8) ?? "1"
16863
+ displayName: scalar4("CFBundleDisplayName", 8) ?? name,
16864
+ version: scalar4("CFBundleShortVersionString", 8) ?? scalar4("MARKETING_VERSION", 8) ?? "1.0",
16865
+ build: scalar4("CFBundleVersion", 8) ?? scalar4("CURRENT_PROJECT_VERSION", 8) ?? "1"
16851
16866
  };
16852
16867
  }
16853
16868
  function liveActivityPlan(project, enabled, deferredActions = false) {
@@ -18941,8 +18956,8 @@ var init_store2 = __esm({
18941
18956
  `);
18942
18957
  }
18943
18958
  seedNotesIfEmpty() {
18944
- const count = this.db.prepare("SELECT COUNT(*) as c FROM notes").get();
18945
- if (count.c > 0) return;
18959
+ const count2 = this.db.prepare("SELECT COUNT(*) as c FROM notes").get();
18960
+ if (count2.c > 0) return;
18946
18961
  const seedNotes = [
18947
18962
  {
18948
18963
  id: "n_001",
@@ -18982,8 +18997,8 @@ var init_store2 = __esm({
18982
18997
  }
18983
18998
  }
18984
18999
  seedProductsIfEmpty() {
18985
- const count = this.db.prepare("SELECT COUNT(*) as c FROM products").get();
18986
- if (count.c > 0) return;
19000
+ const count2 = this.db.prepare("SELECT COUNT(*) as c FROM products").get();
19001
+ if (count2.c > 0) return;
18987
19002
  const now = Math.floor(Date.now() / 1e3);
18988
19003
  const seed2 = [
18989
19004
  { id: "p_001", sku: "MNT-CAP-01", title: "Summit cap", description: "Wool-blend beanie. Charcoal. One size, packs flat.", price_cents: 2900, currency: "USD", category: "apparel", inventory: 28, status: "active" },
@@ -20560,6 +20575,23 @@ function forEachChildComponent(node, cb) {
20560
20575
  };
20561
20576
  visit(node.body.props);
20562
20577
  }
20578
+ function visitComponents(root, cb) {
20579
+ const visit = (value) => {
20580
+ if (isNode(value)) {
20581
+ cb(value);
20582
+ visit(value.body.props);
20583
+ return;
20584
+ }
20585
+ if (Array.isArray(value)) {
20586
+ for (const entry of value) visit(entry);
20587
+ return;
20588
+ }
20589
+ if (typeof value === "object" && value !== null) {
20590
+ for (const entry of Object.values(value)) visit(entry);
20591
+ }
20592
+ };
20593
+ visit(root);
20594
+ }
20563
20595
  function assignStableIds(descriptor) {
20564
20596
  const used = /* @__PURE__ */ new Set();
20565
20597
  const uniquify = (base) => {
@@ -26171,8 +26203,8 @@ function componentShape(value) {
26171
26203
  if (!isRecord(value) || typeof value.id !== "string" || !isRecord(value.body)) return false;
26172
26204
  return typeof value.body.package === "string" && typeof value.body.type === "string" && isRecord(value.body.props);
26173
26205
  }
26174
- function rotateRight(value, count) {
26175
- return value >>> count | value << 32 - count;
26206
+ function rotateRight(value, count2) {
26207
+ return value >>> count2 | value << 32 - count2;
26176
26208
  }
26177
26209
  function workerSubtreeSha256(raw) {
26178
26210
  const input = new TextEncoder().encode(raw);
@@ -27337,14 +27369,14 @@ function dataTableCells(row, columns) {
27337
27369
  if (Array.isArray(o.cells)) return columns.map((_, i) => o.cells[i] == null ? "" : String(o.cells[i]));
27338
27370
  return columns.map((c) => c.key && o[c.key] != null ? String(o[c.key]) : "");
27339
27371
  }
27340
- function resampleWaveform(samples, count) {
27372
+ function resampleWaveform(samples, count2) {
27341
27373
  const clean2 = samples.map((n) => Number.isFinite(n) ? Math.min(Math.max(n, 0), 1) : 0);
27342
- if (count <= 0 || clean2.length === 0) return [];
27343
- if (clean2.length <= count) return clean2;
27374
+ if (count2 <= 0 || clean2.length === 0) return [];
27375
+ if (clean2.length <= count2) return clean2;
27344
27376
  const out = [];
27345
- for (let i = 0; i < count; i++) {
27346
- const lo = Math.floor(i * clean2.length / count);
27347
- const hi = Math.min(Math.max(lo + 1, Math.floor((i + 1) * clean2.length / count)), clean2.length);
27377
+ for (let i = 0; i < count2; i++) {
27378
+ const lo = Math.floor(i * clean2.length / count2);
27379
+ const hi = Math.min(Math.max(lo + 1, Math.floor((i + 1) * clean2.length / count2)), clean2.length);
27348
27380
  let sum = 0;
27349
27381
  for (let j = lo; j < hi; j++) sum += clean2[j];
27350
27382
  out.push(sum / (hi - lo));
@@ -28541,10 +28573,10 @@ function renderInner(node, ctx) {
28541
28573
  case "Agenda":
28542
28574
  return /* @__PURE__ */ jsx6(AgendaNode, { props, ctx, wrap });
28543
28575
  case "PageControl": {
28544
- const count = props.count ?? 0;
28576
+ const count2 = props.count ?? 0;
28545
28577
  const cur = props.currentIndex ?? 0;
28546
28578
  const accent = C("accentPrimary", "#116DFF");
28547
- return /* @__PURE__ */ jsx6("div", { style: { display: "inline-flex", alignItems: "center", gap: 8, ...wrap }, children: Array.from({ length: count }).map((_, i) => /* @__PURE__ */ jsx6("span", { style: { width: i === cur ? 22 : 9, height: 9, borderRadius: 999, background: i === cur ? accent : C("textTertiary", "#aab2bd"), transition: "width .2s" } }, i)) });
28579
+ return /* @__PURE__ */ jsx6("div", { style: { display: "inline-flex", alignItems: "center", gap: 8, ...wrap }, children: Array.from({ length: count2 }).map((_, i) => /* @__PURE__ */ jsx6("span", { style: { width: i === cur ? 22 : 9, height: 9, borderRadius: 999, background: i === cur ? accent : C("textTertiary", "#aab2bd"), transition: "width .2s" } }, i)) });
28548
28580
  }
28549
28581
  // ───────────── pickers & inputs (RFC 0033) ─────────────
28550
28582
  case "WheelPicker":
@@ -28649,9 +28681,9 @@ function renderInner(node, ctx) {
28649
28681
  case "WorkerSubtree":
28650
28682
  return /* @__PURE__ */ jsx6(WorkerSubtreeNode, { props, ctx, wrap, nodeId: node.id });
28651
28683
  case "Skeleton": {
28652
- const count = typeof props.count === "number" ? props.count : 1;
28653
- if (count <= 1) return /* @__PURE__ */ jsx6("div", { style: { width: props.width ?? "100%", height: props.height ?? 14, borderRadius: props.cornerRadius ?? 6, background: C("surfaceTertiary", "#f0f0f3"), flex: "none", ...wrap } });
28654
- return /* @__PURE__ */ jsx6("div", { style: { display: "flex", flexDirection: "column", gap: 10, width: "100%", ...wrap }, children: Array.from({ length: count }).map((_, i) => /* @__PURE__ */ jsx6("div", { style: { height: props.height ?? 22, width: `${100 - i * 8}%`, borderRadius: props.cornerRadius ?? 8, background: C("surfaceTertiary", "#f0f0f3") } }, i)) });
28684
+ const count2 = typeof props.count === "number" ? props.count : 1;
28685
+ if (count2 <= 1) return /* @__PURE__ */ jsx6("div", { style: { width: props.width ?? "100%", height: props.height ?? 14, borderRadius: props.cornerRadius ?? 6, background: C("surfaceTertiary", "#f0f0f3"), flex: "none", ...wrap } });
28686
+ return /* @__PURE__ */ jsx6("div", { style: { display: "flex", flexDirection: "column", gap: 10, width: "100%", ...wrap }, children: Array.from({ length: count2 }).map((_, i) => /* @__PURE__ */ jsx6("div", { style: { height: props.height ?? 22, width: `${100 - i * 8}%`, borderRadius: props.cornerRadius ?? 8, background: C("surfaceTertiary", "#f0f0f3") } }, i)) });
28655
28687
  }
28656
28688
  // ───────────── search & filter ─────────────
28657
28689
  case "SearchField":
@@ -30879,8 +30911,8 @@ var init_renderNode = __esm({
30879
30911
  const overflow = props.overflowCount || Math.max(0, avatars.length - maxVisible);
30880
30912
  const shift = Math.round(size * (1 - (props.overlapRatio ?? 0.67)));
30881
30913
  const caption = useBoundString(props.caption, ctx).trim();
30882
- const count = shown2.length + overflow;
30883
- const countText = count > 0 ? `${count} ${count === 1 ? "person" : "people"}` : "";
30914
+ const count2 = shown2.length + overflow;
30915
+ const countText = count2 > 0 ? `${count2} ${count2 === 1 ? "person" : "people"}` : "";
30884
30916
  const groupName = [caption, countText].filter(Boolean).join(", ");
30885
30917
  return /* @__PURE__ */ jsxs3("div", { role: "group", "aria-label": groupName || void 0, "data-a11y-value": countText || void 0, ...{ [SELF_RENDERED_LEAF]: true }, style: { width: "100%", ...wrap }, children: [
30886
30918
  /* @__PURE__ */ jsxs3("div", { style: { display: "flex" }, children: [
@@ -31195,7 +31227,7 @@ var init_renderNode = __esm({
31195
31227
  const W = 100, H = 100;
31196
31228
  const slot = bars.length > 0 ? W / bars.length : W;
31197
31229
  const barW = Math.max(0.6, slot * 0.58);
31198
- const cut = progress * W;
31230
+ const cut2 = progress * W;
31199
31231
  const rates = Array.isArray(props.rates) ? props.rates.map(Number).filter((n) => n > 0) : [];
31200
31232
  const [pickedRate, setPickedRate] = React5.useState(null);
31201
31233
  React5.useEffect(() => {
@@ -31306,7 +31338,7 @@ var init_renderNode = __esm({
31306
31338
  height: bh,
31307
31339
  rx: barW / 2,
31308
31340
  fill: accent,
31309
- opacity: cx <= cut ? isPlayable ? 1 : 0.45 : isPlayable ? 0.28 : 0.16
31341
+ opacity: cx <= cut2 ? isPlayable ? 1 : 0.45 : isPlayable ? 0.28 : 0.16
31310
31342
  },
31311
31343
  i
31312
31344
  );
@@ -36222,7 +36254,7 @@ async function handleWebWix(req, res, state) {
36222
36254
  const session = webWixSession(req, res, state);
36223
36255
  const auth = resolveWebWixAuth(state.app);
36224
36256
  const path2 = new URL(req.url ?? "", "http://localhost").pathname;
36225
- const readJson5 = async () => {
36257
+ const readJson6 = async () => {
36226
36258
  const chunks = [];
36227
36259
  for await (const c of req) chunks.push(c);
36228
36260
  const t = Buffer.concat(chunks).toString("utf8");
@@ -36239,7 +36271,7 @@ async function handleWebWix(req, res, state) {
36239
36271
  res.end(JSON.stringify(obj3));
36240
36272
  };
36241
36273
  if (path2 === "/v1/__webwix/login" && req.method === "POST") {
36242
- const b = await readJson5();
36274
+ const b = await readJson6();
36243
36275
  try {
36244
36276
  const { memberToken, visitorToken } = await memberDirectLogin(
36245
36277
  auth.clientId,
@@ -36256,7 +36288,7 @@ async function handleWebWix(req, res, state) {
36256
36288
  }
36257
36289
  }
36258
36290
  if (path2 === "/v1/__webwix/call" && req.method === "POST") {
36259
- const b = await readJson5();
36291
+ const b = await readJson6();
36260
36292
  const ownerRung = b.ownerMsid !== void 0 || b.checkOwnerSession === true;
36261
36293
  const msid = String(b.ownerMsid ?? "");
36262
36294
  const cookie = req.headers.cookie ?? "";
@@ -36404,7 +36436,7 @@ async function handleWebWix(req, res, state) {
36404
36436
  });
36405
36437
  }
36406
36438
  if (path2 === "/v1/__webwix/register" && req.method === "POST") {
36407
- const b = await readJson5();
36439
+ const b = await readJson6();
36408
36440
  try {
36409
36441
  const { memberToken, visitorToken } = await memberDirectRegister(
36410
36442
  auth.clientId,
@@ -39403,10 +39435,10 @@ function effectSince(w, mark, action, componentId) {
39403
39435
  }
39404
39436
  function resolveSiteRows(itemsPath, valueOf) {
39405
39437
  if (!itemsPath || itemsPath === UNTRACKED_ITEMS) return void 0;
39406
- const cut = itemsPath.indexOf(NESTED_ITEMS_SEP);
39407
- if (cut < 0) return valueOf(itemsPath);
39408
- const outer = valueOf(itemsPath.slice(0, cut));
39409
- const field = itemsPath.slice(cut + NESTED_ITEMS_SEP.length);
39438
+ const cut2 = itemsPath.indexOf(NESTED_ITEMS_SEP);
39439
+ if (cut2 < 0) return valueOf(itemsPath);
39440
+ const outer = valueOf(itemsPath.slice(0, cut2));
39441
+ const field = itemsPath.slice(cut2 + NESTED_ITEMS_SEP.length);
39410
39442
  const first = Array.isArray(outer) ? outer[0] : void 0;
39411
39443
  return first && typeof first === "object" ? first[field] : void 0;
39412
39444
  }
@@ -39561,6 +39593,7 @@ async function crawlScreenInner(opts) {
39561
39593
  const findings = [];
39562
39594
  const actionsSkipped = [];
39563
39595
  const navEdges = [];
39596
+ const navUnfollowed = /* @__PURE__ */ new Set();
39564
39597
  const unobservedActions = [];
39565
39598
  const effects = [];
39566
39599
  let observations;
@@ -39583,7 +39616,10 @@ async function crawlScreenInner(opts) {
39583
39616
  return pathValue(row0, path2);
39584
39617
  };
39585
39618
  const target = resolveItem(merged.screenId);
39586
- if (typeof target !== "string" || !target || target.includes("$")) continue;
39619
+ if (typeof target !== "string" || !target || target.includes("$")) {
39620
+ navUnfollowed.add(`tap:${site.componentId ?? JSON.stringify(site.params ?? {})}`);
39621
+ continue;
39622
+ }
39587
39623
  const params = {};
39588
39624
  for (const [k, v] of Object.entries(merged)) {
39589
39625
  if (k === "screenId" || k === "presentation") continue;
@@ -39596,7 +39632,10 @@ async function crawlScreenInner(opts) {
39596
39632
  };
39597
39633
  const drainWorkerEdges = (w, via) => {
39598
39634
  for (const d of w.navDirectives.splice(0)) {
39599
- if (!d.screenId || d.screenId.includes("$")) continue;
39635
+ if (!d.screenId || d.screenId.includes("$")) {
39636
+ navUnfollowed.add(`${via}:${d.screenId ?? ""}`);
39637
+ continue;
39638
+ }
39600
39639
  const params = {};
39601
39640
  for (const [k, v] of Object.entries(d.args)) if (!v.includes("$")) params[k] = v;
39602
39641
  navEdges.push({ target: d.screenId, params, via });
@@ -39977,7 +40016,7 @@ async function crawlScreenInner(opts) {
39977
40016
  const edges = dedupeEdges(navEdges);
39978
40017
  if (linkableScreenIds?.size && !opts.capabilitySuccess) {
39979
40018
  const linkable = linkableScreenIds;
39980
- const resolvesToLinkable = (target) => {
40019
+ const resolvesToLinkable2 = (target) => {
39981
40020
  if (linkable.has(target)) return true;
39982
40021
  for (const base of linkable) {
39983
40022
  if (target.length > base.length + 1 && target.startsWith(base)) {
@@ -39991,7 +40030,7 @@ async function crawlScreenInner(opts) {
39991
40030
  for (const e of edges) {
39992
40031
  if (e.target === screenId) continue;
39993
40032
  if (reported.has(e.target)) continue;
39994
- if (knownScreenIds.has(e.target) || resolvesToLinkable(e.target)) continue;
40033
+ if (knownScreenIds.has(e.target) || resolvesToLinkable2(e.target)) continue;
39995
40034
  reported.add(e.target);
39996
40035
  findings.push({
39997
40036
  screen: screenId,
@@ -40012,6 +40051,7 @@ async function crawlScreenInner(opts) {
40012
40051
  actionsExercised: exercised,
40013
40052
  actionsSkipped,
40014
40053
  navEdges: edges,
40054
+ ...navUnfollowed.size ? { navUnfollowed: navUnfollowed.size } : {},
40015
40055
  ...effects.length ? { effects } : {},
40016
40056
  ...observations ? { observations } : {},
40017
40057
  ...unobservedActions.length ? { unobservedActions } : {},
@@ -42067,6 +42107,7 @@ async function buildFromLoaded(preloaded, dest, opts = {}) {
42067
42107
  ...collect2?.lint ? { lint, workers } : {},
42068
42108
  ...collect2?.render ? { renders } : {},
42069
42109
  ...collect2?.descriptors ? { descriptors: baked, linkableScreenIds: [...linkable] } : {},
42110
+ ...collect2?.descriptors && typeof preloaded.meta.homeScreenId === "string" && preloaded.meta.homeScreenId ? { homeScreenId: preloaded.meta.homeScreenId } : {},
42070
42111
  ...renderTokens !== void 0 ? { tokens: renderTokens } : {},
42071
42112
  ...preloaded.meta.surfaces ? { surfaceNav: preloaded.meta.surfaces } : {},
42072
42113
  auth: (() => {
@@ -42332,6 +42373,27 @@ h2{font-size:12px;font-weight:600;color:var(--muted);text-transform:uppercase;le
42332
42373
  .lant{font-weight:600}
42333
42374
  .lanu{font-size:12px;word-break:break-all}
42334
42375
  .lanh{color:var(--muted);font-size:12px;margin-top:4px}
42376
+ .mbox{overflow-x:auto}
42377
+ .msum{color:var(--muted);font-size:12px;margin-bottom:8px}
42378
+ .mnode rect{fill:var(--card);stroke:var(--line);stroke-width:1.2}
42379
+ .mnode text{font-size:11px;fill:var(--ink)}
42380
+ .mnode.ok rect{fill:var(--okbg);stroke:var(--ok)}
42381
+ .mnode.failed rect{fill:var(--badbg);stroke:var(--bad)}
42382
+ .mnode.pending rect{stroke-dasharray:4 3}
42383
+ .mnode.skipped text{fill:var(--muted)}
42384
+ .mnode.entry rect{stroke-width:2.4}
42385
+ .mnode.unreached rect{fill:var(--warnbg);stroke:var(--warn);stroke-dasharray:4 3}
42386
+ .medge{fill:none;stroke:var(--faint);stroke-width:1.2}
42387
+ .mbroken{font-size:11px;fill:var(--bad)}
42388
+ .mrow{font-size:11px;fill:var(--warn)}
42389
+ .mfollow{font-size:9px;fill:var(--warn)}
42390
+ .mnode.unfollowed rect{stroke:var(--warn)}
42391
+ .changes{margin-top:12px;padding:10px 12px;border:1px solid var(--line);border-radius:12px;font-size:12px}
42392
+ .changes .cht{font-weight:600;margin-bottom:4px}
42393
+ .changes ul{margin:0;padding-left:16px}
42394
+ .changes li{margin:2px 0;word-break:break-word}
42395
+ .changes .sw{display:inline-block;width:10px;height:10px;border-radius:3px;margin-left:6px;vertical-align:middle;border:1px solid var(--line)}
42396
+ .changes .chm{color:var(--muted);margin-top:2px}
42335
42397
  .film{display:flex;gap:6px;overflow-x:auto;margin-top:12px;padding-bottom:4px}.film img{width:48px;aspect-ratio:390/844;object-fit:cover;object-position:top;border-radius:8px;border:1px solid var(--line);cursor:zoom-in}
42336
42398
  .chips{display:flex;flex-wrap:wrap;gap:8px;margin-bottom:16px}.chip{font-size:12px;padding:5px 10px;border-radius:999px;background:var(--card);border:1px solid var(--line);color:var(--muted)}
42337
42399
  .chip.ok{background:var(--okbg);color:var(--ok);border-color:transparent}.chip.warn{background:var(--warnbg);color:var(--warn);border-color:transparent}.chip.bad{background:var(--badbg);color:var(--bad);border-color:transparent}
@@ -42389,6 +42451,7 @@ h2{font-size:12px;font-weight:600;color:var(--muted);text-transform:uppercase;le
42389
42451
  <div class="card section"><h2>Screens</h2><div class="wall" id="wall"></div></div>
42390
42452
  <div class="card section" id="brandcard"><h2>Brand</h2><div class="brand" id="brand"></div></div>
42391
42453
  <div class="card section" id="navcard"><h2>Navigation the test crawl followed</h2><div class="nav" id="nav"></div></div>
42454
+ <div class="card section" id="appmap" hidden><h2>App map</h2><div class="msum" id="msum"></div><div class="mbox" id="mapbox"></div></div>
42392
42455
  <div class="card section"><h2>Postcards</h2><ul class="log" id="log"></ul></div>
42393
42456
  </div></div></div>
42394
42457
  <div id="lb"><img alt=""></div>
@@ -42400,6 +42463,48 @@ h2{font-size:12px;font-weight:600;color:var(--muted);text-transform:uppercase;le
42400
42463
  var cur=null, lastSeen=Date.now();
42401
42464
  function $(id){return document.getElementById(id)}
42402
42465
  function el(tag,cls,txt){var e=document.createElement(tag);if(cls)e.className=cls;if(txt!=null)e.textContent=txt;return e}
42466
+ /* #3342 \u2014 the app map. Layers are the distance from an entry (tab roots, or the first screen), the
42467
+ screens nothing leads to sit in their own last row, a link to a screen that does not exist is a red
42468
+ stub naming it. Built with DOM calls: a screen id is SVG text, never markup. Past 60 screens only
42469
+ the ones worth looking at are drawn (unreached, failed) and the summary carries the rest. */
42470
+ function drawMap(m){var card=$("appmap"),box=$("mapbox");clear(box);if(!m){card.hidden=true;return}card.hidden=false;
42471
+ var un=m.screens.filter(function(x){return !x.reached});
42472
+ /* "Nothing leads here" only when the crawl saw every link: a link built from list data (a feed's rows,
42473
+ a home grid) has a target it cannot resolve, and one of those may well open the screen. */
42474
+ var gone=!m.done?" not reached yet \xB7 ":m.unfollowed?" not seen linked \xB7 ":" unreached \xB7 ";
42475
+ $("msum").textContent=m.screens.length+" screens \xB7 "+un.length+gone+m.broken.length+" broken link"+(m.broken.length===1?"":"s")+(m.unfollowed?" \xB7 "+m.unfollowed+" link"+(m.unfollowed===1?"":"s")+" the crawl could not follow":"")+(m.edgesDropped?" \xB7 "+m.edgesDropped+" more link"+(m.edgesDropped===1?"":"s")+" not drawn":"")+(m.done?"":" \xB7 crawling");
42476
+ var show=m.screens.length>60?m.screens.filter(function(x){return !x.reached||x.status==="failed"}):m.screens,on={};show.forEach(function(x){on[x.id]=1});
42477
+ var out={},depth={},q=[];m.edges.forEach(function(e){if(on[e.from]&&on[e.to])(out[e.from]=out[e.from]||[]).push(e.to)});
42478
+ show.forEach(function(x){if(x.entry){depth[x.id]=0;q.push(x.id)}});
42479
+ while(q.length){var c=q.shift();(out[c]||[]).forEach(function(t){if(depth[t]==null){depth[t]=depth[c]+1;q.push(t)}})}
42480
+ var maxd=0;show.forEach(function(x){if(depth[x.id]>maxd)maxd=depth[x.id]});
42481
+ var rows=[];show.forEach(function(x){var r=!x.reached?maxd+2:depth[x.id]!=null?depth[x.id]:maxd+1;(rows[r]=rows[r]||[]).push(x)});
42482
+ /* Nodes are as wide as the longest id drawn needs (up to 300px): an id cut to its first letters
42483
+ cannot be told from its template siblings, which differ only at the end. Past that, the middle
42484
+ goes and both ends stay; the full id is always in the node's title. */
42485
+ var CW=6.5,longest=0;show.forEach(function(x){longest=Math.max(longest,x.id.length)});
42486
+ var W=Math.round(Math.min(300,Math.max(124,longest*CW+20))),H=28,HG=12,VG=36,P=10,ns="http://www.w3.org/2000/svg",pos={},y=P,width=0,unRow=maxd+2;
42487
+ function lab(id){var k=Math.floor((W-20)/CW);if(id.length<=k)return id;var a=Math.ceil((k-1)/2),b=Math.floor((k-1)/2);return id.slice(0,a)+"\u2026"+id.slice(id.length-b)}
42488
+ var svg=document.createElementNS(ns,"svg"),edgesG=document.createElementNS(ns,"g"),nodesG=document.createElementNS(ns,"g");svg.appendChild(edgesG);svg.appendChild(nodesG);
42489
+ function sv(tag,attrs,txt){var e=document.createElementNS(ns,tag);for(var k in attrs)e.setAttribute(k,String(attrs[k]));if(txt!=null)e.textContent=txt;return e}
42490
+ for(var r=0;r<rows.length;r++){var row=rows[r];if(!row)continue;
42491
+ if(r===unRow){nodesG.appendChild(sv("text",{x:P,y:y+10,"class":"mrow"},!m.done?"Not reached yet \u2014 no link to these seen so far":m.unfollowed?"Not seen linked \u2014 "+m.unfollowed+" link"+(m.unfollowed===1?"":"s")+" built from list data could not be followed":"Unreached \u2014 nothing leads here"));y+=16}
42492
+ row.forEach(function(x,i){var nx=P+i*(W+HG);pos[x.id]={x:nx,y:y};width=Math.max(width,nx+W+P);
42493
+ var g=sv("g",{"class":"mnode "+x.status+(x.entry?" entry":"")+(x.reached?"":" unreached")+(x.unfollowed?" unfollowed":""),"data-id":x.id});
42494
+ g.appendChild(sv("rect",{x:nx,y:y,width:W,height:H,rx:7}));g.appendChild(sv("text",{x:nx+W/2,y:y+H/2+4,"text-anchor":"middle"},lab(x.id)));
42495
+ g.appendChild(sv("title",{},x.id+" \xB7 "+x.status+(x.entry?" \xB7 entry":"")+(x.reached?"":" \xB7 unreached")+(x.unfollowed?" \xB7 "+x.unfollowed+" links the crawl could not follow":"")));
42496
+ if(x.unfollowed)g.appendChild(sv("text",{x:nx+W-6,y:y+10,"text-anchor":"end","class":"mfollow"},"+"+x.unfollowed+"?"));nodesG.appendChild(g)});
42497
+ y+=H+VG}
42498
+ m.edges.forEach(function(e){var a=pos[e.from],b=pos[e.to];if(!a||!b)return;var x1=a.x+W/2,y1=a.y+H,x2=b.x+W/2,y2=b.y,my=(y1+y2)/2;
42499
+ edgesG.appendChild(sv("path",{"class":"medge",d:"M"+x1+" "+y1+" C"+x1+" "+my+" "+x2+" "+my+" "+x2+" "+y2}))});
42500
+ var by=y;m.broken.forEach(function(e,i){var a=pos[e.from];if(!a)return;var bx=a.x+W+4,yy=a.y+H/2+4+i%2*10;
42501
+ nodesG.appendChild(sv("text",{x:bx,y:yy,"class":"mbroken"},"\u2192 missing: "+e.to));width=Math.max(width,bx+170)});
42502
+ svg.setAttribute("width",String(Math.max(width,200)));svg.setAttribute("height",String(Math.max(by,40)));svg.setAttribute("role","img");svg.setAttribute("aria-label","App map: "+$("msum").textContent);box.appendChild(svg)}
42503
+ /* #3342 \u2014 what the last rebuild changed, one line each, already text (studio.ts formats it); a hex
42504
+ token gets old/new swatches, painted through style, never markup. */
42505
+ function changesCard(b){var d=el("div","changes");d.appendChild(el("div","cht","What changed \xB7 "+hhmm(b.at)));var ul=document.createElement("ul");
42506
+ b.changes.lines.forEach(function(l){var li=el("li",null,l.text);if(l.from){var a=el("span","sw");a.style.background=l.from;li.appendChild(a)}if(l.to){var z=el("span","sw");z.style.background=l.to;li.appendChild(z)}ul.appendChild(li)});
42507
+ d.appendChild(ul);if(b.changes.more)d.appendChild(el("div","chm","+"+b.changes.more+" more"));return d}
42403
42508
  /* #3250 \u2014 a --lan run's way onto the owner's phone. The QR arrives as rows of 1/0 modules and is drawn
42404
42509
  as ONE path of unit squares: built with DOM calls, so nothing in the URL can become markup. */
42405
42510
  function lanCard(lan){var d=el("div","lan");d.appendChild(el("div","lant","Open on your phone"));
@@ -42435,7 +42540,7 @@ function render(s){
42435
42540
  if(pip){paintStrip(s)}else{
42436
42541
  var kept=s.live?focus.querySelector("iframe"):null;if(kept&&(kept.getAttribute("src")!==s.live.url||kept.getAttribute("data-run")!==s.live.runId))kept=null;
42437
42542
  if(kept){while(focus.lastChild&&focus.lastChild!==kept.parentNode)focus.removeChild(focus.lastChild)}else clear(focus);
42438
- if(s.live){$("focuslbl").textContent="Live preview";if(!kept){var f=el("div","phone vpf");var fr=document.createElement("iframe");fr.className="vp";fr.setAttribute("width","390");fr.setAttribute("height","844");fr.setAttribute("data-run",s.live.runId);fr.src=s.live.url;fr.title="Live preview";f.appendChild(fr);focus.appendChild(f)}fit();if(s.live.buildError){var be=el("div","chip bad",null);be.style.display="block";be.style.marginTop="8px";be.textContent="Last edit did not build, still showing the last good version: "+s.live.buildError;focus.appendChild(be)}focus.appendChild(el("div","cap",(s.live.watching?"Repaints as the app is edited \xB7 ":"The build this run started with; himi run --watch repaints on every edit \xB7 ")+s.live.url));if(s.live.lan)focus.appendChild(lanCard(s.live.lan))}
42543
+ if(s.live){$("focuslbl").textContent="Live preview";if(!kept){var f=el("div","phone vpf");var fr=document.createElement("iframe");fr.className="vp";fr.setAttribute("width","390");fr.setAttribute("height","844");fr.setAttribute("data-run",s.live.runId);fr.src=s.live.url;fr.title="Live preview";f.appendChild(fr);focus.appendChild(f)}fit();if(s.live.buildError){var be=el("div","chip bad",null);be.style.display="block";be.style.marginTop="8px";be.textContent="Last edit did not build, still showing the last good version: "+s.live.buildError;focus.appendChild(be)}focus.appendChild(el("div","cap",(s.live.watching?"Repaints as the app is edited \xB7 ":"The build this run started with; himi run --watch repaints on every edit \xB7 ")+s.live.url));if(s.live.lan)focus.appendChild(lanCard(s.live.lan));if(s.live.lastBuild&&s.live.lastBuild.changes)focus.appendChild(changesCard(s.live.lastBuild))}
42439
42544
  else if(s.focus&&s.focus.frames.length){var fl=s.focus.frames;$("focuslbl").textContent="Latest \xB7 "+s.focus.id;var p=el("div","phone");p.appendChild(img(fl[fl.length-1].img));focus.appendChild(p);focus.appendChild(el("div","cap",fl.length+(fl.length===1?" version":" versions")+" so far"))}
42440
42545
  else{$("focuslbl").textContent="Latest";focus.appendChild(el("div","empty","The first render appears here."))}
42441
42546
  if(s.focus&&s.focus.frames.length>1)s.focus.frames.forEach(function(fm){film.appendChild(img(fm.img))});}}
@@ -42453,7 +42558,7 @@ function render(s){
42453
42558
  if(s.brand.site){var si=img(s.brand.site,"site");brand.appendChild(si)}
42454
42559
  if(s.brand.site&&s.focus&&s.focus.frames.length){brand.appendChild(el("span","sub","\u2192"));var pf=el("div","shot");pf.style.width="90px";var ff=el("div","frame");ff.appendChild(img(s.focus.frames[s.focus.frames.length-1].img));pf.appendChild(ff);brand.appendChild(pf)}
42455
42560
  if(s.app.palette.length){var sw=el("div","sw");s.app.palette.forEach(function(c){var d=el("span");d.style.background=c.hex;d.title=c.name+" "+c.hex;sw.appendChild(d)});brand.appendChild(sw)}
42456
- var nav=$("nav");clear(nav);$("navcard").style.display=s.nav.length?"":"none";s.nav.forEach(function(e){nav.appendChild(el("span",null,e.from+" \u2192 "+e.to))});
42561
+ var nav=$("nav");clear(nav);$("navcard").style.display=s.nav.length&&!s.map?"":"none";drawMap(s.map);s.nav.forEach(function(e){nav.appendChild(el("span",null,e.from+" \u2192 "+e.to))});
42457
42562
  var log=$("log");clear(log);if(!s.journal.length)log.appendChild(el("li","empty","Postcards the agent journals appear here."));
42458
42563
  s.journal.forEach(function(j){var li=el("li");li.appendChild(el("div","t",hhmm(j.ts)));var body=el("div");body.appendChild(el("div",null,j.message));if(j.step)body.appendChild(el("div","s","step "+j.stepNumber+" \xB7 "+j.step));
42459
42564
  if(j.images.length){var row=el("div","imgs");j.images.forEach(function(u){row.appendChild(img(u))});body.appendChild(row)}li.appendChild(body);log.appendChild(li)});
@@ -42573,7 +42678,7 @@ function favicon(n,bad){try{var c=document.createElement("canvas");c.width=c.hei
42573
42678
  function burst(){var c=$("fx");c.setAttribute("data-bursts",String((+c.getAttribute("data-bursts")||0)+1));if(reduced())return;var x=c.getContext&&c.getContext("2d");if(!x)return;var W=c.width=innerWidth,H=c.height=innerHeight,cols=["#1f7a4d","#3b6fd8","#e0a030","#d4537e",accentNow()],P=[];for(var i=0;i<120;i++)P.push({x:W/2,y:H*.35,vx:(Math.random()-.5)*14,vy:-Math.random()*12-4,c:cols[i%cols.length],r:Math.random()*6.28});var t0=now();(function f(){x.clearRect(0,0,W,H);P.forEach(function(p){p.x+=p.vx;p.y+=p.vy;p.vy+=.35;p.r+=.2;x.save();x.translate(p.x,p.y);x.rotate(p.r);x.fillStyle=p.c;x.fillRect(-4,-2,8,4);x.restore()});if(now()-t0<2200)raf(f);else x.clearRect(0,0,W,H)})()}
42574
42679
  function countTo(e,a,b){if(!e)return;if(reduced()){e.textContent=String(b);return}var t0=now();(function f(){var k=Math.min(1,(now()-t0)/900);e.textContent=String(Math.round(a+(b-a)*k));if(k<1)raf(f)})()}
42575
42680
  function snap(s){var sc={},acc=null;s.screens.forEach(function(x){sc[x.id]=x.img});s.app.palette.forEach(function(p){if(p.name==="accentPrimary")acc=p.hex});return{screens:sc,step:s.current?s.current.stepNumber:0,top:s.journal[0]?s.journal[0].ts:null,build:s.live&&s.live.lastBuild?s.live.lastBuild.at:s.pastBuild?s.pastBuild.at:null,run:s.live?s.live.runId:s.pastBuild?s.pastBuild.run:null,push:s.health.push?s.health.push.releaseId:null,gaps:s.health.test?s.health.test.gaps:null,accent:acc,reveal:s.reveal?s.reveal.at:null}}
42576
- function buildLine(b){if(!b.ok)return "Build failed \xB7 still showing the last good version";var what=b.screens.length?b.screens.slice(0,3).join(", ")+(b.screens.length>3?" +"+(b.screens.length-3):""):b.tokens?"design tokens":b.config?"app config":"no visible change";return "Rebuilt"+(b.ms!=null?" in "+(b.ms/1000).toFixed(1)+" s":"")+" \xB7 "+what}
42681
+ function buildLine(b){if(!b.ok)return "Build failed \xB7 still showing the last good version";var what=b.headline?b.headline:b.screens.length?b.screens.slice(0,3).join(", ")+(b.screens.length>3?" +"+(b.screens.length-3):""):b.tokens?"design tokens":b.config?"app config":"no visible change";return "Rebuilt"+(b.ms!=null?" in "+(b.ms/1000).toFixed(1)+" s":"")+" \xB7 "+what}
42577
42682
  function effects(s){var is=snap(s),was=seen;seen=is;if(is.accent)document.documentElement.style.setProperty("--accent",is.accent);
42578
42683
  /* The phone (kept across renders) and the Now line outlive a render: clear what they played last. */
42579
42684
  var stage=rewinding?$("still"):$("focus"),ph=stage.querySelector(".phone"),nm=$("nowmsg");if(ph)ph.classList.remove("pulse","bad");nm.classList.remove("slidein");if(!was)return;
@@ -42636,9 +42741,174 @@ var init_studio_page = __esm({
42636
42741
  }
42637
42742
  });
42638
42743
 
42639
- // src/studio-timeline.ts
42640
- import { existsSync as existsSync40, readFileSync as readFileSync43 } from "node:fs";
42744
+ // src/build-changes.ts
42745
+ var build_changes_exports = {};
42746
+ __export(build_changes_exports, {
42747
+ boardLines: () => boardLines,
42748
+ describeChanges: () => describeChanges,
42749
+ diffDescriptors: () => diffDescriptors,
42750
+ diffTokens: () => diffTokens,
42751
+ headline: () => headline
42752
+ });
42753
+ import { existsSync as existsSync40, readdirSync as readdirSync22, readFileSync as readFileSync43 } from "node:fs";
42641
42754
  import { join as join38 } from "node:path";
42755
+ function componentsOf(descriptor) {
42756
+ const out = /* @__PURE__ */ new Map();
42757
+ visitComponents(descriptor, (n) => {
42758
+ if (typeof n.id !== "string" || out.has(n.id)) return;
42759
+ const props = typeof n.body.props === "object" && n.body.props !== null ? n.body.props : {};
42760
+ out.set(n.id, { type: n.body.type, props });
42761
+ });
42762
+ return out;
42763
+ }
42764
+ function diffDescriptors(screen, before, after) {
42765
+ const a = componentsOf(before);
42766
+ const b = componentsOf(after);
42767
+ const props = [];
42768
+ const added = [];
42769
+ for (const [id, now] of b) {
42770
+ const was = a.get(id);
42771
+ if (!was) {
42772
+ added.push({ kind: "added", screen, node: id, type: now.type });
42773
+ continue;
42774
+ }
42775
+ const keys = /* @__PURE__ */ new Set([...Object.keys(was.props), ...Object.keys(now.props)]);
42776
+ for (const key2 of keys) {
42777
+ const x = was.props[key2];
42778
+ const y = now.props[key2];
42779
+ if (!scalar2(x) && !scalar2(y)) continue;
42780
+ if (x === y) continue;
42781
+ props.push({
42782
+ kind: TEXT_PROPS.has(key2) ? "text" : "prop",
42783
+ screen,
42784
+ node: id,
42785
+ path: key2,
42786
+ ...scalar2(x) ? { from: cut(String(x)) } : {},
42787
+ ...scalar2(y) ? { to: cut(String(y)) } : {}
42788
+ });
42789
+ }
42790
+ }
42791
+ const removed = [];
42792
+ for (const [id, was] of a) if (!b.has(id)) removed.push({ kind: "removed", screen, node: id, type: was.type });
42793
+ return [...props.filter((c) => c.kind === "text"), ...added, ...removed, ...props.filter((c) => c.kind === "prop")];
42794
+ }
42795
+ function leaves(value, prefix, out) {
42796
+ if (scalar2(value)) {
42797
+ out.set(prefix, String(value));
42798
+ return;
42799
+ }
42800
+ if (typeof value !== "object" || value === null) return;
42801
+ for (const [k, v] of Object.entries(value)) leaves(v, prefix ? `${prefix}.${k}` : k, out);
42802
+ }
42803
+ function diffTokens(before, after) {
42804
+ const a = /* @__PURE__ */ new Map();
42805
+ const b = /* @__PURE__ */ new Map();
42806
+ leaves(before, "", a);
42807
+ leaves(after, "", b);
42808
+ const paths = [.../* @__PURE__ */ new Set([...a.keys(), ...b.keys()])].sort();
42809
+ const out = [];
42810
+ for (const path2 of paths) {
42811
+ const x = a.get(path2);
42812
+ const y = b.get(path2);
42813
+ if (x === y) continue;
42814
+ out.push({ kind: "token", path: path2, ...x !== void 0 ? { from: cut(x) } : {}, ...y !== void 0 ? { to: cut(y) } : {} });
42815
+ }
42816
+ return out;
42817
+ }
42818
+ function describeChanges(oldTree, newTree, diff, opts = {}) {
42819
+ if (!existsSync40(oldTree) || !existsSync40(newTree)) return { items: [], more: 0 };
42820
+ const all = [];
42821
+ const before = screensIn(oldTree);
42822
+ const after = screensIn(newTree);
42823
+ for (const id of diff.screens) {
42824
+ if (!after.has(id) || !before.has(id)) continue;
42825
+ const was = readJson2(join38(oldTree, "screens", `${id}.json`));
42826
+ const now = readJson2(join38(newTree, "screens", `${id}.json`));
42827
+ const changes = diffDescriptors(id, was, now);
42828
+ if (changes.length) all.push(...changes);
42829
+ else all.push({ kind: JSON.stringify(was) === JSON.stringify(now) ? "logic" : "descriptor", screen: id });
42830
+ }
42831
+ if (diff.tokens) all.push(...diffTokens(readJson2(join38(oldTree, "tokens.json")), readJson2(join38(newTree, "tokens.json"))));
42832
+ for (const id of [...after].sort()) if (!before.has(id)) all.push({ kind: "screen-added", screen: id });
42833
+ for (const id of [...before].sort()) if (!after.has(id)) all.push({ kind: "screen-removed", screen: id });
42834
+ const ranked = all.map((c, i) => ({ c, i })).sort((x, y) => RANK[x.c.kind] - RANK[y.c.kind] || x.i - y.i).map((x) => x.c);
42835
+ const max = opts.max ?? MAX_ITEMS;
42836
+ return { items: ranked.slice(0, max), more: Math.max(0, ranked.length - max) };
42837
+ }
42838
+ function headline(changes) {
42839
+ const c = changes.items[0];
42840
+ return c ? lineOf(c) : null;
42841
+ }
42842
+ function boardLines(changes) {
42843
+ const lines = [];
42844
+ for (const c of changes.items) {
42845
+ const text5 = lineOf(c);
42846
+ const same2 = lines.find((l) => l.text === text5);
42847
+ if (same2) {
42848
+ same2.n++;
42849
+ continue;
42850
+ }
42851
+ lines.push({
42852
+ text: text5,
42853
+ n: 1,
42854
+ ...c.kind === "token" && c.from && HEX.test(c.from) ? { from: c.from } : {},
42855
+ ...c.kind === "token" && c.to && HEX.test(c.to) ? { to: c.to } : {}
42856
+ });
42857
+ }
42858
+ return { lines: lines.map(({ n, ...l }) => n > 1 ? { ...l, text: `${l.text} (\xD7${n})` } : l), more: changes.more };
42859
+ }
42860
+ function lineOf(c) {
42861
+ switch (c.kind) {
42862
+ case "text":
42863
+ case "prop":
42864
+ return `${c.screen}: \u201C${c.from ?? ""}\u201D \u2192 \u201C${c.to ?? ""}\u201D`;
42865
+ case "added":
42866
+ return `${c.screen}: + ${c.type}`;
42867
+ case "removed":
42868
+ return `${c.screen}: \u2212 ${c.type}`;
42869
+ case "token":
42870
+ return `${c.path} ${c.from ?? "(new)"} \u2192 ${c.to ?? "(removed)"}`;
42871
+ case "descriptor":
42872
+ return `${c.screen}: descriptor changed`;
42873
+ case "logic":
42874
+ return `${c.screen}: logic changed`;
42875
+ case "screen-added":
42876
+ return `new screen: ${c.screen}`;
42877
+ case "screen-removed":
42878
+ return `screen removed: ${c.screen}`;
42879
+ }
42880
+ }
42881
+ var TEXT_PROPS, RANK, MAX_TEXT, MAX_ITEMS, cut, scalar2, readJson2, screensIn, HEX;
42882
+ var init_build_changes = __esm({
42883
+ "src/build-changes.ts"() {
42884
+ init_stable_ids();
42885
+ TEXT_PROPS = /* @__PURE__ */ new Set(["text", "template", "title", "subtitle", "label", "placeholder", "caption", "message", "value", "hint", "description"]);
42886
+ RANK = { text: 0, added: 1, removed: 1, token: 2, prop: 3, "screen-added": 4, "screen-removed": 4, descriptor: 5, logic: 5 };
42887
+ MAX_TEXT = 60;
42888
+ MAX_ITEMS = 12;
42889
+ cut = (s) => s.length > MAX_TEXT ? `${s.slice(0, MAX_TEXT - 1)}\u2026` : s;
42890
+ scalar2 = (v) => typeof v === "string" || typeof v === "number" || typeof v === "boolean";
42891
+ readJson2 = (path2) => {
42892
+ try {
42893
+ return JSON.parse(readFileSync43(path2, "utf8"));
42894
+ } catch {
42895
+ return void 0;
42896
+ }
42897
+ };
42898
+ screensIn = (tree) => {
42899
+ try {
42900
+ return new Set(readdirSync22(join38(tree, "screens")).filter((f) => f.endsWith(".json")).map((f) => f.slice(0, -5)));
42901
+ } catch {
42902
+ return /* @__PURE__ */ new Set();
42903
+ }
42904
+ };
42905
+ HEX = /^#(?:[0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$/i;
42906
+ }
42907
+ });
42908
+
42909
+ // src/studio-timeline.ts
42910
+ import { existsSync as existsSync41, readFileSync as readFileSync44 } from "node:fs";
42911
+ import { join as join39 } from "node:path";
42642
42912
  function stateOf(view) {
42643
42913
  const j = view.journal[0];
42644
42914
  const h = view.health;
@@ -42705,10 +42975,10 @@ function validFrame(f) {
42705
42975
  return true;
42706
42976
  }
42707
42977
  function readTimeline(dir) {
42708
- const file = join38(dir, TIMELINE_FILE);
42709
- if (!existsSync40(file)) return [];
42978
+ const file = join39(dir, TIMELINE_FILE);
42979
+ if (!existsSync41(file)) return [];
42710
42980
  const out = [];
42711
- for (const line of readFileSync43(file, "utf8").split("\n")) {
42981
+ for (const line of readFileSync44(file, "utf8").split("\n")) {
42712
42982
  if (!line.trim()) continue;
42713
42983
  try {
42714
42984
  const f = JSON.parse(line);
@@ -42725,22 +42995,23 @@ function trim(frames) {
42725
42995
  return frames.slice(-MAX_FRAMES);
42726
42996
  }
42727
42997
  function writeTimelineJs(dir, frames) {
42728
- writeFileAtomic(join38(dir, TIMELINE_JS), `window.__himiTimeline && window.__himiTimeline(${scriptJson(frames)});
42998
+ writeFileAtomic(join39(dir, TIMELINE_JS), `window.__himiTimeline && window.__himiTimeline(${scriptJson(frames)});
42729
42999
  `);
42730
43000
  }
42731
43001
  function appendFrame(dir, view, reveal = null) {
42732
43002
  const frames = readTimeline(dir);
42733
43003
  const last = frames[frames.length - 1];
42734
43004
  if (last && last.v === view.v && !reveal) {
42735
- if (!existsSync40(join38(dir, TIMELINE_JS))) writeTimelineJs(dir, frames);
43005
+ if (!existsSync41(join39(dir, TIMELINE_JS))) writeTimelineJs(dir, frames);
42736
43006
  return frames.length;
42737
43007
  }
42738
43008
  let prev = null;
42739
43009
  for (const f of frames) prev = fold(prev, f);
42740
43010
  const frame = frameFor(stateOf(view), prev, view.generatedAt, view.v, frames.length % KEYFRAME_EVERY === 0);
43011
+ if (!frame.key && !reveal && Object.keys(frame).every((k) => k === "ts" || k === "v")) return frames.length;
42741
43012
  if (reveal) frame.reveal = reveal;
42742
43013
  const kept = trim([...frames, frame]);
42743
- writeFileAtomic(join38(dir, TIMELINE_FILE), kept.map((f) => JSON.stringify(f)).join("\n") + "\n");
43014
+ writeFileAtomic(join39(dir, TIMELINE_FILE), kept.map((f) => JSON.stringify(f)).join("\n") + "\n");
42744
43015
  writeTimelineJs(dir, kept);
42745
43016
  return kept.length;
42746
43017
  }
@@ -42762,8 +43033,8 @@ var init_studio_timeline = __esm({
42762
43033
  "src/studio-timeline.ts"() {
42763
43034
  init_progress();
42764
43035
  init_studio_page();
42765
- TIMELINE_FILE = join38(".himi", "studio", "timeline.jsonl");
42766
- TIMELINE_JS = join38(".himi", "studio", "timeline.js");
43036
+ TIMELINE_FILE = join39(".himi", "studio", "timeline.jsonl");
43037
+ TIMELINE_JS = join39(".himi", "studio", "timeline.js");
42767
43038
  MAX_FRAMES = 2e3;
42768
43039
  KEYFRAME_EVERY = 50;
42769
43040
  same = (a, b) => JSON.stringify(a) === JSON.stringify(b);
@@ -42786,6 +43057,7 @@ var init_studio_timeline = __esm({
42786
43057
  var studio_exports = {};
42787
43058
  __export(studio_exports, {
42788
43059
  EDGES_RECORD: () => EDGES_RECORD,
43060
+ MAP_RECORD: () => MAP_RECORD,
42789
43061
  PUSH_RECORD: () => PUSH_RECORD,
42790
43062
  RUN_RECORD: () => RUN_RECORD,
42791
43063
  STUDIO_DIR: () => STUDIO_DIR,
@@ -42800,8 +43072,8 @@ __export(studio_exports, {
42800
43072
  writeStudioRecord: () => writeStudioRecord
42801
43073
  });
42802
43074
  import { createHash as createHash21 } from "node:crypto";
42803
- import { existsSync as existsSync41, readdirSync as readdirSync22, readFileSync as readFileSync44, realpathSync as realpathSync5, statSync as statSync8 } from "node:fs";
42804
- import { basename as basename10, extname as extname3, join as join39, relative as relative7, resolve as resolve33, sep as sep9 } from "node:path";
43075
+ import { existsSync as existsSync42, readdirSync as readdirSync23, readFileSync as readFileSync45, realpathSync as realpathSync5, statSync as statSync8 } from "node:fs";
43076
+ import { basename as basename10, extname as extname3, join as join40, relative as relative7, resolve as resolve33, sep as sep9 } from "node:path";
42805
43077
  import { pathToFileURL as pathToFileURL7 } from "node:url";
42806
43078
  function realInside(dir, abs) {
42807
43079
  try {
@@ -42826,12 +43098,12 @@ function archiveFile(dir, memory, abs) {
42826
43098
  if (size === 0) return null;
42827
43099
  const key2 = relative7(dir, abs);
42828
43100
  const hit = memory.files?.[key2];
42829
- if (hit && hit.size === size && hit.mtimeMs === mtimeMs && hit.ino === ino && existsSync41(join39(dir, HISTORY_DIR, `${hit.sha}.${hit.ext}`))) {
43101
+ if (hit && hit.size === size && hit.mtimeMs === mtimeMs && hit.ino === ino && existsSync42(join40(dir, HISTORY_DIR, `${hit.sha}.${hit.ext}`))) {
42830
43102
  return { sha: hit.sha, ext: hit.ext, file: `${hit.sha}.${hit.ext}` };
42831
43103
  }
42832
43104
  let archived;
42833
43105
  try {
42834
- archived = archiveBytes(dir, readFileSync44(real));
43106
+ archived = archiveBytes(dir, readFileSync45(real));
42835
43107
  } catch {
42836
43108
  return null;
42837
43109
  }
@@ -42840,17 +43112,17 @@ function archiveFile(dir, memory, abs) {
42840
43112
  return { sha: archived.sha256, ext: archived.ext, file: `${archived.sha256}.${archived.ext}` };
42841
43113
  }
42842
43114
  function loadMemory(dir, now) {
42843
- const raw = obj(readJson2(join39(dir, MEMORY_FILE)));
43115
+ const raw = obj(readJson3(join40(dir, MEMORY_FILE)));
42844
43116
  if (raw && raw.version === 1 && obj(raw.shots) && Array.isArray(raw.timeline)) return raw;
42845
43117
  return { version: 1, createdAt: now, shots: {}, timeline: [] };
42846
43118
  }
42847
43119
  function observeShots(dir, memory, now) {
42848
- const shotsDir = join39(dir, ".himi", "shots");
43120
+ const shotsDir = join40(dir, ".himi", "shots");
42849
43121
  const seen = /* @__PURE__ */ new Set();
42850
- const files = existsSync41(shotsDir) ? readdirSync22(shotsDir).filter((f) => /\.(png|jpe?g)$/i.test(f)).sort() : [];
43122
+ const files = existsSync42(shotsDir) ? readdirSync23(shotsDir).filter((f) => /\.(png|jpe?g)$/i.test(f)).sort() : [];
42851
43123
  for (const f of files) {
42852
43124
  const id = basename10(f, extname3(f));
42853
- const archived = archiveFile(dir, memory, join39(shotsDir, f));
43125
+ const archived = archiveFile(dir, memory, join40(shotsDir, f));
42854
43126
  if (!archived) continue;
42855
43127
  seen.add(id);
42856
43128
  const { sha, ext } = archived;
@@ -42865,14 +43137,14 @@ function observeShots(dir, memory, now) {
42865
43137
  memory.shots[id] = { ...prev, present: true };
42866
43138
  }
42867
43139
  }
42868
- if (existsSync41(shotsDir)) {
43140
+ if (existsSync42(shotsDir)) {
42869
43141
  for (const [id, shot] of Object.entries(memory.shots)) if (!seen.has(id) && shot.present) memory.shots[id] = { ...shot, present: false };
42870
43142
  }
42871
43143
  if (memory.timeline.length > MAX_TIMELINE) memory.timeline = memory.timeline.slice(-MAX_TIMELINE);
42872
43144
  return memory;
42873
43145
  }
42874
43146
  function palette(dir) {
42875
- const colors = obj(obj(readJson2(join39(dir, "tokens.json")))?.colors);
43147
+ const colors = obj(obj(readJson3(join40(dir, "tokens.json")))?.colors);
42876
43148
  if (!colors) return [];
42877
43149
  const pick2 = (key2) => {
42878
43150
  const v = colors[key2];
@@ -42882,7 +43154,7 @@ function palette(dir) {
42882
43154
  return ["accentPrimary", "accentSecondary", "accentSurface", "surfacePrimary", "textPrimary"].map(pick2).filter((x) => !!x);
42883
43155
  }
42884
43156
  function appIdentity(dir) {
42885
- const manifest = obj(readJson2(join39(dir, "himalaya.content.json")));
43157
+ const manifest = obj(readJson3(join40(dir, "himalaya.content.json")));
42886
43158
  const name = text(manifest?.name) ?? basename10(resolve33(dir));
42887
43159
  const icon = text(manifest?.icon)?.trim();
42888
43160
  const iconPath = icon ? resolve33(dir, icon) : null;
@@ -42890,12 +43162,12 @@ function appIdentity(dir) {
42890
43162
  return { name, iconFile: real && statSync8(real).isFile() ? iconPath : null };
42891
43163
  }
42892
43164
  function testRuns(dir) {
42893
- const att = join39(dir, ".himi", "attestations");
42894
- if (!existsSync41(att)) return [];
43165
+ const att = join40(dir, ".himi", "attestations");
43166
+ if (!existsSync42(att)) return [];
42895
43167
  const runs = [];
42896
- for (const f of readdirSync22(att)) {
43168
+ for (const f of readdirSync23(att)) {
42897
43169
  if (!f.endsWith(".json") || f === "latest.json") continue;
42898
- const r = obj(readJson2(join39(att, f)));
43170
+ const r = obj(readJson3(join40(att, f)));
42899
43171
  if (!r || r.tool !== "himi-test" || typeof r.runId !== "string") continue;
42900
43172
  const report2 = obj(r.report);
42901
43173
  const coverage = obj(r.coverage);
@@ -42911,20 +43183,20 @@ function testRuns(dir) {
42911
43183
  return runs.sort((a, b) => a.runId.localeCompare(b.runId));
42912
43184
  }
42913
43185
  function siteHome(dir) {
42914
- for (const pages of [join39(dir, ".himi", "business-book", "evidence", "pages"), join39(dir, "evidence", "pages")]) {
42915
- if (!existsSync41(pages)) continue;
42916
- const home = readdirSync22(pages).filter((f) => /^home.*\.(png|jpe?g)$/i.test(f)).sort()[0];
42917
- if (home) return join39(pages, home);
43186
+ for (const pages of [join40(dir, ".himi", "business-book", "evidence", "pages"), join40(dir, "evidence", "pages")]) {
43187
+ if (!existsSync42(pages)) continue;
43188
+ const home = readdirSync23(pages).filter((f) => /^home.*\.(png|jpe?g)$/i.test(f)).sort()[0];
43189
+ if (home) return join40(pages, home);
42918
43190
  }
42919
43191
  return null;
42920
43192
  }
42921
43193
  function uxStatuses(dir) {
42922
- const platforms = obj(obj(readJson2(join39(dir, ".himi", "ux-audit.json")))?.platforms);
43194
+ const platforms = obj(obj(readJson3(join40(dir, ".himi", "ux-audit.json")))?.platforms);
42923
43195
  if (!platforms) return [];
42924
43196
  return Object.entries(platforms).map(([platform, rec]) => ({ platform, status: text(obj(rec)?.status) ?? "" })).filter((p) => p.status);
42925
43197
  }
42926
43198
  function liveRun(dir) {
42927
- const rec = obj(readJson2(join39(dir, RUN_RECORD)));
43199
+ const rec = obj(readJson3(join40(dir, RUN_RECORD)));
42928
43200
  const url = text(rec?.url);
42929
43201
  const pid = rec?.pid;
42930
43202
  if (!url || !/^https?:\/\//i.test(url) || typeof pid !== "number") return null;
@@ -42945,7 +43217,8 @@ function liveRun(dir) {
42945
43217
  ms: typeof build2.ms === "number" && Number.isFinite(build2.ms) ? Math.max(0, Math.round(build2.ms)) : null,
42946
43218
  screens: (Array.isArray(build2.screens) ? build2.screens : []).filter((x) => typeof x === "string").slice(0, 20),
42947
43219
  tokens: build2.tokens === true,
42948
- config: build2.config === true
43220
+ config: build2.config === true,
43221
+ ...changesOf(build2.changes)
42949
43222
  } : null,
42950
43223
  lan: lanOf(rec?.lanUrl)
42951
43224
  };
@@ -42953,6 +43226,47 @@ function liveRun(dir) {
42953
43226
  return null;
42954
43227
  }
42955
43228
  }
43229
+ function mapOf(value) {
43230
+ const raw = obj(value);
43231
+ if (!raw || !Array.isArray(raw.screens)) return null;
43232
+ const screens = [];
43233
+ const ids = /* @__PURE__ */ new Set();
43234
+ for (const it of raw.screens.slice(0, 300)) {
43235
+ const o = obj(it);
43236
+ const id = typeof o?.id === "string" ? o.id.slice(0, 120) : null;
43237
+ if (!o || !id || ids.has(id) || typeof o.status !== "string" || !MAP_STATUSES.has(o.status)) continue;
43238
+ ids.add(id);
43239
+ screens.push({ id, entry: o.entry === true, reached: o.reached === true, status: o.status, unfollowed: count(o.unfollowed) });
43240
+ }
43241
+ if (!screens.length) return null;
43242
+ const link = (it, needTo) => {
43243
+ const o = obj(it);
43244
+ const from = typeof o?.from === "string" ? o.from.slice(0, 120) : null;
43245
+ const to = typeof o?.to === "string" ? o.to.slice(0, 120) : null;
43246
+ if (!from || !to || !ids.has(from) || needTo && !ids.has(to)) return null;
43247
+ return { from, to, via: typeof o?.via === "string" ? o.via.slice(0, 120) : "" };
43248
+ };
43249
+ const edges = (Array.isArray(raw.edges) ? raw.edges.slice(0, 600) : []).map((e) => link(e, true)).filter((e) => !!e);
43250
+ const broken = (Array.isArray(raw.broken) ? raw.broken.slice(0, 100) : []).map((e) => link(e, false)).filter((e) => !!e);
43251
+ return { screens, edges, broken, unfollowed: count(raw.unfollowed), edgesDropped: count(raw.edgesDropped), done: raw.done === true };
43252
+ }
43253
+ function changesOf(value) {
43254
+ const raw = obj(value);
43255
+ const items = [];
43256
+ for (const it of Array.isArray(raw?.items) ? raw.items.slice(0, 12) : []) {
43257
+ const o = obj(it);
43258
+ if (!o || typeof o.kind !== "string" || !CHANGE_KINDS.has(o.kind)) continue;
43259
+ const c = { kind: o.kind };
43260
+ for (const k of ["screen", "node", "type", "path", "from", "to"]) {
43261
+ if (typeof o[k] === "string") c[k] = o[k].slice(0, 80);
43262
+ }
43263
+ items.push(c);
43264
+ }
43265
+ if (!items.length) return { headline: null, changes: null };
43266
+ const more = typeof raw?.more === "number" && Number.isFinite(raw.more) ? Math.max(0, Math.round(raw.more)) : 0;
43267
+ const changes = { items, more };
43268
+ return { headline: headline(changes), changes: boardLines(changes) };
43269
+ }
42956
43270
  function lanOf(value) {
42957
43271
  const raw = text(value);
42958
43272
  if (!raw) return null;
@@ -42992,9 +43306,9 @@ function buildView(dir, memory, now) {
42992
43306
  const runs = testRuns(dir);
42993
43307
  const last = runs[runs.length - 1];
42994
43308
  const prev = runs[runs.length - 2];
42995
- const validate3 = obj(readJson2(join39(dir, VALIDATE_RECORD)));
42996
- const push = obj(readJson2(join39(dir, PUSH_RECORD)));
42997
- const edges = readJson2(join39(dir, EDGES_RECORD));
43309
+ const validate3 = obj(readJson3(join40(dir, VALIDATE_RECORD)));
43310
+ const push = obj(readJson3(join40(dir, PUSH_RECORD)));
43311
+ const edges = readJson3(join40(dir, EDGES_RECORD));
42998
43312
  const nav = [];
42999
43313
  if (Array.isArray(edges)) {
43000
43314
  const seen = /* @__PURE__ */ new Set();
@@ -43032,6 +43346,7 @@ function buildView(dir, memory, now) {
43032
43346
  },
43033
43347
  brand: { site: siteImg },
43034
43348
  nav,
43349
+ map: mapOf(readJson3(join40(dir, MAP_RECORD))),
43035
43350
  live: liveRun(dir),
43036
43351
  timelineFrames: 0,
43037
43352
  reveal: memory.reveal ?? null
@@ -43065,15 +43380,15 @@ function writeBoard(dir, now) {
43065
43380
  } catch {
43066
43381
  view.timelineFrames = 0;
43067
43382
  }
43068
- writeAtomic(join39(dir, MEMORY_FILE), JSON.stringify(memory));
43069
- writeAtomic(join39(dir, STATE_FILE), `window.__himiStudio && window.__himiStudio(${scriptJson(view)});
43383
+ writeAtomic(join40(dir, MEMORY_FILE), JSON.stringify(memory));
43384
+ writeAtomic(join40(dir, STATE_FILE), `window.__himiStudio && window.__himiStudio(${scriptJson(view)});
43070
43385
  `);
43071
- writeAtomic(join39(dir, STUDIO_PAGE), renderStudioHtml(view));
43386
+ writeAtomic(join40(dir, STUDIO_PAGE), renderStudioHtml(view));
43072
43387
  return view;
43073
43388
  }
43074
43389
  function refreshStudio(dir, now = /* @__PURE__ */ new Date()) {
43075
43390
  try {
43076
- if (!existsSync41(join39(dir, STUDIO_PAGE))) return { refreshed: false };
43391
+ if (!existsSync42(join40(dir, STUDIO_PAGE))) return { refreshed: false };
43077
43392
  return { refreshed: true, view: writeBoard(dir, now.toISOString()) };
43078
43393
  } catch {
43079
43394
  return { refreshed: false };
@@ -43081,39 +43396,41 @@ function refreshStudio(dir, now = /* @__PURE__ */ new Date()) {
43081
43396
  }
43082
43397
  function openStudio(dir, now = /* @__PURE__ */ new Date()) {
43083
43398
  const view = writeBoard(dir, now.toISOString());
43084
- const page = join39(dir, STUDIO_PAGE);
43399
+ const page = join40(dir, STUDIO_PAGE);
43085
43400
  return { page, url: pathToFileURL7(page).href, view };
43086
43401
  }
43087
43402
  function writeStudioRecord(dir, record2, value) {
43088
43403
  try {
43089
43404
  if (!realDirIn(dir, ".himi")) return;
43090
- writeAtomic(join39(dir, record2), `${JSON.stringify(value, null, 2)}
43405
+ writeAtomic(join40(dir, record2), `${JSON.stringify(value, null, 2)}
43091
43406
  `);
43092
43407
  } catch {
43093
43408
  }
43094
43409
  }
43095
- var STUDIO_DIR, STUDIO_PAGE, STATE_FILE, MEMORY_FILE, VALIDATE_RECORD, PUSH_RECORD, EDGES_RECORD, RUN_RECORD, MAX_TIMELINE, MAX_JOURNAL, MAX_NAV_EDGES, readJson2, obj, num, text, historyUrl, runTime;
43410
+ var STUDIO_DIR, STUDIO_PAGE, STATE_FILE, MEMORY_FILE, VALIDATE_RECORD, PUSH_RECORD, EDGES_RECORD, MAP_RECORD, RUN_RECORD, MAX_TIMELINE, MAX_JOURNAL, MAX_NAV_EDGES, readJson3, obj, num, text, historyUrl, runTime, MAP_STATUSES, count, CHANGE_KINDS;
43096
43411
  var init_studio = __esm({
43097
43412
  "src/studio.ts"() {
43098
43413
  init_studio_page();
43099
43414
  init_qr();
43415
+ init_build_changes();
43100
43416
  init_studio_timeline();
43101
43417
  init_progress();
43102
43418
  init_studio_page();
43103
- STUDIO_DIR = join39(".himi", "studio");
43104
- STUDIO_PAGE = join39(STUDIO_DIR, "index.html");
43105
- STATE_FILE = join39(STUDIO_DIR, "state.js");
43106
- MEMORY_FILE = join39(STUDIO_DIR, "memory.json");
43107
- VALIDATE_RECORD = join39(".himi", "validate.json");
43108
- PUSH_RECORD = join39(".himi", "last-push.json");
43109
- EDGES_RECORD = join39(".himi", "test-edges.json");
43110
- RUN_RECORD = join39(".himi", "run.json");
43419
+ STUDIO_DIR = join40(".himi", "studio");
43420
+ STUDIO_PAGE = join40(STUDIO_DIR, "index.html");
43421
+ STATE_FILE = join40(STUDIO_DIR, "state.js");
43422
+ MEMORY_FILE = join40(STUDIO_DIR, "memory.json");
43423
+ VALIDATE_RECORD = join40(".himi", "validate.json");
43424
+ PUSH_RECORD = join40(".himi", "last-push.json");
43425
+ EDGES_RECORD = join40(".himi", "test-edges.json");
43426
+ MAP_RECORD = join40(".himi", "test-map.json");
43427
+ RUN_RECORD = join40(".himi", "run.json");
43111
43428
  MAX_TIMELINE = 500;
43112
43429
  MAX_JOURNAL = 80;
43113
43430
  MAX_NAV_EDGES = 40;
43114
- readJson2 = (file) => {
43431
+ readJson3 = (file) => {
43115
43432
  try {
43116
- return JSON.parse(readFileSync44(file, "utf8"));
43433
+ return JSON.parse(readFileSync45(file, "utf8"));
43117
43434
  } catch {
43118
43435
  return null;
43119
43436
  }
@@ -43123,6 +43440,9 @@ var init_studio = __esm({
43123
43440
  text = (v) => typeof v === "string" && v ? v : null;
43124
43441
  historyUrl = (fileName) => `../history/${fileName}`;
43125
43442
  runTime = (runId) => runId.replace(/-[0-9a-f]+$/i, "");
43443
+ MAP_STATUSES = /* @__PURE__ */ new Set(["ok", "failed", "pending", "skipped"]);
43444
+ count = (v) => typeof v === "number" && Number.isFinite(v) && v > 0 ? Math.min(Math.round(v), 1e5) : 0;
43445
+ CHANGE_KINDS = /* @__PURE__ */ new Set(["text", "prop", "added", "removed", "token", "descriptor", "logic", "screen-added", "screen-removed"]);
43126
43446
  }
43127
43447
  });
43128
43448
 
@@ -43412,16 +43732,16 @@ function regressionDiff(base, live2, touched) {
43412
43732
  rows.push({ item: `screen:${id}`, live: resolveBundleSha(live2, l.bundle), base: resolveBundleSha(base, b.bundle), effect: `${id} rebinds to its ${base.releaseId} worker bundle` });
43413
43733
  }
43414
43734
  }
43415
- const scalar3 = (key2, item, what) => {
43735
+ const scalar4 = (key2, item, what) => {
43416
43736
  if (moves.has(item)) return;
43417
43737
  const l = live2[key2] ?? null;
43418
43738
  const b = base[key2] ?? null;
43419
43739
  if (l !== b) rows.push({ item, live: l, base: b, effect: `${what} reverts to ${base.releaseId}` });
43420
43740
  };
43421
- scalar3("tokensHash", "tokens", "design tokens");
43422
- scalar3("appConfigHash", "app-config", "app-config");
43423
- scalar3("iconHash", "icon", "the launcher icon");
43424
- scalar3("minBinaryVersion", "minBinaryVersion", "the minimum binary version");
43741
+ scalar4("tokensHash", "tokens", "design tokens");
43742
+ scalar4("appConfigHash", "app-config", "app-config");
43743
+ scalar4("iconHash", "icon", "the launcher icon");
43744
+ scalar4("minBinaryVersion", "minBinaryVersion", "the minimum binary version");
43425
43745
  for (const name of /* @__PURE__ */ new Set([...Object.keys(live2.assets ?? {}), ...Object.keys(base.assets ?? {})])) {
43426
43746
  const l = live2.assets?.[name]?.sha256 ?? null;
43427
43747
  const b = base.assets?.[name]?.sha256 ?? null;
@@ -43601,11 +43921,11 @@ __export(captcha_exports, {
43601
43921
  captchaContentInput: () => captchaContentInput,
43602
43922
  runCaptcha: () => runCaptcha
43603
43923
  });
43604
- import { readFileSync as readFileSync45 } from "node:fs";
43924
+ import { readFileSync as readFileSync46 } from "node:fs";
43605
43925
  import { resolve as resolve34 } from "node:path";
43606
43926
  function readObject(path2) {
43607
43927
  try {
43608
- return JSON.parse(readFileSync45(path2, "utf8"));
43928
+ return JSON.parse(readFileSync46(path2, "utf8"));
43609
43929
  } catch {
43610
43930
  return {};
43611
43931
  }
@@ -43749,11 +44069,11 @@ var init_push_setup_apple_credentials = __esm({
43749
44069
  });
43750
44070
 
43751
44071
  // src/push-onboarding.ts
43752
- import { existsSync as existsSync42, mkdirSync as mkdirSync19, readFileSync as readFileSync46, statSync as statSync9, writeFileSync as writeFileSync15, chmodSync as chmodSync2 } from "node:fs";
43753
- import { basename as basename11, dirname as dirname24, join as join40, resolve as resolve35 } from "node:path";
43754
- function readJson3(path2) {
44072
+ import { existsSync as existsSync43, mkdirSync as mkdirSync19, readFileSync as readFileSync47, statSync as statSync9, writeFileSync as writeFileSync15, chmodSync as chmodSync2 } from "node:fs";
44073
+ import { basename as basename11, dirname as dirname24, join as join41, resolve as resolve35 } from "node:path";
44074
+ function readJson4(path2) {
43755
44075
  try {
43756
- const parsed = JSON.parse(readFileSync46(path2, "utf8"));
44076
+ const parsed = JSON.parse(readFileSync47(path2, "utf8"));
43757
44077
  return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
43758
44078
  } catch {
43759
44079
  return {};
@@ -43761,9 +44081,9 @@ function readJson3(path2) {
43761
44081
  }
43762
44082
  function pushInputsFromContent(dir) {
43763
44083
  const root = resolve35(dir);
43764
- if (!existsSync42(join40(root, "himalaya.content.json"))) return { initialized: false };
43765
- const content = readJson3(join40(root, "himalaya.content.json"));
43766
- const provisioning = readJson3(join40(root, "provisioning.json"));
44084
+ if (!existsSync43(join41(root, "himalaya.content.json"))) return { initialized: false };
44085
+ const content = readJson4(join41(root, "himalaya.content.json"));
44086
+ const provisioning = readJson4(join41(root, "provisioning.json"));
43767
44087
  const native = provisioning.native ?? {};
43768
44088
  return {
43769
44089
  initialized: true,
@@ -43838,7 +44158,7 @@ function readKeyFile(path2) {
43838
44158
  }
43839
44159
  let body;
43840
44160
  try {
43841
- body = readFileSync46(path2, "utf8");
44161
+ body = readFileSync47(path2, "utf8");
43842
44162
  } catch {
43843
44163
  return { ok: false, code: "key_file_unreadable", reason: "cannot read the selected key file; check its path and file permissions" };
43844
44164
  }
@@ -43892,13 +44212,13 @@ function buildPushSetupBody(i, keys) {
43892
44212
  };
43893
44213
  }
43894
44214
  function readPushOnboarding(dir) {
43895
- const path2 = join40(resolve35(dir), PUSH_ONBOARDING_PATH);
43896
- if (!existsSync42(path2)) return { version: 1 };
43897
- const parsed = readJson3(path2);
44215
+ const path2 = join41(resolve35(dir), PUSH_ONBOARDING_PATH);
44216
+ if (!existsSync43(path2)) return { version: 1 };
44217
+ const parsed = readJson4(path2);
43898
44218
  return { version: 1, ...parsed };
43899
44219
  }
43900
44220
  function writeFile(dir, file) {
43901
- const path2 = join40(resolve35(dir), PUSH_ONBOARDING_PATH);
44221
+ const path2 = join41(resolve35(dir), PUSH_ONBOARDING_PATH);
43902
44222
  mkdirSync19(dirname24(path2), { recursive: true, mode: 448 });
43903
44223
  writeFileSync15(path2, JSON.stringify(file, null, 2) + "\n", { mode: 384 });
43904
44224
  try {
@@ -43927,8 +44247,8 @@ function recordPushDeferral(dir, d) {
43927
44247
  return push;
43928
44248
  }
43929
44249
  function clearPushDeferral(dir) {
43930
- const path2 = join40(resolve35(dir), PUSH_ONBOARDING_PATH);
43931
- if (!existsSync42(path2)) return;
44250
+ const path2 = join41(resolve35(dir), PUSH_ONBOARDING_PATH);
44251
+ if (!existsSync43(path2)) return;
43932
44252
  const { push: _dropped, ...rest } = readPushOnboarding(dir);
43933
44253
  writeFile(dir, { ...rest, version: 1 });
43934
44254
  }
@@ -44699,8 +45019,8 @@ __export(template_trim_exports, {
44699
45019
  routeMatcher: () => routeMatcher,
44700
45020
  runTemplateTrim: () => runTemplateTrim
44701
45021
  });
44702
- import { cpSync, existsSync as existsSync43, mkdirSync as mkdirSync20, readdirSync as readdirSync23, readFileSync as readFileSync47, renameSync as renameSync5, rmSync as rmSync8, statSync as statSync10, writeFileSync as writeFileSync16 } from "node:fs";
44703
- import { dirname as dirname25, join as join41 } from "node:path";
45022
+ import { cpSync, existsSync as existsSync44, mkdirSync as mkdirSync20, readdirSync as readdirSync24, readFileSync as readFileSync48, renameSync as renameSync5, rmSync as rmSync8, statSync as statSync10, writeFileSync as writeFileSync16 } from "node:fs";
45023
+ import { dirname as dirname25, join as join42 } from "node:path";
44704
45024
  function routeMatcher(pattern) {
44705
45025
  const detail = /^\^([A-Za-z0-9_]+)\(\?:\[_\/\]\(\.\+\)\)\?\$$/.exec(pattern);
44706
45026
  if (detail) {
@@ -44815,8 +45135,8 @@ function parseApp(dev, config) {
44815
45135
  while (!dev.startsWith("})", pos)) {
44816
45136
  ENTRY.lastIndex = pos;
44817
45137
  const m = ENTRY.exec(dev);
44818
- if (!m) throw new TrimRefusal(`dev/index.ts line ${lineOf(dev, pos)}: expected a \`"<screenId>": {\` entry of the SCREENS map`);
44819
- const id = jsonString(m[1], `dev/index.ts line ${lineOf(dev, pos)}: the SCREENS key`);
45138
+ if (!m) throw new TrimRefusal(`dev/index.ts line ${lineOf2(dev, pos)}: expected a \`"<screenId>": {\` entry of the SCREENS map`);
45139
+ const id = jsonString(m[1], `dev/index.ts line ${lineOf2(dev, pos)}: the SCREENS key`);
44820
45140
  const open = pos + m[0].length - 1;
44821
45141
  const close = objectEnd(dev, open);
44822
45142
  if (close < 0 || dev.slice(close, close + 2) !== ",\n") {
@@ -44845,9 +45165,9 @@ function parseApp(dev, config) {
44845
45165
  if (dev.startsWith("];", pos) || dev.startsWith("\n];", pos)) break;
44846
45166
  TENTRY.lastIndex = pos;
44847
45167
  const m = TENTRY.exec(dev);
44848
- if (!m) throw new TrimRefusal(`dev/index.ts line ${lineOf(dev, pos)}: expected a \`{ pattern: \u2026, display: \u2026, template: {\u2026} },\` entry of TEMPLATED`);
44849
- const pattern = jsonString(m[1], `dev/index.ts line ${lineOf(dev, pos)}: the TEMPLATED pattern`);
44850
- const display = jsonString(m[2], `dev/index.ts line ${lineOf(dev, pos)}: the TEMPLATED display`);
45168
+ if (!m) throw new TrimRefusal(`dev/index.ts line ${lineOf2(dev, pos)}: expected a \`{ pattern: \u2026, display: \u2026, template: {\u2026} },\` entry of TEMPLATED`);
45169
+ const pattern = jsonString(m[1], `dev/index.ts line ${lineOf2(dev, pos)}: the TEMPLATED pattern`);
45170
+ const display = jsonString(m[2], `dev/index.ts line ${lineOf2(dev, pos)}: the TEMPLATED display`);
44851
45171
  const lineEnd = dev.indexOf("\n", pos);
44852
45172
  const tk = dev.indexOf("template: {", pos);
44853
45173
  if (tk < 0 || tk > lineEnd) throw new TrimRefusal(`dev/index.ts: the templated route ${display} has no \`template: {\` on its first line`);
@@ -45319,7 +45639,7 @@ function planTrim(src, keepIds, links) {
45319
45639
  const homeModules = links === "drop" ? homeModulesPass(app, kept, edited) : void 0;
45320
45640
  let dev = "";
45321
45641
  let cursor = 0;
45322
- const cut = (from, to, replacement = "") => {
45642
+ const cut2 = (from, to, replacement = "") => {
45323
45643
  dev += app.dev.slice(cursor, from) + replacement;
45324
45644
  cursor = to;
45325
45645
  };
@@ -45328,15 +45648,15 @@ function planTrim(src, keepIds, links) {
45328
45648
  for (const r of app.routes) {
45329
45649
  if (templatedEmptied && r.kind === "templated") continue;
45330
45650
  if (!kept.has(r)) {
45331
- cut(r.lineStart, r.end);
45651
+ cut2(r.lineStart, r.end);
45332
45652
  continue;
45333
45653
  }
45334
45654
  const block = edited.get(r);
45335
45655
  let text5 = block ? serializeAt(block, app.dev, r.open) : void 0;
45336
45656
  if (r === app.home && homeModules?.text !== void 0) text5 = homeModules.text;
45337
- if (text5 !== void 0) cut(r.open, r.close, text5);
45657
+ if (text5 !== void 0) cut2(r.open, r.close, text5);
45338
45658
  }
45339
- if (templatedEmptied) cut(templatedEmptied[0], templatedEmptied[1], "\n");
45659
+ if (templatedEmptied) cut2(templatedEmptied[0], templatedEmptied[1], "\n");
45340
45660
  dev += app.dev.slice(cursor);
45341
45661
  let config = app.config;
45342
45662
  const edits = [];
@@ -45394,20 +45714,20 @@ function homeModulesPass(app, kept, edited) {
45394
45714
  return { text: patched.src, dropped: patched.droppedModules };
45395
45715
  }
45396
45716
  function listWorkers(dir) {
45397
- const root = join41(dir, "tier5-src");
45398
- if (!existsSync43(root)) return { dirs: [], files: {} };
45399
- const dirs = readdirSync23(root).filter((n) => statSync10(join41(root, n)).isDirectory()).sort();
45717
+ const root = join42(dir, "tier5-src");
45718
+ if (!existsSync44(root)) return { dirs: [], files: {} };
45719
+ const dirs = readdirSync24(root).filter((n) => statSync10(join42(root, n)).isDirectory()).sort();
45400
45720
  const files = {};
45401
45721
  const walk2 = (abs, rel) => {
45402
- for (const name of readdirSync23(abs)) {
45722
+ for (const name of readdirSync24(abs)) {
45403
45723
  if (name === "node_modules") continue;
45404
- const p = join41(abs, name);
45724
+ const p = join42(abs, name);
45405
45725
  const r = `${rel}/${name}`;
45406
45726
  if (statSync10(p).isDirectory()) walk2(p, r);
45407
- else if (/\.(?:[cm]?[jt]sx?|json)$/.test(name)) files[r] = readFileSync47(p, "utf8");
45727
+ else if (/\.(?:[cm]?[jt]sx?|json)$/.test(name)) files[r] = readFileSync48(p, "utf8");
45408
45728
  }
45409
45729
  };
45410
- for (const d of dirs) walk2(join41(root, d), `tier5-src/${d}`);
45730
+ for (const d of dirs) walk2(join42(root, d), `tier5-src/${d}`);
45411
45731
  return { dirs, files };
45412
45732
  }
45413
45733
  function csv(v) {
@@ -45430,16 +45750,16 @@ async function runTemplateTrim(req, deps) {
45430
45750
  links = req.links;
45431
45751
  }
45432
45752
  const dir = req.dir;
45433
- const devPath = join41(dir, "dev/index.ts");
45434
- const configPath = join41(dir, "config.ts");
45435
- if (!existsSync43(devPath) || !existsSync43(configPath)) {
45753
+ const devPath = join42(dir, "dev/index.ts");
45754
+ const configPath = join42(dir, "config.ts");
45755
+ if (!existsSync44(devPath) || !existsSync44(configPath)) {
45436
45756
  return refuse3(new TrimRefusal(`${dir} has no dev/index.ts + config.ts \u2014 point --content at an app scaffolded with \`himi init --template\``));
45437
45757
  }
45438
45758
  if (isTemplateSourceTree(dir)) {
45439
45759
  return refuse3(new TrimRefusal(`${dir} is a template source tree (its identity is still __HIMI_APP_*__ sentinels) \u2014 trim the app \`himi init --template\` scaffolds from it, not the template itself`));
45440
45760
  }
45441
- const dev = readFileSync47(devPath, "utf8");
45442
- const config = readFileSync47(configPath, "utf8");
45761
+ const dev = readFileSync48(devPath, "utf8");
45762
+ const config = readFileSync48(configPath, "utf8");
45443
45763
  const workers = listWorkers(dir);
45444
45764
  let plan;
45445
45765
  try {
@@ -45449,23 +45769,23 @@ async function runTemplateTrim(req, deps) {
45449
45769
  throw e;
45450
45770
  }
45451
45771
  if (req.dryRun) return { code: 0, result: { ok: true, dryRun: true, ...plan.report, validate: null } };
45452
- const hadHimi = existsSync43(join41(dir, ".himi"));
45453
- const backupRel = join41(".himi", "trim-backup", (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-"));
45454
- const backup = join41(dir, backupRel);
45772
+ const hadHimi = existsSync44(join42(dir, ".himi"));
45773
+ const backupRel = join42(".himi", "trim-backup", (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-"));
45774
+ const backup = join42(dir, backupRel);
45455
45775
  const originals = [[devPath, "dev/index.ts"], [configPath, "config.ts"]];
45456
45776
  const moved = [];
45457
45777
  const restore = () => {
45458
45778
  for (const [path2, rel] of originals) {
45459
- const original = readFileSync47(join41(backup, rel));
45460
- if (!existsSync43(path2) || !readFileSync47(path2).equals(original)) writeFileSync16(path2, original);
45779
+ const original = readFileSync48(join42(backup, rel));
45780
+ if (!existsSync44(path2) || !readFileSync48(path2).equals(original)) writeFileSync16(path2, original);
45461
45781
  }
45462
- for (const name of [...moved].reverse()) renameSync5(join41(backup, "tier5-src", name), join41(dir, "tier5-src", name));
45782
+ for (const name of [...moved].reverse()) renameSync5(join42(backup, "tier5-src", name), join42(dir, "tier5-src", name));
45463
45783
  moved.length = 0;
45464
45784
  };
45465
45785
  const apply = () => {
45466
45786
  const moveDir = deps.moveDir ?? renameSync5;
45467
45787
  for (const name of plan.removedWorkers) {
45468
- moveDir(join41(dir, "tier5-src", name), join41(backup, "tier5-src", name));
45788
+ moveDir(join42(dir, "tier5-src", name), join42(backup, "tier5-src", name));
45469
45789
  moved.push(name);
45470
45790
  }
45471
45791
  if (plan.dev !== dev) writeFileSync16(devPath, plan.dev);
@@ -45473,10 +45793,10 @@ async function runTemplateTrim(req, deps) {
45473
45793
  };
45474
45794
  const dropBackup = () => {
45475
45795
  rmSync8(backup, { recursive: true, force: true });
45476
- for (const d of [dirname25(backup), join41(dir, ".himi")]) {
45477
- if (d === join41(dir, ".himi") && hadHimi) break;
45796
+ for (const d of [dirname25(backup), join42(dir, ".himi")]) {
45797
+ if (d === join42(dir, ".himi") && hadHimi) break;
45478
45798
  try {
45479
- if (!readdirSync23(d).length) rmSync8(d, { recursive: true });
45799
+ if (!readdirSync24(d).length) rmSync8(d, { recursive: true });
45480
45800
  } catch {
45481
45801
  }
45482
45802
  }
@@ -45500,9 +45820,9 @@ async function runTemplateTrim(req, deps) {
45500
45820
  return { code: 1, result: { ok: false, error: `${error} (${message}); every file was restored`, rolledBack: true, ...base(validate4) } };
45501
45821
  };
45502
45822
  try {
45503
- mkdirSync20(join41(backup, "dev"), { recursive: true });
45504
- for (const [path2, rel] of originals) cpSync(path2, join41(backup, rel));
45505
- if (plan.removedWorkers.length) mkdirSync20(join41(backup, "tier5-src"), { recursive: true });
45823
+ mkdirSync20(join42(backup, "dev"), { recursive: true });
45824
+ for (const [path2, rel] of originals) cpSync(path2, join42(backup, rel));
45825
+ if (plan.removedWorkers.length) mkdirSync20(join42(backup, "tier5-src"), { recursive: true });
45506
45826
  } catch (e) {
45507
45827
  dropBackup();
45508
45828
  return { code: 1, result: { ok: false, error: `could not back up the app before trimming it (${e.message}); nothing was changed`, rolledBack: true, ...base() } };
@@ -45515,7 +45835,7 @@ async function runTemplateTrim(req, deps) {
45515
45835
  let failure;
45516
45836
  let validate3;
45517
45837
  try {
45518
- const written = parseApp(readFileSync47(devPath, "utf8"), readFileSync47(configPath, "utf8"));
45838
+ const written = parseApp(readFileSync48(devPath, "utf8"), readFileSync48(configPath, "utf8"));
45519
45839
  const leftovers = leftoverNavRefs(written.nav.value, plan.removedIds);
45520
45840
  if (leftovers.length) failure = { error: "navigation still names screens this trim removed", leftovers };
45521
45841
  else {
@@ -45619,7 +45939,7 @@ function summarize3(errors) {
45619
45939
  const shown2 = errors.slice(0, 5).map(one).join("; ");
45620
45940
  return errors.length > 5 ? `${shown2}; and ${errors.length - 5} more` : shown2;
45621
45941
  }
45622
- var TrimRefusal, LIT, ATOM, QUANT, CAP, SAFE_ROUTE_PATTERN, lineOf, isStatic, entryLabel, isNode3, asItems, NAV_SCREEN_KEYS, sorted, refuse3, plural;
45942
+ var TrimRefusal, LIT, ATOM, QUANT, CAP, SAFE_ROUTE_PATTERN, lineOf2, isStatic, entryLabel, isNode3, asItems, NAV_SCREEN_KEYS, sorted, refuse3, plural;
45623
45943
  var init_template_trim = __esm({
45624
45944
  "src/template-trim.ts"() {
45625
45945
  init_validate_descriptor();
@@ -45639,7 +45959,7 @@ var init_template_trim = __esm({
45639
45959
  SAFE_ROUTE_PATTERN = new RegExp(
45640
45960
  String.raw`^\^${LIT}(?:${CAP}${LIT}|\(\?:(?:${LIT}|\[(?:[^\]\\[]|\\.)+\])${CAP}\)\?)?\$$`
45641
45961
  );
45642
- lineOf = (src, at) => src.slice(0, at).split("\n").length;
45962
+ lineOf2 = (src, at) => src.slice(0, at).split("\n").length;
45643
45963
  isStatic = (id) => typeof id === "string" && id !== "" && !id.startsWith("$");
45644
45964
  entryLabel = (entry) => {
45645
45965
  const id = entry?.id;
@@ -46188,10 +46508,10 @@ function parseZipEntries(buf) {
46188
46508
  }
46189
46509
  }
46190
46510
  if (eocd < 0) throw new Error("not a zip archive (no end-of-central-directory record)");
46191
- const count = u16(eocd + 10);
46511
+ const count2 = u16(eocd + 10);
46192
46512
  let p = u32(eocd + 16);
46193
46513
  const out = [];
46194
- for (let i = 0; i < count; i++) {
46514
+ for (let i = 0; i < count2; i++) {
46195
46515
  if (p + 46 > buf.length || u32(p) !== ZIP_CENTRAL_SIG) throw new Error("corrupt zip central directory");
46196
46516
  const method = u16(p + 10);
46197
46517
  const crc = u32(p + 16);
@@ -46283,8 +46603,8 @@ __export(pack_exports, {
46283
46603
  });
46284
46604
  import { createHash as createHash22 } from "node:crypto";
46285
46605
  import { deflateRawSync, inflateRawSync } from "node:zlib";
46286
- import { existsSync as existsSync44, mkdirSync as mkdirSync21, readFileSync as readFileSync48, readdirSync as readdirSync24, rmSync as rmSync9, statSync as statSync11, writeFileSync as writeFileSync17 } from "node:fs";
46287
- import { dirname as dirname26, join as join42, resolve as resolve37 } from "node:path";
46606
+ import { existsSync as existsSync45, mkdirSync as mkdirSync21, readFileSync as readFileSync49, readdirSync as readdirSync25, rmSync as rmSync9, statSync as statSync11, writeFileSync as writeFileSync17 } from "node:fs";
46607
+ import { dirname as dirname26, join as join43, resolve as resolve37 } from "node:path";
46288
46608
  function writeZip(entries) {
46289
46609
  const sorted2 = entries.slice().sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
46290
46610
  const total = sorted2.reduce((n, e) => n + e.bytes.length, 0);
@@ -46378,13 +46698,13 @@ function inflateEntry(buf, entry) {
46378
46698
  return bytes2;
46379
46699
  }
46380
46700
  function readIf(path2) {
46381
- return existsSync44(path2) && statSync11(path2).isFile() ? readFileSync48(path2) : null;
46701
+ return existsSync45(path2) && statSync11(path2).isFile() ? readFileSync49(path2) : null;
46382
46702
  }
46383
46703
  function listDir(dir) {
46384
- return existsSync44(dir) ? readdirSync24(dir).filter((f) => statSync11(join42(dir, f)).isFile()) : [];
46704
+ return existsSync45(dir) ? readdirSync25(dir).filter((f) => statSync11(join43(dir, f)).isFile()) : [];
46385
46705
  }
46386
46706
  function gatherReleaseEntries(appDir2) {
46387
- const manifestPath = join42(appDir2, HIMI_PACKAGE_MANIFEST_PATH);
46707
+ const manifestPath = join43(appDir2, HIMI_PACKAGE_MANIFEST_PATH);
46388
46708
  const manifestBytes = readIf(manifestPath);
46389
46709
  if (!manifestBytes) throw new Error(`no ${HIMI_PACKAGE_MANIFEST_PATH} in ${appDir2} \u2014 build a release there first`);
46390
46710
  const manifest = JSON.parse(manifestBytes.toString("utf8"));
@@ -46408,7 +46728,7 @@ function gatherReleaseEntries(appDir2) {
46408
46728
  }
46409
46729
  const missing = [];
46410
46730
  for (const rel of wanted) {
46411
- const bytes2 = readIf(join42(appDir2, rel));
46731
+ const bytes2 = readIf(join43(appDir2, rel));
46412
46732
  if (bytes2) entries.push({ path: rel, bytes: bytes2 });
46413
46733
  else missing.push(rel);
46414
46734
  }
@@ -46419,12 +46739,12 @@ function gatherReleaseEntries(appDir2) {
46419
46739
  );
46420
46740
  }
46421
46741
  for (const name of SINGLETONS) {
46422
- const bytes2 = readIf(join42(appDir2, name));
46742
+ const bytes2 = readIf(join43(appDir2, name));
46423
46743
  if (bytes2) entries.push({ path: name, bytes: bytes2 });
46424
46744
  }
46425
46745
  const skipped = [];
46426
46746
  for (const sub of ["screens", "bundles", "assets", "widgets"]) {
46427
- for (const f of listDir(join42(appDir2, sub))) {
46747
+ for (const f of listDir(join43(appDir2, sub))) {
46428
46748
  if (!wanted.has(`${sub}/${f}`)) skipped.push(`${sub}/${f}`);
46429
46749
  }
46430
46750
  }
@@ -46457,18 +46777,18 @@ function buildEntries(appDir2, app, opts) {
46457
46777
  return { all, header, manifest, skipped };
46458
46778
  }
46459
46779
  function packRelease(releaseRoot, app, out, opts = {}) {
46460
- const appDir2 = join42(resolve37(releaseRoot), app);
46780
+ const appDir2 = join43(resolve37(releaseRoot), app);
46461
46781
  const { all, header, skipped } = buildEntries(appDir2, app, opts);
46462
46782
  const buf = writeZip(all);
46463
- const isDir2 = existsSync44(out) && statSync11(out).isDirectory();
46464
- const outFile = isDir2 ? join42(resolve37(out), packageFileName(app, header.releaseId)) : resolve37(out);
46783
+ const isDir2 = existsSync45(out) && statSync11(out).isDirectory();
46784
+ const outFile = isDir2 ? join43(resolve37(out), packageFileName(app, header.releaseId)) : resolve37(out);
46465
46785
  mkdirSync21(dirname26(outFile), { recursive: true });
46466
46786
  writeFileSync17(outFile, buf);
46467
46787
  return { app, releaseId: header.releaseId, header, out: outFile, bytes: buf.length, entryCount: all.length, skipped };
46468
46788
  }
46469
46789
  function resolvePackageDirDest(outDir, app, releaseId) {
46470
46790
  const packagedApp = (dir) => {
46471
- const bytes2 = readIf(join42(dir, HIMI_PACKAGE_HEADER_PATH));
46791
+ const bytes2 = readIf(join43(dir, HIMI_PACKAGE_HEADER_PATH));
46472
46792
  if (!bytes2) return void 0;
46473
46793
  try {
46474
46794
  return JSON.parse(bytes2.toString("utf8")).app;
@@ -46477,15 +46797,15 @@ function resolvePackageDirDest(outDir, app, releaseId) {
46477
46797
  }
46478
46798
  };
46479
46799
  const replaceable = (dir) => {
46480
- if (!existsSync44(dir)) return true;
46800
+ if (!existsSync45(dir)) return true;
46481
46801
  if (!statSync11(dir).isDirectory()) return false;
46482
- return readdirSync24(dir).length === 0 || packagedApp(dir) === app;
46802
+ return readdirSync25(dir).length === 0 || packagedApp(dir) === app;
46483
46803
  };
46484
- if (existsSync44(outDir) && !statSync11(outDir).isDirectory()) {
46804
+ if (existsSync45(outDir) && !statSync11(outDir).isDirectory()) {
46485
46805
  throw new Error(`refusing to write a package tree over ${outDir} \u2014 it is a file, not a directory`);
46486
46806
  }
46487
46807
  if (replaceable(outDir)) return outDir;
46488
- const child = join42(outDir, packageDirName(app, releaseId));
46808
+ const child = join43(outDir, packageDirName(app, releaseId));
46489
46809
  if (!replaceable(child)) {
46490
46810
  throw new Error(
46491
46811
  `refusing to replace ${child}: it is not empty and is not a ${app} package (no ${HIMI_PACKAGE_HEADER_PATH}). Pass --out to a new or empty directory.`
@@ -46499,13 +46819,13 @@ function packReleaseToDir(releaseRoot, app, outDir, opts = {}) {
46499
46819
  "a package carrying the app's source has no --format dir form: readers that predate the source entry drop it before hashing a directory and refuse the package as tampered. Pack it as a .himi archive."
46500
46820
  );
46501
46821
  }
46502
- const appDir2 = join42(resolve37(releaseRoot), app);
46822
+ const appDir2 = join43(resolve37(releaseRoot), app);
46503
46823
  const { all, header, skipped } = buildEntries(appDir2, app, opts);
46504
46824
  const dest = resolvePackageDirDest(resolve37(outDir), app, header.releaseId);
46505
46825
  rmSync9(dest, { recursive: true, force: true });
46506
46826
  let bytes2 = 0;
46507
46827
  for (const entry of all) {
46508
- const target = join42(dest, entry.path);
46828
+ const target = join43(dest, entry.path);
46509
46829
  mkdirSync21(dirname26(target), { recursive: true });
46510
46830
  writeFileSync17(target, entry.bytes);
46511
46831
  bytes2 += entry.bytes.length;
@@ -46528,21 +46848,21 @@ function openPackageBuffer(buf) {
46528
46848
  return openFromEntries(readZip(buf), { requireHeader: true });
46529
46849
  }
46530
46850
  function openPackageFile(file) {
46531
- return openPackageBuffer(readFileSync48(file));
46851
+ return openPackageBuffer(readFileSync49(file));
46532
46852
  }
46533
46853
  function openPackageDir(dir) {
46534
46854
  const root = resolve37(dir);
46535
46855
  const entries = /* @__PURE__ */ new Map();
46536
46856
  const walk2 = (rel) => {
46537
- const abs = rel ? join42(root, rel) : root;
46538
- for (const name of readdirSync24(abs)) {
46857
+ const abs = rel ? join43(root, rel) : root;
46858
+ for (const name of readdirSync25(abs)) {
46539
46859
  const childRel = rel ? `${rel}/${name}` : name;
46540
- const childAbs = join42(abs, name);
46860
+ const childAbs = join43(abs, name);
46541
46861
  if (statSync11(childAbs).isDirectory()) walk2(childRel);
46542
- else if (isSafeEntryName(childRel)) entries.set(childRel, readFileSync48(childAbs));
46862
+ else if (isSafeEntryName(childRel)) entries.set(childRel, readFileSync49(childAbs));
46543
46863
  }
46544
46864
  };
46545
- if (!existsSync44(root)) throw new Error(`no such package directory: ${root}`);
46865
+ if (!existsSync45(root)) throw new Error(`no such package directory: ${root}`);
46546
46866
  walk2("");
46547
46867
  const manifestBytes = entries.get(HIMI_PACKAGE_MANIFEST_PATH);
46548
46868
  if (manifestBytes) {
@@ -46584,10 +46904,10 @@ function unpackTo(buf, dest, appFallback) {
46584
46904
  if (!app) throw new Error("package declares no app id (no himi-package.json and no manifest.app)");
46585
46905
  if (!isSafeAppId(app)) throw new Error(`package declares an unsafe app id ${JSON.stringify(app)} \u2014 refusing to unpack it`);
46586
46906
  const root = resolve37(dest);
46587
- const appDir2 = join42(root, app);
46907
+ const appDir2 = join43(root, app);
46588
46908
  rmSync9(appDir2, { recursive: true, force: true });
46589
46909
  for (const [rel, bytes2] of entries) {
46590
- const target = join42(appDir2, rel);
46910
+ const target = join43(appDir2, rel);
46591
46911
  mkdirSync21(dirname26(target), { recursive: true });
46592
46912
  writeFileSync17(target, bytes2);
46593
46913
  }
@@ -46623,20 +46943,20 @@ __export(app_source_exports, {
46623
46943
  writeConflicts: () => writeConflicts,
46624
46944
  zipAppSource: () => zipAppSource
46625
46945
  });
46626
- import { readdirSync as readdirSync25, lstatSync as lstatSync8, readFileSync as readFileSync49, mkdirSync as mkdirSync22, writeFileSync as writeFileSync18, existsSync as existsSync45, rmSync as rmSync10, rmdirSync as rmdirSync2, unlinkSync as unlinkSync3 } from "node:fs";
46627
- import { join as join43, resolve as resolve38, sep as sep10 } from "node:path";
46946
+ import { readdirSync as readdirSync26, lstatSync as lstatSync8, readFileSync as readFileSync50, mkdirSync as mkdirSync22, writeFileSync as writeFileSync18, existsSync as existsSync46, rmSync as rmSync10, rmdirSync as rmdirSync2, unlinkSync as unlinkSync3 } from "node:fs";
46947
+ import { join as join44, resolve as resolve38, sep as sep10 } from "node:path";
46628
46948
  import { deflateRawSync as deflateRawSync2 } from "node:zlib";
46629
46949
  function walkSourceTree(dir, file, skip) {
46630
46950
  const walk2 = (relDir) => {
46631
- for (const name of readdirSync25(join43(dir, relDir))) {
46632
- const rel = relDir ? join43(relDir, name) : name;
46633
- const st = lstatSync8(join43(dir, rel));
46951
+ for (const name of readdirSync26(join44(dir, relDir))) {
46952
+ const rel = relDir ? join44(relDir, name) : name;
46953
+ const st = lstatSync8(join44(dir, rel));
46634
46954
  if (st.isSymbolicLink()) {
46635
46955
  skip(rel);
46636
46956
  continue;
46637
46957
  }
46638
46958
  if (st.isDirectory()) {
46639
- if (isDeniedSourceDir(name, relDir) || existsSync45(join43(dir, rel, PACKAGE_DIR_MARKER))) skip(rel);
46959
+ if (isDeniedSourceDir(name, relDir) || existsSync46(join44(dir, rel, PACKAGE_DIR_MARKER))) skip(rel);
46640
46960
  else walk2(rel);
46641
46961
  continue;
46642
46962
  }
@@ -46654,7 +46974,7 @@ function collectAppSource(dir) {
46654
46974
  const skipped = [];
46655
46975
  walkSourceTree(
46656
46976
  dir,
46657
- (archivePath, hostRel) => entries.push({ path: archivePath, bytes: readFileSync49(join43(dir, hostRel)) }),
46977
+ (archivePath, hostRel) => entries.push({ path: archivePath, bytes: readFileSync50(join44(dir, hostRel)) }),
46658
46978
  (hostRel) => skipped.push(hostRel)
46659
46979
  );
46660
46980
  entries.sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
@@ -46708,13 +47028,13 @@ function writeAppSource(entries, destDir) {
46708
47028
  const segments = entry.path.split("/");
46709
47029
  let dir = root;
46710
47030
  for (const segment of segments.slice(0, -1)) {
46711
- dir = join43(dir, segment);
47031
+ dir = join44(dir, segment);
46712
47032
  const kind = pathKind(dir);
46713
47033
  if (kind?.isSymbolicLink()) unlinkSync3(dir);
46714
47034
  else if (kind) continue;
46715
47035
  mkdirSync22(dir);
46716
47036
  }
46717
- const target = join43(root, ...segments);
47037
+ const target = join44(root, ...segments);
46718
47038
  if (pathKind(target)?.isSymbolicLink()) unlinkSync3(target);
46719
47039
  writeFileSync18(target, entry.bytes);
46720
47040
  written.push(entry.path);
@@ -46722,7 +47042,7 @@ function writeAppSource(entries, destDir) {
46722
47042
  return written;
46723
47043
  }
46724
47044
  function staleSourcePaths(entries, destDir) {
46725
- if (!existsSync45(destDir)) return [];
47045
+ if (!existsSync46(destDir)) return [];
46726
47046
  const archived = new Set(entries.map((e) => e.path));
46727
47047
  const stale = [];
46728
47048
  walkSourceTree(destDir, (archivePath) => {
@@ -46749,7 +47069,7 @@ function writeConflicts(entries, destDir) {
46749
47069
  let dir = root;
46750
47070
  let stop = false;
46751
47071
  for (const [i, segment] of segments.slice(0, -1).entries()) {
46752
- dir = join43(dir, segment);
47072
+ dir = join44(dir, segment);
46753
47073
  const kind2 = pathKind(dir);
46754
47074
  if (!kind2 || kind2.isSymbolicLink()) {
46755
47075
  stop = true;
@@ -46761,7 +47081,7 @@ function writeConflicts(entries, destDir) {
46761
47081
  break;
46762
47082
  }
46763
47083
  if (stop) continue;
46764
- const kind = pathKind(join43(root, ...segments));
47084
+ const kind = pathKind(join44(root, ...segments));
46765
47085
  if (kind && !kind.isSymbolicLink() && kind.isDirectory()) {
46766
47086
  say(entry.path, "is a directory here, but the release has a file at that path");
46767
47087
  }
@@ -46771,22 +47091,22 @@ function writeConflicts(entries, destDir) {
46771
47091
  function removeStaleSource(entries, destDir) {
46772
47092
  const root = resolve38(destDir);
46773
47093
  const removed = staleSourcePaths(entries, root);
46774
- for (const rel of removed) rmSync10(join43(root, ...rel.split("/")), { force: true });
47094
+ for (const rel of removed) rmSync10(join44(root, ...rel.split("/")), { force: true });
46775
47095
  const needed = /* @__PURE__ */ new Set();
46776
47096
  for (const entry of entries) {
46777
47097
  if (isDeniedSourcePath(entry.path)) continue;
46778
47098
  const segments = entry.path.split("/");
46779
47099
  for (let i = 1; i < segments.length; i++) needed.add(segments.slice(0, i).join("/"));
46780
47100
  }
46781
- if (existsSync45(root)) pruneEmptyDirs(root, "", needed, removed);
47101
+ if (existsSync46(root)) pruneEmptyDirs(root, "", needed, removed);
46782
47102
  return removed.sort();
46783
47103
  }
46784
47104
  function pruneEmptyDirs(root, rel, needed, removed) {
46785
47105
  let empty = true;
46786
- for (const name of readdirSync25(rel ? join43(root, rel) : root)) {
46787
- const childRel = rel ? join43(rel, name) : name;
47106
+ for (const name of readdirSync26(rel ? join44(root, rel) : root)) {
47107
+ const childRel = rel ? join44(rel, name) : name;
46788
47108
  const posix4 = childRel.split(sep10).join("/");
46789
- const kind = lstatSync8(join43(root, childRel));
47109
+ const kind = lstatSync8(join44(root, childRel));
46790
47110
  if (kind.isSymbolicLink() || !kind.isDirectory() || isDeniedSourceDir(name, rel)) {
46791
47111
  empty = false;
46792
47112
  continue;
@@ -46796,7 +47116,7 @@ function pruneEmptyDirs(root, rel, needed, removed) {
46796
47116
  empty = false;
46797
47117
  continue;
46798
47118
  }
46799
- rmdirSync2(join43(root, childRel));
47119
+ rmdirSync2(join44(root, childRel));
46800
47120
  removed.push(`${posix4}/`);
46801
47121
  }
46802
47122
  return empty;
@@ -46851,13 +47171,13 @@ __export(eject_exports, {
46851
47171
  EJECTABLE: () => EJECTABLE,
46852
47172
  ejectModule: () => ejectModule
46853
47173
  });
46854
- import { existsSync as existsSync46, mkdirSync as mkdirSync23, readFileSync as readFileSync50, writeFileSync as writeFileSync19 } from "node:fs";
46855
- import { dirname as dirname27, join as join44, relative as relative8, resolve as resolve39, sep as sep11 } from "node:path";
47174
+ import { existsSync as existsSync47, mkdirSync as mkdirSync23, readFileSync as readFileSync51, writeFileSync as writeFileSync19 } from "node:fs";
47175
+ import { dirname as dirname27, join as join45, relative as relative8, resolve as resolve39, sep as sep11 } from "node:path";
46856
47176
  function locate(repoRootOrCwd, mod) {
46857
47177
  const inRepo = resolve39(repoRootOrCwd, `stdlib/flows/${mod}/src/index.ts`);
46858
- if (existsSync46(inRepo)) return { path: inRepo, form: "source" };
47178
+ if (existsSync47(inRepo)) return { path: inRepo, form: "source" };
46859
47179
  const dist = resolve39(repoRootOrCwd, `node_modules/@wix/himalaya/dist/${mod}.mjs`);
46860
- if (existsSync46(dist)) return { path: dist, form: "bundle" };
47180
+ if (existsSync47(dist)) return { path: dist, form: "bundle" };
46861
47181
  return null;
46862
47182
  }
46863
47183
  function specifiersFor(mod) {
@@ -46881,7 +47201,7 @@ function ejectModule(opts) {
46881
47201
  };
46882
47202
  }
46883
47203
  const ext = found.form === "source" ? "ts" : "mjs";
46884
- const target = join44(contentDir2, "tier5-src", "_ejected", `${mod}.${ext}`);
47204
+ const target = join45(contentDir2, "tier5-src", "_ejected", `${mod}.${ext}`);
46885
47205
  mkdirSync23(dirname27(target), { recursive: true });
46886
47206
  const banner = `// EJECTED from @wix/himalaya/${mod}. This app owns this file now.
46887
47207
  //
@@ -46895,15 +47215,15 @@ function ejectModule(opts) {
46895
47215
  // gate still applies. Ejecting buys layout and flow, never the floor.
46896
47216
 
46897
47217
  `;
46898
- const normalized = readFileSync50(found.path, "utf8").replace(
47218
+ const normalized = readFileSync51(found.path, "utf8").replace(
46899
47219
  /(["'])@himalaya\/([a-z0-9-]+(?:\/[a-z0-9-]+)?)\1/g,
46900
47220
  (_m, q, sub) => `${q}@wix/himalaya/${sub}${q}`
46901
47221
  );
46902
47222
  writeFileSync19(target, banner + normalized);
46903
47223
  const rewired = [];
46904
47224
  for (const f of files) {
46905
- if (!existsSync46(f)) continue;
46906
- const before = readFileSync50(f, "utf8");
47225
+ if (!existsSync47(f)) continue;
47226
+ const before = readFileSync51(f, "utf8");
46907
47227
  let after = before;
46908
47228
  let rel = relative8(dirname27(f), target).split(sep11).join("/").replace(/\.tsx?$/, ".js");
46909
47229
  if (!rel.startsWith(".")) rel = `./${rel}`;
@@ -46935,9 +47255,96 @@ var init_eject = __esm({
46935
47255
  }
46936
47256
  });
46937
47257
 
47258
+ // src/app-map.ts
47259
+ function buildAppMap(input) {
47260
+ const declared = [...new Set(input.declared)].slice(0, MAX_SCREENS);
47261
+ const known = new Set(declared);
47262
+ let entries = [...new Set(input.entries)].filter((id) => known.has(id));
47263
+ if (!entries.length && declared.length) entries = [declared[0]];
47264
+ const entrySet = new Set(entries);
47265
+ const all = [];
47266
+ const broken = [];
47267
+ const seen = /* @__PURE__ */ new Set();
47268
+ const crawled = /* @__PURE__ */ new Map();
47269
+ const unfollowed = /* @__PURE__ */ new Map();
47270
+ for (const r of input.results) {
47271
+ crawled.set(r.screen, (crawled.get(r.screen) ?? false) || r.failed);
47272
+ if (r.navUnfollowed) unfollowed.set(r.screen, Math.max(unfollowed.get(r.screen) ?? 0, r.navUnfollowed));
47273
+ for (const e of r.navEdges ?? []) {
47274
+ const to = known.has(e.target) ? e.target : baseOf(e.target, input.linkable, known);
47275
+ if (!to) {
47276
+ const key3 = `${r.screen}>!${e.target}`;
47277
+ if (input.linkable?.size && !resolvesToLinkable(e.target, input.linkable) && !seen.has(key3)) {
47278
+ seen.add(key3);
47279
+ broken.push({ from: r.screen, to: e.target, via: e.via });
47280
+ }
47281
+ continue;
47282
+ }
47283
+ const key2 = `${r.screen}>${to}`;
47284
+ if (seen.has(key2)) continue;
47285
+ seen.add(key2);
47286
+ all.push({ from: r.screen, to, via: e.via });
47287
+ }
47288
+ }
47289
+ const reached = new Set(entries);
47290
+ const out = /* @__PURE__ */ new Map();
47291
+ for (const e of all) {
47292
+ const next = out.get(e.from);
47293
+ if (next) next.push(e.to);
47294
+ else out.set(e.from, [e.to]);
47295
+ }
47296
+ for (const queue = [...entries]; queue.length; ) {
47297
+ for (const next of out.get(queue.shift()) ?? []) {
47298
+ if (reached.has(next)) continue;
47299
+ reached.add(next);
47300
+ queue.push(next);
47301
+ }
47302
+ }
47303
+ const screens = declared.map((id) => ({
47304
+ id,
47305
+ entry: entrySet.has(id),
47306
+ reached: reached.has(id),
47307
+ status: crawled.has(id) ? crawled.get(id) ? "failed" : "ok" : input.done ? "skipped" : "pending",
47308
+ unfollowed: unfollowed.get(id) ?? 0
47309
+ }));
47310
+ const totalUnfollowed = [...unfollowed.values()].reduce((a, b) => a + b, 0);
47311
+ return {
47312
+ screens,
47313
+ edges: all.slice(0, MAX_EDGES),
47314
+ broken,
47315
+ unreached: declared.filter((id) => !reached.has(id)),
47316
+ unfollowed: totalUnfollowed,
47317
+ edgesDropped: Math.max(0, all.length - MAX_EDGES),
47318
+ done: input.done
47319
+ };
47320
+ }
47321
+ function resolvesToLinkable(target, linkable) {
47322
+ if (linkable.has(target)) return true;
47323
+ for (const base of linkable) {
47324
+ if (target.length > base.length + 1 && target.startsWith(base) && (target[base.length] === "_" || target[base.length] === "/")) return true;
47325
+ }
47326
+ return false;
47327
+ }
47328
+ function baseOf(target, linkable, known) {
47329
+ let best = null;
47330
+ for (const base of linkable ?? []) {
47331
+ if (!known.has(base) || target.length <= base.length + 1 || !target.startsWith(base)) continue;
47332
+ const sep15 = target[base.length];
47333
+ if ((sep15 === "_" || sep15 === "/") && (!best || base.length > best.length)) best = base;
47334
+ }
47335
+ return best;
47336
+ }
47337
+ var MAX_SCREENS, MAX_EDGES;
47338
+ var init_app_map = __esm({
47339
+ "src/app-map.ts"() {
47340
+ MAX_SCREENS = 300;
47341
+ MAX_EDGES = 600;
47342
+ }
47343
+ });
47344
+
46938
47345
  // src/wix-writes.ts
46939
- import { existsSync as existsSync47, readFileSync as readFileSync51 } from "node:fs";
46940
- import { join as join45 } from "node:path";
47346
+ import { existsSync as existsSync48, readFileSync as readFileSync52 } from "node:fs";
47347
+ import { join as join46 } from "node:path";
46941
47348
  function isProvenRead(method, path2) {
46942
47349
  if (/[#\x00-\x1F\x7F]/.test(path2)) return false;
46943
47350
  const verb = method.toUpperCase();
@@ -47018,11 +47425,11 @@ function parseDataAccess(text5, from) {
47018
47425
  }
47019
47426
  function readDataAccess(dir) {
47020
47427
  for (const name of ["SPEC.md", "MOBILE-UX.md"]) {
47021
- const p = join45(dir, name);
47022
- if (!existsSync47(p)) continue;
47428
+ const p = join46(dir, name);
47429
+ if (!existsSync48(p)) continue;
47023
47430
  let text5;
47024
47431
  try {
47025
- text5 = readFileSync51(p, "utf8");
47432
+ text5 = readFileSync52(p, "utf8");
47026
47433
  } catch {
47027
47434
  continue;
47028
47435
  }
@@ -47129,14 +47536,14 @@ __export(test_exports, {
47129
47536
  scopeDescriptors: () => scopeDescriptors,
47130
47537
  writeRecordedMocks: () => writeRecordedMocks
47131
47538
  });
47132
- import { existsSync as existsSync48, mkdirSync as mkdirSync24, readFileSync as readFileSync52, writeFileSync as writeFileSync20 } from "node:fs";
47133
- import { join as join46, resolve as resolve40 } from "node:path";
47539
+ import { existsSync as existsSync49, mkdirSync as mkdirSync24, readFileSync as readFileSync53, writeFileSync as writeFileSync20 } from "node:fs";
47540
+ import { join as join47, resolve as resolve40 } from "node:path";
47134
47541
  function readTestConfig(dir) {
47135
47542
  const p = resolve40(dir, TEST_CONFIG_FILE);
47136
- if (!existsSync48(p)) return {};
47543
+ if (!existsSync49(p)) return {};
47137
47544
  let parsed;
47138
47545
  try {
47139
- parsed = JSON.parse(readFileSync52(p, "utf8"));
47546
+ parsed = JSON.parse(readFileSync53(p, "utf8"));
47140
47547
  } catch (e) {
47141
47548
  throw new Error(`${TEST_CONFIG_FILE} is not valid JSON \u2014 ${e.message}`);
47142
47549
  }
@@ -47183,7 +47590,7 @@ function readTestConfig(dir) {
47183
47590
  function readNetRules(path2) {
47184
47591
  let parsed;
47185
47592
  try {
47186
- parsed = JSON.parse(readFileSync52(path2, "utf8"));
47593
+ parsed = JSON.parse(readFileSync53(path2, "utf8"));
47187
47594
  } catch (e) {
47188
47595
  throw new Error(`--net ${path2} could not be read as JSON \u2014 ${e.message}`);
47189
47596
  }
@@ -47204,7 +47611,7 @@ function readNetRules(path2) {
47204
47611
  function defaultNetRules(dir) {
47205
47612
  for (const rel of ["dev/net-mocks.json", "tests/net-mocks.json"]) {
47206
47613
  const p = resolve40(dir, rel);
47207
- if (existsSync48(p)) {
47614
+ if (existsSync49(p)) {
47208
47615
  const rules = readNetRules(p);
47209
47616
  if (rules.length) return { rules, from: rel };
47210
47617
  }
@@ -47220,13 +47627,13 @@ function readCannedResponses() {
47220
47627
  ];
47221
47628
  for (const url of candidates) {
47222
47629
  try {
47223
- const parsed = JSON.parse(readFileSync52(url, "utf8"));
47630
+ const parsed = JSON.parse(readFileSync53(url, "utf8"));
47224
47631
  const byKey = {};
47225
47632
  for (const [k, v] of Object.entries(parsed)) {
47226
47633
  if (!k.startsWith("//")) byKey[k] = v;
47227
47634
  }
47228
- const count = Object.keys(byKey).length;
47229
- if (count) return { byKey, count, from: url.pathname.includes("web-renderer") ? "in-repo fixture" : "bundled fixture" };
47635
+ const count2 = Object.keys(byKey).length;
47636
+ if (count2) return { byKey, count: count2, from: url.pathname.includes("web-renderer") ? "in-repo fixture" : "bundled fixture" };
47230
47637
  } catch {
47231
47638
  }
47232
47639
  }
@@ -47310,8 +47717,8 @@ function writeRecordedMocks(opts) {
47310
47717
  mkdir: (p) => mkdirSync24(p, { recursive: true }),
47311
47718
  write: (p, s) => writeFileSync20(p, s)
47312
47719
  };
47313
- fs.mkdir(join46(opts.dir, "dev"));
47314
- fs.write(join46(opts.dir, "dev", "net-mocks.json"), JSON.stringify(opts.mocks, null, 2) + "\n");
47720
+ fs.mkdir(join47(opts.dir, "dev"));
47721
+ fs.write(join47(opts.dir, "dev", "net-mocks.json"), JSON.stringify(opts.mocks, null, 2) + "\n");
47315
47722
  return { ok: true, rules, to: "dev/net-mocks.json" };
47316
47723
  }
47317
47724
  function scopeDescriptors(descriptors, requestedIds, invalidScreens) {
@@ -47348,6 +47755,15 @@ async function runShotsPass(opts) {
47348
47755
  }
47349
47756
  return { dir: opts.outDir, written };
47350
47757
  }
47758
+ function withCovered(rows, coveredBy) {
47759
+ const byScreen = new Map(rows.map((r) => [r.screen, r]));
47760
+ const out = [...rows];
47761
+ for (const [instance, representative] of coveredBy) {
47762
+ const r = byScreen.get(representative);
47763
+ if (r && !byScreen.has(instance)) out.push({ ...r, screen: instance, navEdges: [], navUnfollowed: 0 });
47764
+ }
47765
+ return out;
47766
+ }
47351
47767
  async function runCrawl(opts) {
47352
47768
  const { descriptors, deps } = opts;
47353
47769
  const localDbSchema = await localDbEntities(opts.dir);
@@ -47358,6 +47774,7 @@ async function runCrawl(opts) {
47358
47774
  const notCrawled = descriptors.filter((d) => (!only || only.has(d.screenId)) && invalid.has(d.screenId)).map((d) => ({ screen: d.screenId, why: "descriptor fails `himi validate` \u2014 fix that first, then this screen is crawled too" }));
47359
47775
  const requested = descriptors.filter((d) => (!only || only.has(d.screenId)) && !invalid.has(d.screenId));
47360
47776
  const collapsed = [];
47777
+ const coveredBy = /* @__PURE__ */ new Map();
47361
47778
  let targets = requested;
47362
47779
  if (!opts.allScreens) {
47363
47780
  const groups = /* @__PURE__ */ new Map();
@@ -47372,6 +47789,7 @@ async function runCrawl(opts) {
47372
47789
  const rep = list.find((d) => Object.keys(d.descriptor?.input ?? {}).length > 0) ?? list[0];
47373
47790
  targets.push(rep);
47374
47791
  if (list.length > 1) collapsed.push({ representative: rep.screenId, instances: list.length });
47792
+ for (const d of list) if (d.screenId !== rep.screenId) coveredBy.set(d.screenId, rep.screenId);
47375
47793
  }
47376
47794
  }
47377
47795
  const net = opts.offline ? null : opts.netPath ? { rules: readNetRules(resolve40(opts.netPath)), from: opts.netPath } : defaultNetRules(opts.dir);
@@ -47417,6 +47835,12 @@ async function runCrawl(opts) {
47417
47835
  const cov = baked.bundleJs && deps.startWorkerCoverage ? await deps.startWorkerCoverage() : null;
47418
47836
  const r = await deps.crawlScreen(args);
47419
47837
  results.push(r);
47838
+ if (opts.onScreen) {
47839
+ try {
47840
+ opts.onScreen(buildAppMap({ declared: [...knownScreenIds], entries: opts.mapEntries ?? opts.tabRoots ?? [], ...opts.linkableScreenIds?.size ? { linkable: opts.linkableScreenIds } : {}, results: withCovered(results.map(mapResult), coveredBy), done: false }));
47841
+ } catch {
47842
+ }
47843
+ }
47420
47844
  writes.push(...writesFromEffects(baked.screenId, r.effects));
47421
47845
  for (const journey of opts.config?.journeys?.filter((candidate) => candidate.screen === baked.screenId) ?? []) {
47422
47846
  if (!deps.crawlScreenJourney) {
@@ -47625,6 +48049,8 @@ async function runCrawl(opts) {
47625
48049
  ...bundleOf.get(r.screen)?.bundleMapWhy ? { sourcemap: bundleOf.get(r.screen).bundleMapWhy } : {}
47626
48050
  })),
47627
48051
  ...edgesFollowed.length ? { edges: edgesFollowed } : {},
48052
+ // Final statuses come from the whole run's failures (hostile, deep, journeys), not one pass.
48053
+ navGraph: buildAppMap({ declared: [...knownScreenIds], entries: opts.mapEntries ?? opts.tabRoots ?? [], ...opts.linkableScreenIds?.size ? { linkable: opts.linkableScreenIds } : {}, results: withCovered(results.map((r) => ({ ...mapResult(r), failed: failedScreens.has(r.screen) })), coveredBy), done: true }),
47628
48054
  ...edgesCapped.length ? { edgesCapped } : {},
47629
48055
  ...retired.length ? { retired } : {},
47630
48056
  ...opts.deep && opts.deep >= 2 ? { deep: { depth: opts.deep, ...deepTotals } } : {},
@@ -47777,13 +48203,15 @@ function formatReport(r) {
47777
48203
  }
47778
48204
  return out.join("\n");
47779
48205
  }
47780
- var TEST_CONFIG_FILE, triggerLabel;
48206
+ var TEST_CONFIG_FILE, mapResult, triggerLabel;
47781
48207
  var init_test = __esm({
47782
48208
  "src/test.ts"() {
48209
+ init_app_map();
47783
48210
  init_wix_gateway_rules();
47784
48211
  init_crawl_layers();
47785
48212
  init_wix_writes();
47786
48213
  TEST_CONFIG_FILE = "himi-test.json";
48214
+ mapResult = (r) => ({ screen: r.screen, navEdges: r.navEdges ?? [], navUnfollowed: r.navUnfollowed ?? 0, failed: (r.findings ?? []).some((x) => x.severity === "error") });
47787
48215
  triggerLabel = (f) => (f.trigger === "boot" ? "on appear" : `${f.trigger.key} ${f.trigger.tap ?? "(no id)"} \u2192 ${f.trigger.action}`) + (f.axis ? ` [${f.axis}]` : "");
47788
48216
  }
47789
48217
  });
@@ -47793,14 +48221,14 @@ var mobile_ux_lint_exports = {};
47793
48221
  __export(mobile_ux_lint_exports, {
47794
48222
  mobileUxIssues: () => mobileUxIssues
47795
48223
  });
47796
- import { existsSync as existsSync49, readFileSync as readFileSync53 } from "node:fs";
47797
- import { join as join47 } from "node:path";
48224
+ import { existsSync as existsSync50, readFileSync as readFileSync54 } from "node:fs";
48225
+ import { join as join48 } from "node:path";
47798
48226
  function mobileUxIssues(dir, strict) {
47799
- const file = join47(dir, "MOBILE-UX.md");
47800
- if (!existsSync49(file)) return [];
48227
+ const file = join48(dir, "MOBILE-UX.md");
48228
+ if (!existsSync50(file)) return [];
47801
48229
  let text5;
47802
48230
  try {
47803
- text5 = readFileSync53(file, "utf8");
48231
+ text5 = readFileSync54(file, "utf8");
47804
48232
  } catch {
47805
48233
  return [];
47806
48234
  }
@@ -49475,8 +49903,8 @@ __export(functions_exports, {
49475
49903
  resolveFunctionsTarget: () => resolveFunctionsTarget,
49476
49904
  runRemoteFunctionOperation: () => runRemoteFunctionOperation
49477
49905
  });
49478
- import { readFileSync as readFileSync54, existsSync as existsSync50 } from "node:fs";
49479
- import { join as join48, resolve as resolve41 } from "node:path";
49906
+ import { readFileSync as readFileSync55, existsSync as existsSync51 } from "node:fs";
49907
+ import { join as join49, resolve as resolve41 } from "node:path";
49480
49908
  function resolveFunctionsTarget(input) {
49481
49909
  const explicitUrl = input.functionsUrl?.trim();
49482
49910
  if (explicitUrl) return { mode: "remote", baseUrl: explicitUrl };
@@ -49495,9 +49923,9 @@ async function loadFunctionsApp(dir) {
49495
49923
  }
49496
49924
  const sources = {};
49497
49925
  for (const definition of config.functions.functions) {
49498
- const source = [".ts", ".js", ".mts", ".mjs"].map((extension) => join48(loaded.dir, "functions", `${definition.name}${extension}`)).find(existsSync50);
49926
+ const source = [".ts", ".js", ".mts", ".mjs"].map((extension) => join49(loaded.dir, "functions", `${definition.name}${extension}`)).find(existsSync51);
49499
49927
  if (!source) throw new Error(`missing source for function ${definition.name}; expected functions/${definition.name}.ts`);
49500
- sources[definition.name] = readFileSync54(source, "utf8");
49928
+ sources[definition.name] = readFileSync55(source, "utf8");
49501
49929
  }
49502
49930
  const data = config.cms || config.data ? mergeDataResources(config) : void 0;
49503
49931
  const dataErrors = validateDataResources(data, loaded.name).filter((issue2) => issue2.severity === "error");
@@ -49657,10 +50085,10 @@ __export(studio_reel_exports, {
49657
50085
  utcDay: () => utcDay
49658
50086
  });
49659
50087
  import { createHash as createHash25 } from "node:crypto";
49660
- import { lstatSync as lstatSync9, readFileSync as readFileSync55 } from "node:fs";
49661
- import { basename as basename12, join as join49 } from "node:path";
50088
+ import { lstatSync as lstatSync9, readFileSync as readFileSync56 } from "node:fs";
50089
+ import { basename as basename12, join as join50 } from "node:path";
49662
50090
  function loadMemory2(dir) {
49663
- const m = readJson4(join49(dir, ".himi", "studio", "memory.json"));
50091
+ const m = readJson5(join50(dir, ".himi", "studio", "memory.json"));
49664
50092
  if (!isObj2(m) || m.version !== 1 || !isObj2(m.shots) || !Array.isArray(m.timeline)) return null;
49665
50093
  const shots = [];
49666
50094
  for (const [id, s] of Object.entries(m.shots)) {
@@ -49697,8 +50125,8 @@ function planReel(dir) {
49697
50125
  const memory = loadMemory2(dir);
49698
50126
  const { entries } = readProgress(dir);
49699
50127
  const summary = summarizeProgress(entries);
49700
- const manifest = readJson4(join49(dir, "himalaya.content.json"));
49701
- const tokens = readJson4(join49(dir, "tokens.json"));
50128
+ const manifest = readJson5(join50(dir, "himalaya.content.json"));
50129
+ const tokens = readJson5(join50(dir, "tokens.json"));
49702
50130
  const accentToken = tokens?.colors?.accentPrimary;
49703
50131
  const accentRaw = typeof accentToken === "string" ? accentToken : accentToken?.light;
49704
50132
  const accent = typeof accentRaw === "string" && ACCENT.test(accentRaw) ? accentRaw : DEFAULT_ACCENT;
@@ -49740,10 +50168,10 @@ function inlineImages(dir, plan, budget = REEL_BUDGET_BYTES) {
49740
50168
  const verified = (f) => {
49741
50169
  if (!HISTORY_NAME.test(f)) return null;
49742
50170
  try {
49743
- const path2 = join49(dir, HISTORY_DIR, f);
50171
+ const path2 = join50(dir, HISTORY_DIR, f);
49744
50172
  const st = lstatSync9(path2);
49745
50173
  if (!st.isFile() || st.nlink !== 1) return null;
49746
- const bytes2 = readFileSync55(path2);
50174
+ const bytes2 = readFileSync56(path2);
49747
50175
  return createHash25("sha256").update(bytes2).digest("hex") === f.slice(0, 64) && imageExtension(bytes2) === f.slice(65) ? bytes2 : null;
49748
50176
  } catch {
49749
50177
  return null;
@@ -49816,7 +50244,7 @@ function sinceStart(start, ts2) {
49816
50244
  }
49817
50245
  function renderReel(plan, images) {
49818
50246
  const img = (f, alt, cls = "") => f && images.has(f) ? `<img class="${cls}" src="${images.get(f)}" alt="${escapeHtml(alt)}">` : "";
49819
- const count = (n) => escapeHtml(String(Number.isSafeInteger(n) && n >= 0 ? n : 0));
50247
+ const count2 = (n) => escapeHtml(String(Number.isSafeInteger(n) && n >= 0 ? n : 0));
49820
50248
  const accent = ACCENT.test(plan.accent) ? plan.accent : DEFAULT_ACCENT;
49821
50249
  const clock = (ts2) => sinceStart(plan.startedAt, ts2);
49822
50250
  const frames = plan.frames.filter((f) => images.has(f.file));
@@ -49842,14 +50270,14 @@ figcaption{font-size:12px;color:var(--muted);margin-top:6px;word-break:break-all
49842
50270
  footer{margin-top:48px;font-size:12px;color:var(--muted)}
49843
50271
  </style></head><body><div class="wrap">
49844
50272
  <h1>${escapeHtml(plan.app)}</h1><div class="muted">The making of \u2014 every version of the app as it was built${plan.startedAt ? `, ${escapeHtml(utcDay(plan.startedAt))}` : ""}</div>
49845
- <div class="stats"><div><b>${duration(plan.stats.elapsedMs)}</b><span class="muted">from first picture to last</span></div><div><b>${count(plan.stats.screens)}</b><span class="muted">screens</span></div><div><b>${count(plan.stats.versions)}</b><span class="muted">versions rendered</span></div><div><b>${count(plan.stats.postcards)}</b><span class="muted">postcards</span></div></div>
50273
+ <div class="stats"><div><b>${duration(plan.stats.elapsedMs)}</b><span class="muted">from first picture to last</span></div><div><b>${count2(plan.stats.screens)}</b><span class="muted">screens</span></div><div><b>${count2(plan.stats.versions)}</b><span class="muted">versions rendered</span></div><div><b>${count2(plan.stats.postcards)}</b><span class="muted">postcards</span></div></div>
49846
50274
  <h2>${escapeHtml(plan.focusId ?? "The app")}, version by version</h2>
49847
50275
  ${flip}
49848
50276
  <h2>Where it ended up</h2>
49849
50277
  <div class="grid">${plan.finals.map((f) => `<figure>${img(f.file, f.id)}<figcaption>${escapeHtml(f.id)}</figcaption></figure>`).join("")}</div>
49850
50278
  <h2>Postcards</h2>
49851
50279
  <ul class="log">${plan.postcards.map((c) => `<li><div class="t">${clock(c.ts)}</div><div><div>${escapeHtml(c.message)}</div>${c.step ? `<div class="s">step ${JOURNEY_STEPS.indexOf(c.step) + 1} \xB7 ${escapeHtml(c.step)}</div>` : ""}${c.files.length ? `<div class="imgs">${c.files.map((f) => img(f, c.message)).join("")}</div>` : ""}</div></li>`).join("") || `<li class="muted">No postcards were journaled.</li>`}</ul>
49852
- <footer>Built with himi.${plan.dropped ? ` ${count(plan.dropped)} pictures were left out to keep this file small.` : ""}</footer>
50280
+ <footer>Built with himi.${plan.dropped ? ` ${count2(plan.dropped)} pictures were left out to keep this file small.` : ""}</footer>
49853
50281
  </div>
49854
50282
  <script>
49855
50283
  (function(){var F=${data};var imgs=document.querySelectorAll(".phone img");if(imgs.length<2)return;var i=0;
@@ -49863,7 +50291,7 @@ function buildReel(dir, budget = REEL_BUDGET_BYTES) {
49863
50291
  const { plan, images } = inlineImages(dir, planReel(dir), budget);
49864
50292
  return { html: renderReel(plan, images), plan };
49865
50293
  }
49866
- var REEL_BUDGET_BYTES, MAX_FLIP_FRAMES, DEFAULT_ACCENT, ACCENT, HISTORY_NAME, MIME2, mimeOf, readJson4, isObj2, str, historyName, cited;
50294
+ var REEL_BUDGET_BYTES, MAX_FLIP_FRAMES, DEFAULT_ACCENT, ACCENT, HISTORY_NAME, MIME2, mimeOf, readJson5, isObj2, str, historyName, cited;
49867
50295
  var init_studio_reel = __esm({
49868
50296
  "src/studio-reel.ts"() {
49869
50297
  init_progress();
@@ -49875,9 +50303,9 @@ var init_studio_reel = __esm({
49875
50303
  HISTORY_NAME = /^[0-9a-f]{64}\.(png|jpg|gif|webp)$/;
49876
50304
  MIME2 = { png: "image/png", jpg: "image/jpeg", gif: "image/gif", webp: "image/webp" };
49877
50305
  mimeOf = (file) => MIME2[file.slice(65)] ?? "image/png";
49878
- readJson4 = (file) => {
50306
+ readJson5 = (file) => {
49879
50307
  try {
49880
- return JSON.parse(readFileSync55(file, "utf8"));
50308
+ return JSON.parse(readFileSync56(file, "utf8"));
49881
50309
  } catch {
49882
50310
  return null;
49883
50311
  }
@@ -49908,9 +50336,9 @@ __export(native_decode_exports, {
49908
50336
  runNativeDecode: () => runNativeDecode
49909
50337
  });
49910
50338
  import { execFileSync as execFileSync5, spawnSync as spawnSync2 } from "node:child_process";
49911
- import { existsSync as existsSync51, mkdtempSync as mkdtempSync2, readFileSync as readFileSync56, rmSync as rmSync11, writeFileSync as writeFileSync21 } from "node:fs";
50339
+ import { existsSync as existsSync52, mkdtempSync as mkdtempSync2, readFileSync as readFileSync57, rmSync as rmSync11, writeFileSync as writeFileSync21 } from "node:fs";
49912
50340
  import { tmpdir as tmpdir3 } from "node:os";
49913
- import { join as join50 } from "node:path";
50341
+ import { join as join51 } from "node:path";
49914
50342
  import { fileURLToPath as fileURLToPath20 } from "node:url";
49915
50343
  function nativeDecodePackageDir() {
49916
50344
  return fileURLToPath20(new URL("../../../core/runtime/ios/HimalayaCore/", import.meta.url));
@@ -49920,8 +50348,8 @@ function nativeDecodeAndroidHarnessDir() {
49920
50348
  }
49921
50349
  function nativeOracleShipped() {
49922
50350
  return {
49923
- ios: existsSync51(join50(nativeDecodePackageDir(), "Package.swift")),
49924
- android: existsSync51(join50(nativeDecodeAndroidHarnessDir(), "gradlew"))
50351
+ ios: existsSync52(join51(nativeDecodePackageDir(), "Package.swift")),
50352
+ android: existsSync52(join51(nativeDecodeAndroidHarnessDir(), "gradlew"))
49925
50353
  };
49926
50354
  }
49927
50355
  function detectNativeDecode(options = {}) {
@@ -49932,7 +50360,7 @@ function detectNativeDecode(options = {}) {
49932
50360
  return { platform: "ios", available: false, reason: `the iOS decoder oracle requires macOS (darwin); this host is ${hostPlatform}` };
49933
50361
  }
49934
50362
  if (!swiftAvailable) return { platform: "ios", available: false, reason: "swift is not on PATH" };
49935
- const packageAvailable = options.packageAvailable ?? existsSync51(join50(packageDir, "Package.swift"));
50363
+ const packageAvailable = options.packageAvailable ?? existsSync52(join51(packageDir, "Package.swift"));
49936
50364
  if (!packageAvailable) {
49937
50365
  return {
49938
50366
  platform: "ios",
@@ -49944,7 +50372,7 @@ function detectNativeDecode(options = {}) {
49944
50372
  }
49945
50373
  function localPropertiesSdk(harnessDir) {
49946
50374
  try {
49947
- const match = readFileSync56(join50(harnessDir, "local.properties"), "utf8").match(/^sdk\.dir\s*=\s*(.+)$/m);
50375
+ const match = readFileSync57(join51(harnessDir, "local.properties"), "utf8").match(/^sdk\.dir\s*=\s*(.+)$/m);
49948
50376
  return match?.[1]?.trim().replace(/\\([ :\\])/g, "$1") ?? null;
49949
50377
  } catch {
49950
50378
  return null;
@@ -49952,8 +50380,8 @@ function localPropertiesSdk(harnessDir) {
49952
50380
  }
49953
50381
  function detectAndroidNativeDecode(options = {}) {
49954
50382
  const harnessDir = options.harnessDir ?? nativeDecodeAndroidHarnessDir();
49955
- const gradlew = join50(harnessDir, "gradlew");
49956
- const gradlewAvailable = options.gradlewAvailable ?? existsSync51(gradlew);
50383
+ const gradlew = join51(harnessDir, "gradlew");
50384
+ const gradlewAvailable = options.gradlewAvailable ?? existsSync52(gradlew);
49957
50385
  if (!gradlewAvailable) {
49958
50386
  return {
49959
50387
  platform: "android",
@@ -49963,7 +50391,7 @@ function detectAndroidNativeDecode(options = {}) {
49963
50391
  }
49964
50392
  const androidHome = options.androidHome ?? process.env.ANDROID_HOME ?? "";
49965
50393
  const sdk = androidHome.trim() || (options.localPropertiesSdk !== void 0 ? options.localPropertiesSdk : localPropertiesSdk(harnessDir));
49966
- const sdkAvailable = options.sdkAvailable ?? (typeof sdk === "string" && sdk.length > 0 && existsSync51(sdk));
50394
+ const sdkAvailable = options.sdkAvailable ?? (typeof sdk === "string" && sdk.length > 0 && existsSync52(sdk));
49967
50395
  if (!sdkAvailable) {
49968
50396
  return {
49969
50397
  platform: "android",
@@ -50035,14 +50463,14 @@ function parseNativeDecodeOutput(stdout, expectedScreenIds, platform = "ios") {
50035
50463
  return verdicts;
50036
50464
  }
50037
50465
  function runNativeDecode(options) {
50038
- const descriptorDir = mkdtempSync2(join50(tmpdir3(), "himi-native-decode-"));
50466
+ const descriptorDir = mkdtempSync2(join51(tmpdir3(), "himi-native-decode-"));
50039
50467
  const startedAt = Date.now();
50040
50468
  try {
50041
50469
  for (const descriptor of options.descriptors) {
50042
50470
  if (typeof descriptor.descriptorJson !== "string") {
50043
50471
  throw new Error(`native decode cannot judge ${descriptor.screenId}: the release builder did not return its exact baked JSON bytes`);
50044
50472
  }
50045
- writeFileSync21(join50(descriptorDir, `${encodeURIComponent(descriptor.screenId)}.json`), descriptor.descriptorJson);
50473
+ writeFileSync21(join51(descriptorDir, `${encodeURIComponent(descriptor.screenId)}.json`), descriptor.descriptorJson);
50046
50474
  }
50047
50475
  options.progress?.("building cached Swift oracle");
50048
50476
  execFileSync5("swift", ["build", "--package-path", options.packageDir, "--product", "himi-decode-oracle"], {
@@ -50054,7 +50482,7 @@ function runNativeDecode(options) {
50054
50482
  encoding: "utf8"
50055
50483
  }).trim();
50056
50484
  options.progress?.(`running real Swift decoder over ${options.descriptors.length} screen${options.descriptors.length === 1 ? "" : "s"}`);
50057
- const stdout = execFileSync5(join50(binPath, "himi-decode-oracle"), ["--dir", descriptorDir], {
50485
+ const stdout = execFileSync5(join51(binPath, "himi-decode-oracle"), ["--dir", descriptorDir], {
50058
50486
  stdio: ["ignore", "pipe", "pipe"],
50059
50487
  encoding: "utf8"
50060
50488
  });
@@ -50072,15 +50500,15 @@ function runNativeDecode(options) {
50072
50500
  }
50073
50501
  }
50074
50502
  function runAndroidNativeDecode(options) {
50075
- const descriptorDir = mkdtempSync2(join50(tmpdir3(), "himi-native-decode-android-"));
50076
- const outputPath = join50(descriptorDir, "verdicts.ndjson");
50503
+ const descriptorDir = mkdtempSync2(join51(tmpdir3(), "himi-native-decode-android-"));
50504
+ const outputPath = join51(descriptorDir, "verdicts.ndjson");
50077
50505
  const startedAt = Date.now();
50078
50506
  try {
50079
50507
  for (const descriptor of options.descriptors) {
50080
50508
  if (typeof descriptor.descriptorJson !== "string") {
50081
50509
  throw new Error(`native decode cannot judge ${descriptor.screenId}: the release builder did not return its exact baked JSON bytes`);
50082
50510
  }
50083
- writeFileSync21(join50(descriptorDir, `${encodeURIComponent(descriptor.screenId)}.json`), descriptor.descriptorJson);
50511
+ writeFileSync21(join51(descriptorDir, `${encodeURIComponent(descriptor.screenId)}.json`), descriptor.descriptorJson);
50084
50512
  }
50085
50513
  options.progress?.(`running real Kotlin decoder over ${options.descriptors.length} screen${options.descriptors.length === 1 ? "" : "s"}`);
50086
50514
  execFileSync5(options.gradlew, [
@@ -50095,7 +50523,7 @@ function runAndroidNativeDecode(options) {
50095
50523
  stdio: ["ignore", "ignore", "pipe"],
50096
50524
  encoding: "utf8"
50097
50525
  });
50098
- const stdout = readFileSync56(outputPath, "utf8");
50526
+ const stdout = readFileSync57(outputPath, "utf8");
50099
50527
  return {
50100
50528
  platform: "android",
50101
50529
  verdicts: parseNativeDecodeOutput(stdout, options.descriptors.map((descriptor) => descriptor.screenId), "android"),
@@ -50165,30 +50593,30 @@ __export(browser_authoring_exports, {
50165
50593
  });
50166
50594
  import { createServer as createServer3 } from "node:http";
50167
50595
  import { createHash as createHash26, randomBytes as randomBytes4 } from "node:crypto";
50168
- import { existsSync as existsSync52, readdirSync as readdirSync26, readFileSync as readFileSync57, realpathSync as realpathSync6, statSync as statSync12 } from "node:fs";
50169
- import { join as join51, posix as posix2, relative as relative9, sep as sep12 } from "node:path";
50596
+ import { existsSync as existsSync53, readdirSync as readdirSync27, readFileSync as readFileSync58, realpathSync as realpathSync6, statSync as statSync12 } from "node:fs";
50597
+ import { join as join52, posix as posix2, relative as relative9, sep as sep12 } from "node:path";
50170
50598
  function humanMs(ms) {
50171
50599
  return ms < 1e3 ? `${ms}ms` : `${Math.round(ms / 1e3)}s`;
50172
50600
  }
50173
50601
  function collectWorkerSources(tier5SrcDir, bundleName) {
50174
- const root = join51(tier5SrcDir, bundleName);
50602
+ const root = join52(tier5SrcDir, bundleName);
50175
50603
  const files = {};
50176
50604
  const visitedDirs = /* @__PURE__ */ new Set();
50177
50605
  const walk2 = (abs, rel) => {
50178
50606
  const real = realpathSync6(abs);
50179
50607
  if (visitedDirs.has(real)) return;
50180
50608
  visitedDirs.add(real);
50181
- for (const name of readdirSync26(abs).sort()) {
50182
- const childAbs = join51(abs, name);
50609
+ for (const name of readdirSync27(abs).sort()) {
50610
+ const childAbs = join52(abs, name);
50183
50611
  const childRel = posix2.join(rel, name);
50184
50612
  if (statSync12(childAbs).isDirectory()) {
50185
50613
  walk2(childAbs, childRel);
50186
50614
  } else if (/\.(ts|tsx|js|mjs|json)$/.test(name)) {
50187
- files[childRel] = readFileSync57(childAbs, "utf8");
50615
+ files[childRel] = readFileSync58(childAbs, "utf8");
50188
50616
  }
50189
50617
  }
50190
50618
  };
50191
- if (!existsSync52(root)) return null;
50619
+ if (!existsSync53(root)) return null;
50192
50620
  walk2(root, `/tier5-src/${bundleName}`);
50193
50621
  const entry = [`/tier5-src/${bundleName}/index.ts`, `/tier5-src/${bundleName}/index.js`].find((p) => p in files);
50194
50622
  if (!entry) return null;
@@ -50205,20 +50633,20 @@ function collectRelativeSiblings(files, tier5SrcDir) {
50205
50633
  const spec = m[1];
50206
50634
  const virtual = posix2.normalize(posix2.join(dir, spec));
50207
50635
  if (!virtual.startsWith("/tier5-src/")) continue;
50208
- const onDisk = join51(tier5SrcDir, virtual.slice("/tier5-src/".length));
50636
+ const onDisk = join52(tier5SrcDir, virtual.slice("/tier5-src/".length));
50209
50637
  const candidates = [
50210
50638
  onDisk,
50211
50639
  `${onDisk}.ts`,
50212
50640
  `${onDisk}.js`,
50213
50641
  onDisk.replace(/\.js$/, ".ts"),
50214
- join51(onDisk, "index.ts"),
50215
- join51(onDisk, "index.js")
50642
+ join52(onDisk, "index.ts"),
50643
+ join52(onDisk, "index.js")
50216
50644
  ];
50217
50645
  for (const abs of candidates) {
50218
- if (!existsSync52(abs) || statSync12(abs).isDirectory()) continue;
50646
+ if (!existsSync53(abs) || statSync12(abs).isDirectory()) continue;
50219
50647
  const key2 = `/tier5-src/${relative9(tier5SrcDir, abs).split(sep12).join(posix2.sep)}`;
50220
50648
  if (key2 in files) break;
50221
- files[key2] = readFileSync57(abs, "utf8");
50649
+ files[key2] = readFileSync58(abs, "utf8");
50222
50650
  queue.push(key2);
50223
50651
  break;
50224
50652
  }
@@ -50253,7 +50681,7 @@ function collectSdkModules(files, resolveSubpath) {
50253
50681
  function sdkSubpathReader(resolve48) {
50254
50682
  return (sub) => {
50255
50683
  try {
50256
- return readFileSync57(resolve48(`@wix/himalaya/${sub}`), "utf8");
50684
+ return readFileSync58(resolve48(`@wix/himalaya/${sub}`), "utf8");
50257
50685
  } catch {
50258
50686
  return null;
50259
50687
  }
@@ -50425,8 +50853,8 @@ __export(coverage_exports, {
50425
50853
  suppressedNativeKeys: () => suppressedNativeKeys,
50426
50854
  trendAndStore: () => trendAndStore
50427
50855
  });
50428
- import { existsSync as existsSync53, mkdirSync as mkdirSync25, readFileSync as readFileSync58, writeFileSync as writeFileSync22 } from "node:fs";
50429
- import { dirname as dirname28, join as join52 } from "node:path";
50856
+ import { existsSync as existsSync54, mkdirSync as mkdirSync25, readFileSync as readFileSync59, writeFileSync as writeFileSync22 } from "node:fs";
50857
+ import { dirname as dirname28, join as join53 } from "node:path";
50430
50858
  function suppressedNativeKeys(oracle) {
50431
50859
  return [...oracle.ios ? [] : ["native-decode|ios"], ...oracle.android ? [] : ["native-decode|android"]];
50432
50860
  }
@@ -50606,11 +51034,11 @@ function assembleLedger(r, options = {}) {
50606
51034
  return out;
50607
51035
  }
50608
51036
  function trendAndStore(dir, entries, suppressed) {
50609
- const p = join52(dir, LEDGER_FILE);
51037
+ const p = join53(dir, LEDGER_FILE);
50610
51038
  let previous = null;
50611
51039
  try {
50612
- if (existsSync53(p)) {
50613
- const parsed = JSON.parse(readFileSync58(p, "utf8"));
51040
+ if (existsSync54(p)) {
51041
+ const parsed = JSON.parse(readFileSync59(p, "utf8"));
50614
51042
  if (Array.isArray(parsed.keys)) previous = parsed.keys.filter((k) => typeof k === "string");
50615
51043
  }
50616
51044
  } catch {
@@ -50649,11 +51077,11 @@ function formatLedger(entries, trend) {
50649
51077
  return out.join("\n");
50650
51078
  }
50651
51079
  function diffAndStoreObservations(dir, current) {
50652
- const p = join52(dir, OBS_FILE);
51080
+ const p = join53(dir, OBS_FILE);
50653
51081
  let previous = null;
50654
51082
  try {
50655
- if (existsSync53(p)) {
50656
- const parsed = JSON.parse(readFileSync58(p, "utf8"));
51083
+ if (existsSync54(p)) {
51084
+ const parsed = JSON.parse(readFileSync59(p, "utf8"));
50657
51085
  if (parsed.screens && typeof parsed.screens === "object" && !Array.isArray(parsed.screens) && Object.values(parsed.screens).every(
50658
51086
  (v) => Array.isArray(v) && v.every((l) => typeof l === "string")
50659
51087
  )) {
@@ -50702,8 +51130,8 @@ var LEDGER_FILE, OBS_FILE;
50702
51130
  var init_coverage = __esm({
50703
51131
  "src/coverage.ts"() {
50704
51132
  init_native_decode();
50705
- LEDGER_FILE = join52(".himi", "test-ledger.json");
50706
- OBS_FILE = join52(".himi", "test-observations.json");
51133
+ LEDGER_FILE = join53(".himi", "test-ledger.json");
51134
+ OBS_FILE = join53(".himi", "test-observations.json");
50707
51135
  }
50708
51136
  });
50709
51137
 
@@ -50714,16 +51142,16 @@ __export(design_kits_exports, {
50714
51142
  kitsDir: () => kitsDir,
50715
51143
  listKits: () => listKits2
50716
51144
  });
50717
- import { existsSync as existsSync54, readFileSync as readFileSync59, readdirSync as readdirSync27 } from "node:fs";
50718
- import { join as join53, basename as basename13 } from "node:path";
51145
+ import { existsSync as existsSync55, readFileSync as readFileSync60, readdirSync as readdirSync28 } from "node:fs";
51146
+ import { join as join54, basename as basename13 } from "node:path";
50719
51147
  import { fileURLToPath as fileURLToPath21 } from "node:url";
50720
51148
  function kitsDir() {
50721
- return KIT_DIR_CANDIDATES.find((p) => existsSync54(p));
51149
+ return KIT_DIR_CANDIDATES.find((p) => existsSync55(p));
50722
51150
  }
50723
51151
  function listKits2() {
50724
51152
  const dir = kitsDir();
50725
51153
  if (!dir) return [];
50726
- return readdirSync27(dir).filter((f) => f.endsWith(".json")).map((f) => basename13(f, ".json")).sort();
51154
+ return readdirSync28(dir).filter((f) => f.endsWith(".json")).map((f) => basename13(f, ".json")).sort();
50727
51155
  }
50728
51156
  function kitReport(name) {
50729
51157
  const dir = kitsDir();
@@ -50742,7 +51170,7 @@ In a published @wix/himalaya-cli this is a packaging bug \u2014 the kits should
50742
51170
  if (!kits.includes(name)) {
50743
51171
  return { ok: false, kit: name, kits, problems: [`no design kit named "${name}". Available: ${kits.join(", ")}`] };
50744
51172
  }
50745
- const raw = readFileSync59(join53(dir, `${name}.json`), "utf8");
51173
+ const raw = readFileSync60(join54(dir, `${name}.json`), "utf8");
50746
51174
  let parsed;
50747
51175
  try {
50748
51176
  parsed = JSON.parse(raw);
@@ -50766,9 +51194,9 @@ var init_design_kits = __esm({
50766
51194
  "src/design-kits.ts"() {
50767
51195
  KIT_DIR_CANDIDATES = [
50768
51196
  // in-repo: tools/himi-cli/src/design-kits.ts → <repo>/stdlib/design-kits/
50769
- join53(fileURLToPath21(new URL("../../..", import.meta.url)), "stdlib", "design-kits"),
51197
+ join54(fileURLToPath21(new URL("../../..", import.meta.url)), "stdlib", "design-kits"),
50770
51198
  // published CLI: <pkg>/dist/cli.mjs → <pkg>/stdlib/design-kits/
50771
- join53(fileURLToPath21(new URL("..", import.meta.url)), "stdlib", "design-kits")
51199
+ join54(fileURLToPath21(new URL("..", import.meta.url)), "stdlib", "design-kits")
50772
51200
  ];
50773
51201
  }
50774
51202
  });
@@ -50782,11 +51210,11 @@ __export(assets_exports, {
50782
51210
  removeAssetPayload: () => removeAssetPayload,
50783
51211
  verifyReport: () => verifyReport
50784
51212
  });
50785
- import { readFileSync as readFileSync60 } from "node:fs";
51213
+ import { readFileSync as readFileSync61 } from "node:fs";
50786
51214
  import { basename as basename14 } from "node:path";
50787
51215
  function declaredAssets(dir) {
50788
51216
  return collectAssets(dir).map((a) => {
50789
- const bytes2 = readFileSync60(a.path);
51217
+ const bytes2 = readFileSync61(a.path);
50790
51218
  return {
50791
51219
  name: a.name,
50792
51220
  sha256: assetSha256(bytes2),
@@ -50863,7 +51291,7 @@ function verifyReport(dir, app, manifest) {
50863
51291
  };
50864
51292
  }
50865
51293
  function addAssetPayload(manifest, filePath, opts) {
50866
- const bytes2 = readFileSync60(filePath);
51294
+ const bytes2 = readFileSync61(filePath);
50867
51295
  const name = opts.name ?? basename14(filePath);
50868
51296
  const sha2565 = assetSha256(bytes2);
50869
51297
  const record2 = {
@@ -52538,9 +52966,9 @@ __export(run_exports, {
52538
52966
  runPackage: () => runPackage,
52539
52967
  withoutProdServe: () => withoutProdServe
52540
52968
  });
52541
- import { cpSync as cpSync2, existsSync as existsSync55, mkdirSync as mkdirSync26, mkdtempSync as mkdtempSync3, readFileSync as readFileSync61, rmSync as rmSync12, statSync as statSync13 } from "node:fs";
52969
+ import { cpSync as cpSync2, existsSync as existsSync56, mkdirSync as mkdirSync26, mkdtempSync as mkdtempSync3, readFileSync as readFileSync62, rmSync as rmSync12, statSync as statSync13 } from "node:fs";
52542
52970
  import { networkInterfaces as osNetworkInterfaces, tmpdir as tmpdir4 } from "node:os";
52543
- import { dirname as dirname29, join as join54 } from "node:path";
52971
+ import { dirname as dirname29, join as join55 } from "node:path";
52544
52972
  import { fileURLToPath as fileURLToPath22 } from "node:url";
52545
52973
  function makePackageRunHandler(opts) {
52546
52974
  const store = new FileReleaseStore(opts.releaseRoot);
@@ -52568,9 +52996,9 @@ async function fetchBytes(url) {
52568
52996
  }
52569
52997
  async function resolvePackage(source, workDir) {
52570
52998
  const isUrl2 = /^https?:\/\//i.test(source);
52571
- if (!isUrl2 && existsSync55(source) && statSync13(source).isDirectory()) {
52572
- const appDirManifest = join54(source, "release-manifest.json");
52573
- if (existsSync55(appDirManifest)) {
52999
+ if (!isUrl2 && existsSync56(source) && statSync13(source).isDirectory()) {
53000
+ const appDirManifest = join55(source, "release-manifest.json");
53001
+ if (existsSync56(appDirManifest)) {
52574
53002
  const opened2 = openPackageDir(source);
52575
53003
  if (!opened2.validation.ok) {
52576
53004
  throw new Error(`package is not valid:
@@ -52579,9 +53007,9 @@ async function resolvePackage(source, workDir) {
52579
53007
  const app2 = opened2.header?.app ?? opened2.manifest?.app;
52580
53008
  if (!app2) throw new Error(`${source} has no app id (no himi-package.json and no manifest.app)`);
52581
53009
  if (!isSafeAppId(app2)) throw new Error(`${source} declares an unsafe app id ${JSON.stringify(app2)} \u2014 refusing to open it`);
52582
- const root2 = workDir ?? mkdtempSync3(join54(tmpdir4(), "himi-run-"));
52583
- const target = join54(root2, app2);
52584
- if (join54(source) !== target) {
53010
+ const root2 = workDir ?? mkdtempSync3(join55(tmpdir4(), "himi-run-"));
53011
+ const target = join55(root2, app2);
53012
+ if (join55(source) !== target) {
52585
53013
  mkdirSync26(root2, { recursive: true });
52586
53014
  rmSync12(target, { recursive: true, force: true });
52587
53015
  cpSync2(source, target, { recursive: true });
@@ -52598,8 +53026,8 @@ async function resolvePackage(source, workDir) {
52598
53026
  }
52599
53027
  throw new Error(`${source} is a directory but has no release-manifest.json \u2014 is it a package?`);
52600
53028
  }
52601
- const bytes2 = isUrl2 ? await fetchBytes(source) : readFileSync61(source);
52602
- const root = workDir ?? mkdtempSync3(join54(tmpdir4(), "himi-run-"));
53029
+ const bytes2 = isUrl2 ? await fetchBytes(source) : readFileSync62(source);
53030
+ const root = workDir ?? mkdtempSync3(join55(tmpdir4(), "himi-run-"));
52603
53031
  const { app, appDir: appDir2 } = unpackTo(bytes2, root);
52604
53032
  const opened = openPackageDir(appDir2);
52605
53033
  if (!opened.validation.ok) {
@@ -52622,14 +53050,14 @@ async function loadPreviewAssets() {
52622
53050
  const mod = await Promise.resolve().then(() => (init_preview_assets_generated(), preview_assets_generated_exports));
52623
53051
  const here = dirname29(fileURLToPath22(import.meta.url));
52624
53052
  const candidates = [
52625
- join54(here, "preview-assets.generated.bin"),
53053
+ join55(here, "preview-assets.generated.bin"),
52626
53054
  // published: copied next to the bundled CLI
52627
- join54(here, "../../../packages/serve/src/preview-assets.generated.bin")
53055
+ join55(here, "../../../packages/serve/src/preview-assets.generated.bin")
52628
53056
  // in-repo checkout
52629
53057
  ];
52630
53058
  for (const path2 of candidates) {
52631
- if (!existsSync55(path2)) continue;
52632
- const blob = readFileSync61(path2);
53059
+ if (!existsSync56(path2)) continue;
53060
+ const blob = readFileSync62(path2);
52633
53061
  return mod.assemblePreviewAssetsFromBlob(new Uint8Array(blob.buffer, blob.byteOffset, blob.byteLength));
52634
53062
  }
52635
53063
  return /* @__PURE__ */ new Map();
@@ -52776,9 +53204,9 @@ __export(run_watch_exports, {
52776
53204
  watchedContentDir: () => watchedContentDir,
52777
53205
  writeRunRecord: () => writeRunRecord
52778
53206
  });
52779
- import { closeSync as closeSync2, constants, existsSync as existsSync56, fstatSync, lstatSync as lstatSync10, statSync as statSync14, mkdirSync as mkdirSync27, openSync as openSync2, readFileSync as readFileSync62, realpathSync as realpathSync7, renameSync as renameSync6, rmSync as rmSync13, writeFileSync as writeFileSync23, watch as fsWatch } from "node:fs";
53207
+ import { closeSync as closeSync2, constants, existsSync as existsSync57, fstatSync, lstatSync as lstatSync10, statSync as statSync14, mkdirSync as mkdirSync27, openSync as openSync2, readFileSync as readFileSync63, realpathSync as realpathSync7, renameSync as renameSync6, rmSync as rmSync13, writeFileSync as writeFileSync23, watch as fsWatch } from "node:fs";
52780
53208
  import { randomUUID as randomUUID3 } from "node:crypto";
52781
- import { extname as extname4, join as join55, resolve as resolve42, sep as sep13 } from "node:path";
53209
+ import { extname as extname4, join as join56, resolve as resolve42, sep as sep13 } from "node:path";
52782
53210
  function diffReleases(prev, next) {
52783
53211
  const bundles = Object.keys(next.bundles).filter((b) => prev.bundles[b]?.sha256 !== next.bundles[b].sha256).sort();
52784
53212
  const changedBundles = new Set(bundles);
@@ -52819,7 +53247,7 @@ function watchContent(dir, onChange, opts = {}) {
52819
53247
  let timer = null;
52820
53248
  const pending = /* @__PURE__ */ new Set();
52821
53249
  const watcher2 = watch2(dir, { recursive: true }, (_event, file) => {
52822
- if (!file || ignoredChange(file, (rel) => isDir(join55(dir, rel)))) return;
53250
+ if (!file || ignoredChange(file, (rel) => isDir(join56(dir, rel)))) return;
52823
53251
  pending.add(file);
52824
53252
  if (timer) clearTimeout(timer);
52825
53253
  timer = setTimeout(() => {
@@ -52840,7 +53268,7 @@ function watchedContentDir(source) {
52840
53268
  if (!source || /^https?:\/\//i.test(source)) return null;
52841
53269
  const dir = resolve42(source);
52842
53270
  try {
52843
- return statSync14(dir).isDirectory() && existsSync56(join55(dir, "himalaya.content.json")) ? dir : null;
53271
+ return statSync14(dir).isDirectory() && existsSync57(join56(dir, "himalaya.content.json")) ? dir : null;
52844
53272
  } catch {
52845
53273
  return null;
52846
53274
  }
@@ -52897,7 +53325,7 @@ function serialize(task) {
52897
53325
  });
52898
53326
  }
52899
53327
  function himiDir(dir) {
52900
- const root = join55(dir, ".himi");
53328
+ const root = join56(dir, ".himi");
52901
53329
  try {
52902
53330
  return lstatSync10(root).isDirectory() ? root : null;
52903
53331
  } catch (e) {
@@ -52909,11 +53337,11 @@ function himiDir(dir) {
52909
53337
  function writeRunRecord(dir, record2) {
52910
53338
  const root = himiDir(dir);
52911
53339
  if (!root) return false;
52912
- const tmp = join55(root, `.run.json.${process.pid}.${randomUUID3()}.tmp`);
53340
+ const tmp = join56(root, `.run.json.${process.pid}.${randomUUID3()}.tmp`);
52913
53341
  try {
52914
53342
  writeFileSync23(tmp, `${JSON.stringify(record2, null, 2)}
52915
53343
  `, { flag: "wx" });
52916
- renameSync6(tmp, join55(root, "run.json"));
53344
+ renameSync6(tmp, join56(root, "run.json"));
52917
53345
  } catch (e) {
52918
53346
  rmSync13(tmp, { force: true });
52919
53347
  throw e;
@@ -52921,7 +53349,7 @@ function writeRunRecord(dir, record2) {
52921
53349
  return true;
52922
53350
  }
52923
53351
  function removeRunRecord(dir, owner) {
52924
- const root = join55(dir, ".himi");
53352
+ const root = join56(dir, ".himi");
52925
53353
  try {
52926
53354
  if (!lstatSync10(root).isDirectory()) return;
52927
53355
  } catch {
@@ -52936,16 +53364,16 @@ function removeRunRecord(dir, owner) {
52936
53364
  }
52937
53365
  if (typeof pid === "number" && pid !== owner) return;
52938
53366
  }
52939
- rmSync13(join55(root, "run.json"), { force: true });
53367
+ rmSync13(join56(root, "run.json"), { force: true });
52940
53368
  }
52941
53369
  function boardFile(dir, pathname) {
52942
53370
  let rel = null;
52943
- if (pathname === "/__himi/studio/" || pathname === "/__himi/studio/index.html") rel = join55("studio", "index.html");
52944
- else if (pathname === "/__himi/studio/state.js") rel = join55("studio", "state.js");
52945
- else if (pathname === "/__himi/studio/timeline.js") rel = join55("studio", "timeline.js");
53371
+ if (pathname === "/__himi/studio/" || pathname === "/__himi/studio/index.html") rel = join56("studio", "index.html");
53372
+ else if (pathname === "/__himi/studio/state.js") rel = join56("studio", "state.js");
53373
+ else if (pathname === "/__himi/studio/timeline.js") rel = join56("studio", "timeline.js");
52946
53374
  else {
52947
53375
  const m = /^\/__himi\/history\/([0-9a-f]{64}\.(png|jpg|gif|webp))$/.exec(pathname);
52948
- if (m) rel = join55("history", m[1]);
53376
+ if (m) rel = join56("history", m[1]);
52949
53377
  }
52950
53378
  if (!rel) return null;
52951
53379
  const bytes2 = readConfined(resolve42(dir, ".himi"), rel);
@@ -52961,7 +53389,7 @@ function readConfined(root, rel) {
52961
53389
  const parts = rel.split(sep13);
52962
53390
  let at = root;
52963
53391
  for (let i = 0; i < parts.length; i++) {
52964
- at = join55(at, parts[i]);
53392
+ at = join56(at, parts[i]);
52965
53393
  const st = lstatSync10(at);
52966
53394
  if (st.isSymbolicLink() || !(i === parts.length - 1 ? st.isFile() : st.isDirectory())) return null;
52967
53395
  }
@@ -52970,7 +53398,7 @@ function readConfined(root, rel) {
52970
53398
  const fd = openSync2(at, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
52971
53399
  try {
52972
53400
  if (!fstatSync(fd).isFile()) return null;
52973
- return readFileSync62(fd);
53401
+ return readFileSync63(fd);
52974
53402
  } finally {
52975
53403
  closeSync2(fd);
52976
53404
  }
@@ -53022,7 +53450,7 @@ __export(run_browser_exports, {
53022
53450
  runInBrowser: () => runInBrowser
53023
53451
  });
53024
53452
  import { createServer as createServer5 } from "node:http";
53025
- import { existsSync as existsSync57, readFileSync as readFileSync63 } from "node:fs";
53453
+ import { existsSync as existsSync58, readFileSync as readFileSync64 } from "node:fs";
53026
53454
  import { spawnSync as spawnSync3 } from "node:child_process";
53027
53455
  import { dirname as dirname30, resolve as resolve43 } from "node:path";
53028
53456
  import { fileURLToPath as fileURLToPath23 } from "node:url";
@@ -53030,7 +53458,7 @@ function stagedDir() {
53030
53458
  return process.env.HIMI_BROWSER_PLANE_OUT ? resolve43(process.env.HIMI_BROWSER_PLANE_OUT) : resolve43(REPO, "packages", "serve", "dist", "browser-plane");
53031
53459
  }
53032
53460
  function missingStaged() {
53033
- return [...SERVED].filter((f) => !existsSync57(resolve43(stagedDir(), f))).sort();
53461
+ return [...SERVED].filter((f) => !existsSync58(resolve43(stagedDir(), f))).sort();
53034
53462
  }
53035
53463
  function ensureBrowserPlaneStaged() {
53036
53464
  if (missingStaged().length === 0) return { ok: true, detail: "already staged" };
@@ -53115,7 +53543,7 @@ async function runInBrowser(releaseRoot, app, header, opts = {}) {
53115
53543
  }
53116
53544
  const name = path2 === "/" || path2 === "/index.html" ? "host.html" : path2.slice(1);
53117
53545
  if (SERVED.has(name)) {
53118
- if (!existsSync57(resolve43(stagedDir(), name))) {
53546
+ if (!existsSync58(resolve43(stagedDir(), name))) {
53119
53547
  const restaged = ensureBrowserPlaneStaged();
53120
53548
  if (!restaged.ok) {
53121
53549
  res.writeHead(503, { "content-type": "application/json" });
@@ -53125,7 +53553,7 @@ async function runInBrowser(releaseRoot, app, header, opts = {}) {
53125
53553
  }
53126
53554
  let bytes2;
53127
53555
  try {
53128
- bytes2 = readFileSync63(resolve43(stagedDir(), name));
53556
+ bytes2 = readFileSync64(resolve43(stagedDir(), name));
53129
53557
  } catch (e) {
53130
53558
  res.writeHead(503, { "content-type": "application/json" });
53131
53559
  res.end(JSON.stringify({ error: "browser_plane_unstaged", file: name, detail: String(e) }));
@@ -53586,7 +54014,7 @@ __export(preflight_exports, {
53586
54014
  parseStoreConfig: () => parseStoreConfig,
53587
54015
  requireEnv: () => requireEnv
53588
54016
  });
53589
- import { existsSync as existsSync58, readFileSync as readFileSync64 } from "node:fs";
54017
+ import { existsSync as existsSync59, readFileSync as readFileSync65 } from "node:fs";
53590
54018
  import { isAbsolute as isAbsolute3, relative as relative10, resolve as resolve44 } from "node:path";
53591
54019
  function isInsideRepo(p) {
53592
54020
  const rel = relative10(REPO_ROOT3, resolve44(p));
@@ -53610,7 +54038,7 @@ function strip2(line) {
53610
54038
  const hash = line.indexOf("#");
53611
54039
  return (hash >= 0 ? line.slice(0, hash) : line).replace(/\s+$/, "");
53612
54040
  }
53613
- function scalar2(v) {
54041
+ function scalar3(v) {
53614
54042
  const t = v.trim();
53615
54043
  if (t.startsWith('"') && t.endsWith('"') || t.startsWith("'") && t.endsWith("'")) {
53616
54044
  return t.slice(1, -1);
@@ -53633,20 +54061,20 @@ function parseStoreConfig(text5) {
53633
54061
  cfg[key2] = section2;
53634
54062
  } else {
53635
54063
  section2 = null;
53636
- cfg[key2] = scalar2(rest);
54064
+ cfg[key2] = scalar3(rest);
53637
54065
  }
53638
54066
  } else if (section2) {
53639
- section2[key2] = scalar2(rest);
54067
+ section2[key2] = scalar3(rest);
53640
54068
  }
53641
54069
  }
53642
54070
  return cfg;
53643
54071
  }
53644
54072
  function loadStoreConfig(app) {
53645
54073
  const p = storeConfigPath(app);
53646
- if (!existsSync58(p)) {
54074
+ if (!existsSync59(p)) {
53647
54075
  throw new Error(`No store config at ${p}. Create apps/${app}/store/store.config.yaml first (see /setup-store-signing).`);
53648
54076
  }
53649
- return parseStoreConfig(readFileSync64(p, "utf8"));
54077
+ return parseStoreConfig(readFileSync65(p, "utf8"));
53650
54078
  }
53651
54079
  var init_preflight = __esm({
53652
54080
  "../store-release/preflight.ts"() {
@@ -53665,19 +54093,19 @@ __export(credential_profile_exports, {
53665
54093
  profilePath: () => profilePath,
53666
54094
  resolveStoreCredentials: () => resolveStoreCredentials
53667
54095
  });
53668
- import { existsSync as existsSync59, readFileSync as readFileSync65 } from "node:fs";
54096
+ import { existsSync as existsSync60, readFileSync as readFileSync66 } from "node:fs";
53669
54097
  import { homedir as homedir5 } from "node:os";
53670
- import { join as join56 } from "node:path";
54098
+ import { join as join57 } from "node:path";
53671
54099
  function credentialsDir(home = homedir5()) {
53672
- return join56(home, ".himalaya", "store-credentials");
54100
+ return join57(home, ".himalaya", "store-credentials");
53673
54101
  }
53674
54102
  function profilePath(name, home = homedir5()) {
53675
54103
  if (!PROFILE_NAME.test(name)) {
53676
54104
  throw new Error(
53677
- `credential profile ${JSON.stringify(name)} is not a profile name \u2014 use letters, digits, ".", "_" or "-" starting with a letter or digit. It names ${join56(credentialsDir(home), "<name>.json")}; it is never a path.`
54105
+ `credential profile ${JSON.stringify(name)} is not a profile name \u2014 use letters, digits, ".", "_" or "-" starting with a letter or digit. It names ${join57(credentialsDir(home), "<name>.json")}; it is never a path.`
53678
54106
  );
53679
54107
  }
53680
- return join56(credentialsDir(home), `${name}.json`);
54108
+ return join57(credentialsDir(home), `${name}.json`);
53681
54109
  }
53682
54110
  function parseProfile(text5, label2) {
53683
54111
  let doc;
@@ -53708,8 +54136,8 @@ function resolveStoreCredentials(opts) {
53708
54136
  const { app, cfg } = opts;
53709
54137
  const env = opts.env ?? process.env;
53710
54138
  const home = opts.home ?? homedir5();
53711
- const read = opts.readProfile ?? ((p) => readFileSync65(p, "utf8"));
53712
- const exists = opts.profileExists ?? existsSync59;
54139
+ const read = opts.readProfile ?? ((p) => readFileSync66(p, "utf8"));
54140
+ const exists = opts.profileExists ?? existsSync60;
53713
54141
  const ios = cfg.ios ?? {};
53714
54142
  const flagName = opts.profileFlag?.trim();
53715
54143
  const named = namedProfile(cfg, opts.profileFlag);
@@ -53789,9 +54217,9 @@ __export(preflight_app_exports, {
53789
54217
  preflightApp: () => preflightApp,
53790
54218
  profileAccount: () => profileAccount
53791
54219
  });
53792
- import { readFileSync as readFileSync66, existsSync as existsSync60, readdirSync as readdirSync28, statSync as statSync15 } from "node:fs";
54220
+ import { readFileSync as readFileSync67, existsSync as existsSync61, readdirSync as readdirSync29, statSync as statSync15 } from "node:fs";
53793
54221
  import { createHash as createHash27 } from "node:crypto";
53794
- import { join as join57 } from "node:path";
54222
+ import { join as join58 } from "node:path";
53795
54223
  function analyze(projectYml, storeConfig, icon, account) {
53796
54224
  const f = [];
53797
54225
  const has = (re, s) => re.test(s);
@@ -53855,13 +54283,13 @@ function profileAccount(app, storeConfig, opts = {}) {
53855
54283
  }
53856
54284
  }
53857
54285
  async function preflightApp(app, opts = {}) {
53858
- const projectYml = existsSync60(iosProjectYml(app)) ? readFileSync66(iosProjectYml(app), "utf8") : "";
53859
- const storeConfig = existsSync60(storeConfigPath(app)) ? readFileSync66(storeConfigPath(app), "utf8") : "";
54286
+ const projectYml = existsSync61(iosProjectYml(app)) ? readFileSync67(iosProjectYml(app), "utf8") : "";
54287
+ const storeConfig = existsSync61(storeConfigPath(app)) ? readFileSync67(storeConfigPath(app), "utf8") : "";
53860
54288
  if (!storeConfig) return [{ level: "error", message: `no apps/${app}/store/store.config.yaml \u2014 run /setup-store-signing first.` }];
53861
54289
  const findings = analyze(
53862
54290
  projectYml,
53863
54291
  storeConfig,
53864
- await appIconState(join57(appDir(app), "ios")),
54292
+ await appIconState(join58(appDir(app), "ios")),
53865
54293
  profileAccount(app, storeConfig, opts)
53866
54294
  );
53867
54295
  findings.push(...analyzeListing(metadataFilesPresent(app), listScreenshots(app)));
@@ -53890,18 +54318,18 @@ function analyzeSubmissionRecord(appLevel, review, opts) {
53890
54318
  return f;
53891
54319
  }
53892
54320
  function appLevelMetadataFiles(app, locale = "en-US") {
53893
- const root = join57(storeDir(app), "metadata");
54321
+ const root = join58(storeDir(app), "metadata");
53894
54322
  const out = /* @__PURE__ */ new Set();
53895
- if (existsSync60(root)) {
53896
- for (const n of readdirSync28(root)) if (n.endsWith(".txt")) out.add(n);
54323
+ if (existsSync61(root)) {
54324
+ for (const n of readdirSync29(root)) if (n.endsWith(".txt")) out.add(n);
53897
54325
  }
53898
- if (existsSync60(join57(root, locale, "privacy_url.txt"))) out.add("privacy_url.txt");
54326
+ if (existsSync61(join58(root, locale, "privacy_url.txt"))) out.add("privacy_url.txt");
53899
54327
  return out;
53900
54328
  }
53901
54329
  function reviewInfoFiles(app) {
53902
- const dir = join57(storeDir(app), "review_information");
53903
- if (!existsSync60(dir)) return /* @__PURE__ */ new Set();
53904
- return new Set(readdirSync28(dir).filter((n) => n.endsWith(".txt")));
54330
+ const dir = join58(storeDir(app), "review_information");
54331
+ if (!existsSync61(dir)) return /* @__PURE__ */ new Set();
54332
+ return new Set(readdirSync29(dir).filter((n) => n.endsWith(".txt")));
53905
54333
  }
53906
54334
  function isValidAppStoreSize(w, h) {
53907
54335
  return APP_STORE_SIZES.some(([a, b]) => w === a && h === b || w === b && h === a);
@@ -54010,12 +54438,12 @@ function analyzePlayListing(assets) {
54010
54438
  return f;
54011
54439
  }
54012
54440
  function playListingAssets(app, locale = "en-US") {
54013
- const dir = join57(storeDir(app), "play", locale, "images");
54441
+ const dir = join58(storeDir(app), "play", locale, "images");
54014
54442
  const read = (names) => {
54015
54443
  for (const name of names) {
54016
- const p = join57(dir, name);
54444
+ const p = join58(dir, name);
54017
54445
  try {
54018
- if (statSync15(p).isFile()) return { name, bytes: readFileSync66(p) };
54446
+ if (statSync15(p).isFile()) return { name, bytes: readFileSync67(p) };
54019
54447
  } catch {
54020
54448
  }
54021
54449
  }
@@ -54046,26 +54474,26 @@ function analyzeListing(metaFiles, shots) {
54046
54474
  return f;
54047
54475
  }
54048
54476
  function metadataFilesPresent(app, locale = "en-US") {
54049
- const dir = join57(storeDir(app), "metadata", locale);
54050
- if (!existsSync60(dir)) return /* @__PURE__ */ new Set();
54051
- return new Set(readdirSync28(dir).filter((n) => n.endsWith(".txt")));
54477
+ const dir = join58(storeDir(app), "metadata", locale);
54478
+ if (!existsSync61(dir)) return /* @__PURE__ */ new Set();
54479
+ return new Set(readdirSync29(dir).filter((n) => n.endsWith(".txt")));
54052
54480
  }
54053
54481
  function listScreenshots(app, locale = "en-US") {
54054
54482
  const out = [];
54055
- const flat = join57(storeDir(app), "screenshots", locale);
54056
- if (existsSync60(flat)) {
54057
- for (const png of readdirSync28(flat).filter((n) => n.endsWith(".png"))) {
54058
- const sz = pngSize(readFileSync66(join57(flat, png)));
54483
+ const flat = join58(storeDir(app), "screenshots", locale);
54484
+ if (existsSync61(flat)) {
54485
+ for (const png of readdirSync29(flat).filter((n) => n.endsWith(".png"))) {
54486
+ const sz = pngSize(readFileSync67(join58(flat, png)));
54059
54487
  if (sz) out.push({ name: png, w: sz.w, h: sz.h });
54060
54488
  }
54061
54489
  }
54062
- const legacy = join57(storeDir(app), "metadata", locale, "screenshots");
54063
- if (existsSync60(legacy)) {
54064
- for (const deviceDir of readdirSync28(legacy)) {
54065
- const d = join57(legacy, deviceDir);
54490
+ const legacy = join58(storeDir(app), "metadata", locale, "screenshots");
54491
+ if (existsSync61(legacy)) {
54492
+ for (const deviceDir of readdirSync29(legacy)) {
54493
+ const d = join58(legacy, deviceDir);
54066
54494
  if (!statSync15(d).isDirectory()) continue;
54067
- for (const png of readdirSync28(d).filter((n) => n.endsWith(".png"))) {
54068
- const sz = pngSize(readFileSync66(join57(d, png)));
54495
+ for (const png of readdirSync29(d).filter((n) => n.endsWith(".png"))) {
54496
+ const sz = pngSize(readFileSync67(join58(d, png)));
54069
54497
  if (sz) out.push({ name: `${deviceDir}/${png}`, w: sz.w, h: sz.h });
54070
54498
  }
54071
54499
  }
@@ -54073,13 +54501,13 @@ function listScreenshots(app, locale = "en-US") {
54073
54501
  return out;
54074
54502
  }
54075
54503
  function placeholderHashes2() {
54076
- const templateIos = join57(appDir("_template"), "ios");
54077
- if (!existsSync60(templateIos)) return [LEGACY_PLACEHOLDER_SHA2562];
54078
- for (const target of readdirSync28(templateIos)) {
54079
- const p = join57(templateIos, target, "Assets.xcassets", "AppIcon.appiconset", "icon_1024.png");
54504
+ const templateIos = join58(appDir("_template"), "ios");
54505
+ if (!existsSync61(templateIos)) return [LEGACY_PLACEHOLDER_SHA2562];
54506
+ for (const target of readdirSync29(templateIos)) {
54507
+ const p = join58(templateIos, target, "Assets.xcassets", "AppIcon.appiconset", "icon_1024.png");
54080
54508
  try {
54081
54509
  if (!statSync15(p).isFile()) continue;
54082
- return [LEGACY_PLACEHOLDER_SHA2562, createHash27("sha256").update(readFileSync66(p)).digest("hex")];
54510
+ return [LEGACY_PLACEHOLDER_SHA2562, createHash27("sha256").update(readFileSync67(p)).digest("hex")];
54083
54511
  } catch {
54084
54512
  }
54085
54513
  }
@@ -54110,12 +54538,12 @@ async function pixelIssues(bytes2) {
54110
54538
  }
54111
54539
  }
54112
54540
  async function appIconState(iosDir) {
54113
- if (!existsSync60(iosDir)) return { exists: false, isPlaceholder: false, storeIssues: [] };
54114
- for (const target of readdirSync28(iosDir)) {
54115
- const p = join57(iosDir, target, "Assets.xcassets", "AppIcon.appiconset", "icon_1024.png");
54541
+ if (!existsSync61(iosDir)) return { exists: false, isPlaceholder: false, storeIssues: [] };
54542
+ for (const target of readdirSync29(iosDir)) {
54543
+ const p = join58(iosDir, target, "Assets.xcassets", "AppIcon.appiconset", "icon_1024.png");
54116
54544
  try {
54117
54545
  if (!statSync15(p).isFile()) continue;
54118
- const bytes2 = readFileSync66(p);
54546
+ const bytes2 = readFileSync67(p);
54119
54547
  const hash = createHash27("sha256").update(bytes2).digest("hex");
54120
54548
  const pixels = await pixelIssues(bytes2);
54121
54549
  return {
@@ -54187,7 +54615,7 @@ __export(version_exports, {
54187
54615
  setAndroidVersion: () => setAndroidVersion,
54188
54616
  setIosVersion: () => setIosVersion
54189
54617
  });
54190
- import { readFileSync as readFileSync67, writeFileSync as writeFileSync24 } from "node:fs";
54618
+ import { readFileSync as readFileSync68, writeFileSync as writeFileSync24 } from "node:fs";
54191
54619
  function readIosVersion(projectYml) {
54192
54620
  const marketing = matchScalar(projectYml, "CFBundleShortVersionString");
54193
54621
  const build2 = matchScalar(projectYml, "CFBundleVersion");
@@ -54245,8 +54673,8 @@ function setAndroidVersion(gradle, v) {
54245
54673
  function bumpVersion(app, opts) {
54246
54674
  const iosPath = iosProjectYml(app);
54247
54675
  const gradlePath = androidGradle(app);
54248
- const iosYml = readFileSync67(iosPath, "utf8");
54249
- const gradle = readFileSync67(gradlePath, "utf8");
54676
+ const iosYml = readFileSync68(iosPath, "utf8");
54677
+ const gradle = readFileSync68(gradlePath, "utf8");
54250
54678
  const current = readIosVersion(iosYml);
54251
54679
  const next = {
54252
54680
  marketing: opts.marketing ?? current.marketing,
@@ -54263,7 +54691,7 @@ var init_version = __esm({
54263
54691
  });
54264
54692
 
54265
54693
  // ../store-release/register-bundle.ts
54266
- import { readFileSync as readFileSync68 } from "node:fs";
54694
+ import { readFileSync as readFileSync69 } from "node:fs";
54267
54695
  import { createPrivateKey as createPrivateKey2, sign } from "node:crypto";
54268
54696
  function b64url2(b) {
54269
54697
  return Buffer.from(b).toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
@@ -54281,7 +54709,7 @@ function bundleIdBody(identifier, name, platform = "IOS") {
54281
54709
  async function registerBundleId(opts) {
54282
54710
  const env = requireEnv(["ASC_KEY_ID", "ASC_ISSUER_ID", "ASC_KEY_PATH"]);
54283
54711
  assertCredentialOutsideRepo("ASC_KEY_PATH", env.ASC_KEY_PATH);
54284
- const jwt = ascJwt(env.ASC_KEY_ID, env.ASC_ISSUER_ID, readFileSync68(env.ASC_KEY_PATH, "utf8"), Math.floor(Date.now() / 1e3));
54712
+ const jwt = ascJwt(env.ASC_KEY_ID, env.ASC_ISSUER_ID, readFileSync69(env.ASC_KEY_PATH, "utf8"), Math.floor(Date.now() / 1e3));
54285
54713
  const res = await fetch(`${ASC_API}/v1/bundleIds`, {
54286
54714
  method: "POST",
54287
54715
  headers: { Authorization: `Bearer ${jwt}`, "Content-Type": "application/json" },
@@ -54335,7 +54763,7 @@ __export(asc_status_exports, {
54335
54763
  ascStatus: () => ascStatus,
54336
54764
  buildVerdict: () => buildVerdict
54337
54765
  });
54338
- import { readFileSync as readFileSync69 } from "node:fs";
54766
+ import { readFileSync as readFileSync70 } from "node:fs";
54339
54767
  function buildVerdict(builds) {
54340
54768
  if (!builds.length) {
54341
54769
  return "NO builds registered \u2014 if you uploaded recently and it's been >15 min, the binary was REJECTED IN PROCESSING (reason is email-only; run validate-binary.ts / scan-purpose-strings.ts to reproduce it).";
@@ -54349,7 +54777,7 @@ function buildVerdict(builds) {
54349
54777
  function client(creds) {
54350
54778
  const env = creds ?? requireEnv(["ASC_KEY_ID", "ASC_ISSUER_ID", "ASC_KEY_PATH"]);
54351
54779
  assertCredentialOutsideRepo("ASC_KEY_PATH", env.ASC_KEY_PATH);
54352
- const jwt = ascJwt(env.ASC_KEY_ID, env.ASC_ISSUER_ID, readFileSync69(env.ASC_KEY_PATH, "utf8"), Math.floor(Date.now() / 1e3));
54780
+ const jwt = ascJwt(env.ASC_KEY_ID, env.ASC_ISSUER_ID, readFileSync70(env.ASC_KEY_PATH, "utf8"), Math.floor(Date.now() / 1e3));
54353
54781
  return async (path2) => {
54354
54782
  const r = await fetch(`${ASC}${path2}`, { headers: { Authorization: `Bearer ${jwt}` } });
54355
54783
  return { status: r.status, json: await r.json().catch(() => ({})) };
@@ -54509,7 +54937,7 @@ __export(secrets_exports, {
54509
54937
  runRemoteSecretsCommand: () => runRemoteSecretsCommand,
54510
54938
  runSecretsCommand: () => runSecretsCommand
54511
54939
  });
54512
- import { readFileSync as readFileSync70 } from "node:fs";
54940
+ import { readFileSync as readFileSync71 } from "node:fs";
54513
54941
  function environment(value) {
54514
54942
  if (value === "preview" || value === "production") return value;
54515
54943
  throw new Error("--environment must be preview or production");
@@ -54540,7 +54968,7 @@ async function runSecretsCommand(operation, flags, io, options = {}) {
54540
54968
  io.log(JSON.stringify(await vault.list({ app, environment: env })));
54541
54969
  return 0;
54542
54970
  }
54543
- const value = options.readValue ? options.readValue() : readFileSync70(0, "utf8").replace(/\r?\n$/, "");
54971
+ const value = options.readValue ? options.readValue() : readFileSync71(0, "utf8").replace(/\r?\n$/, "");
54544
54972
  if (operation === "set") await vault.set({ app, environment: env, name, value });
54545
54973
  else if (operation === "rotate") await vault.rotate({ app, environment: env, name, value });
54546
54974
  else throw new Error(`unknown secrets operation ${operation}`);
@@ -54569,7 +54997,7 @@ async function runRemoteSecretsCommand(operation, flags, io, options) {
54569
54997
  }
54570
54998
  let body;
54571
54999
  if (operation !== "list") {
54572
- const value = options.readValue ? options.readValue() : readFileSync70(0, "utf8").replace(/\r?\n$/, "");
55000
+ const value = options.readValue ? options.readValue() : readFileSync71(0, "utf8").replace(/\r?\n$/, "");
54573
55001
  body = JSON.stringify({ value });
54574
55002
  }
54575
55003
  const response = await (options.fetchImpl ?? fetch)(url, { method: operation === "list" ? "GET" : operation === "set" ? "PUT" : "POST", headers: headers2, body });
@@ -54605,15 +55033,15 @@ __export(notify_exports, {
54605
55033
  runNotify: () => runNotify,
54606
55034
  saveAdminSession: () => saveAdminSession
54607
55035
  });
54608
- import { chmodSync as chmodSync3, mkdirSync as mkdirSync28, readFileSync as readFileSync71, writeFileSync as writeFileSync25 } from "node:fs";
55036
+ import { chmodSync as chmodSync3, mkdirSync as mkdirSync28, readFileSync as readFileSync72, writeFileSync as writeFileSync25 } from "node:fs";
54609
55037
  import { homedir as homedir6 } from "node:os";
54610
- import { dirname as dirname32, join as join58 } from "node:path";
55038
+ import { dirname as dirname32, join as join59 } from "node:path";
54611
55039
  function defaultSessionsPath() {
54612
- return process.env.HIMI_ADMIN_SESSIONS_FILE ?? join58(homedir6(), ".himalaya", "admin-sessions.json");
55040
+ return process.env.HIMI_ADMIN_SESSIONS_FILE ?? join59(homedir6(), ".himalaya", "admin-sessions.json");
54613
55041
  }
54614
55042
  function loadSessionsFile(path2 = defaultSessionsPath()) {
54615
55043
  try {
54616
- return JSON.parse(readFileSync71(path2, "utf8"));
55044
+ return JSON.parse(readFileSync72(path2, "utf8"));
54617
55045
  } catch {
54618
55046
  return {};
54619
55047
  }
@@ -54888,11 +55316,11 @@ function resolveWixUrl(path2) {
54888
55316
  if (/^https?:\/\//i.test(path2)) return path2;
54889
55317
  return `${WIX_API3}${path2}`;
54890
55318
  }
54891
- function classifyHttp(status, count) {
55319
+ function classifyHttp(status, count2) {
54892
55320
  if (status === 428) return "not-installed";
54893
55321
  if (status === 404) return "wrong-path";
54894
55322
  if (status && status >= 400) return "error";
54895
- return count > 0 ? "ok" : "empty";
55323
+ return count2 > 0 ? "ok" : "empty";
54896
55324
  }
54897
55325
  function makeTransport(fetchImpl, auth, timeoutMs = DEFAULT_TIMEOUT_MS4) {
54898
55326
  return async (method, path2, body) => {
@@ -55135,9 +55563,9 @@ function planDetail(p) {
55135
55563
  const pricing = p.pricing && typeof p.pricing === "object" ? p.pricing : void 0;
55136
55564
  const sub = pricing?.subscription && typeof pricing.subscription === "object" ? pricing.subscription : void 0;
55137
55565
  const cycleDur = sub?.cycleDuration && typeof sub.cycleDuration === "object" ? sub.cycleDuration : void 0;
55138
- const count = cycleDur?.count;
55566
+ const count2 = cycleDur?.count;
55139
55567
  const unit = str4(cycleDur?.unit ?? cycleDur?.period).toLowerCase();
55140
- const cycle = typeof count === "number" && unit ? `${count} ${unit}` : str4(sub?.cycle ?? pricing?.cycle ?? p.period ?? p.billingPeriod);
55568
+ const cycle = typeof count2 === "number" && unit ? `${count2} ${unit}` : str4(sub?.cycle ?? pricing?.cycle ?? p.period ?? p.billingPeriod);
55141
55569
  const perks = asArr(p.perks ?? p.benefits).map((x) => str4(x) || str4(x.name)).filter(Boolean);
55142
55570
  const bits = [cycle, perks.slice(0, 4).join("; ")].filter(Boolean);
55143
55571
  return bits.length ? bits.join(" \xB7 ") : void 0;
@@ -55778,9 +56206,9 @@ function deriveTailoring(ctx, overrides = {}, revision = "site-derived") {
55778
56206
  }
55779
56207
  function bounded(s, max) {
55780
56208
  if (s.length <= max) return s;
55781
- const cut = s.slice(0, max - 1);
55782
- const space = cut.lastIndexOf(" ");
55783
- return `${(space > max * 0.6 ? cut.slice(0, space) : cut).trimEnd()}\u2026`;
56209
+ const cut2 = s.slice(0, max - 1);
56210
+ const space = cut2.lastIndexOf(" ");
56211
+ return `${(space > max * 0.6 ? cut2.slice(0, space) : cut2).trimEnd()}\u2026`;
55784
56212
  }
55785
56213
  function deriveStaticFaq(faq) {
55786
56214
  if (!Array.isArray(faq)) return [];
@@ -57478,11 +57906,11 @@ function editorText(fields, probes) {
57478
57906
  function stripLead(name) {
57479
57907
  const [left = "", ...rest] = name.split(/\s+&\s+/);
57480
57908
  const words = left.split(/\s+/);
57481
- let cut = 0;
57909
+ let cut2 = 0;
57482
57910
  words.forEach((w, i) => {
57483
- if (LEAD_STOPWORDS.has(w.toLowerCase())) cut = i + 1;
57911
+ if (LEAD_STOPWORDS.has(w.toLowerCase())) cut2 = i + 1;
57484
57912
  });
57485
- const kept = words.slice(cut).join(" ");
57913
+ const kept = words.slice(cut2).join(" ");
57486
57914
  return kept ? [kept, ...rest].join(" & ") : "";
57487
57915
  }
57488
57916
  function nameMismatches(identity, look, fields) {
@@ -57533,13 +57961,13 @@ function duplicateMedia(look, probes) {
57533
57961
  const k = mediaKey(u);
57534
57962
  counts.set(k, (counts.get(k) ?? 0) + 1);
57535
57963
  }
57536
- for (const [k, count] of counts) {
57537
- if (count < DUPLICATE_MEDIA_MIN || out.length >= CAP3.perKind) continue;
57964
+ for (const [k, count2] of counts) {
57965
+ if (count2 < DUPLICATE_MEDIA_MIN || out.length >= CAP3.perKind) continue;
57538
57966
  out.push({
57539
57967
  kind: "duplicate-media",
57540
57968
  value: k.slice(0, 80),
57541
57969
  where: [where],
57542
- note: `the same image is used ${count} times here \u2014 template filler, not ${count} distinct photos; do not present it as the business's gallery`
57970
+ note: `the same image is used ${count2} times here \u2014 template filler, not ${count2} distinct photos; do not present it as the business's gallery`
57543
57971
  });
57544
57972
  }
57545
57973
  };
@@ -58382,7 +58810,7 @@ var init_brand_font = __esm({
58382
58810
 
58383
58811
  // src/site-analyze/write-artifacts.ts
58384
58812
  import { mkdirSync as mkdirSync29, writeFileSync as writeFileSync26 } from "node:fs";
58385
- import { join as join59 } from "node:path";
58813
+ import { join as join60 } from "node:path";
58386
58814
  function attachVeloSources(book, sources, put) {
58387
58815
  const byPath = new Map((sources ?? []).map((s) => [s.path, s.text]));
58388
58816
  for (const f of book.velo.files) {
@@ -58537,12 +58965,12 @@ async function downloadMedia(book, fetchImpl, put) {
58537
58965
  }
58538
58966
  async function writeBusinessBook(result, opts) {
58539
58967
  const out = opts.outDir;
58540
- mkdirSync29(join59(out, "evidence/pages"), { recursive: true });
58541
- mkdirSync29(join59(out, "media/catalog"), { recursive: true });
58968
+ mkdirSync29(join60(out, "evidence/pages"), { recursive: true });
58969
+ mkdirSync29(join60(out, "media/catalog"), { recursive: true });
58542
58970
  const files = [];
58543
58971
  const put = (rel, body) => {
58544
- const p = join59(out, rel);
58545
- mkdirSync29(join59(p, ".."), { recursive: true });
58972
+ const p = join60(out, rel);
58973
+ mkdirSync29(join60(p, ".."), { recursive: true });
58546
58974
  writeFileSync26(p, body);
58547
58975
  files.push(rel);
58548
58976
  };
@@ -58636,8 +59064,8 @@ var init_customer_build = __esm({
58636
59064
  });
58637
59065
 
58638
59066
  // src/site-analyze/apply.ts
58639
- import { existsSync as existsSync61, readFileSync as readFileSync72, writeFileSync as writeFileSync27, copyFileSync as copyFileSync3, mkdirSync as mkdirSync30, realpathSync as realpathSync8, statSync as statSync16 } from "node:fs";
58640
- import { dirname as dirname33, isAbsolute as isAbsolute4, join as join60, resolve as resolve45, sep as sep14 } from "node:path";
59067
+ import { existsSync as existsSync62, readFileSync as readFileSync73, writeFileSync as writeFileSync27, copyFileSync as copyFileSync3, mkdirSync as mkdirSync30, realpathSync as realpathSync8, statSync as statSync16 } from "node:fs";
59068
+ import { dirname as dirname33, isAbsolute as isAbsolute4, join as join61, resolve as resolve45, sep as sep14 } from "node:path";
58641
59069
  import { spawnSync as spawnSync4 } from "node:child_process";
58642
59070
  function unpairedRedirectNote(overlay) {
58643
59071
  if (!overlay.wix?.clientId || overlay.wix.redirectUri) return void 0;
@@ -58652,7 +59080,7 @@ function committedShellProvisioning(path2) {
58652
59080
  function findRepoRoot(cwd) {
58653
59081
  let dir = cwd;
58654
59082
  for (let i = 0; i < 8; i++) {
58655
- if (SHELL_NAMES2.some((name) => existsSync61(join60(dir, shellAppDir(name), "package.json")))) return dir;
59083
+ if (SHELL_NAMES2.some((name) => existsSync62(join61(dir, shellAppDir(name), "package.json")))) return dir;
58656
59084
  const next = dirname33(dir);
58657
59085
  if (next === dir) break;
58658
59086
  dir = next;
@@ -58721,11 +59149,11 @@ function mergeCustomerOwned(committed, overlay) {
58721
59149
  }
58722
59150
  function buildMergedContract(options) {
58723
59151
  const { appDir: appDir2, packageDir, overlay } = options;
58724
- const committedPath = join60(appDir2, "provisioning.json");
58725
- if (!existsSync61(committedPath)) {
59152
+ const committedPath = join61(appDir2, "provisioning.json");
59153
+ if (!existsSync62(committedPath)) {
58726
59154
  throw new Error(`shell has no committed provisioning.json at ${committedPath} to build a contract from`);
58727
59155
  }
58728
- const committed = JSON.parse(readFileSync72(committedPath, "utf8"));
59156
+ const committed = JSON.parse(readFileSync73(committedPath, "utf8"));
58729
59157
  const merged = mergeCustomerOwned(committed, overlay);
58730
59158
  const overlayFontFamily = (overlay.branding ?? {}).fontFamily;
58731
59159
  const branding = merged.branding ?? {};
@@ -58737,8 +59165,8 @@ function buildMergedContract(options) {
58737
59165
  };
58738
59166
  const stage = (fromRel, toRel) => {
58739
59167
  const from = contained(packageDir, fromRel);
58740
- const to = contained(join60(appDir2, STAGED), toRel.slice(STAGED.length + 1));
58741
- if (!from || !to || !existsSync61(from)) return void 0;
59168
+ const to = contained(join61(appDir2, STAGED), toRel.slice(STAGED.length + 1));
59169
+ if (!from || !to || !existsSync62(from)) return void 0;
58742
59170
  let real;
58743
59171
  let realRoot;
58744
59172
  try {
@@ -58760,11 +59188,11 @@ function buildMergedContract(options) {
58760
59188
  };
58761
59189
  const icon = stage("media/icon.png", `${STAGED}/icon.png`);
58762
59190
  if (icon) branding.appIconPath = icon;
58763
- const facesPath = join60(packageDir, "media/fonts/faces.json");
58764
- if (existsSync61(facesPath)) {
59191
+ const facesPath = join61(packageDir, "media/fonts/faces.json");
59192
+ if (existsSync62(facesPath)) {
58765
59193
  let faces = [];
58766
59194
  try {
58767
- const parsed = JSON.parse(readFileSync72(facesPath, "utf8"));
59195
+ const parsed = JSON.parse(readFileSync73(facesPath, "utf8"));
58768
59196
  faces = Array.isArray(parsed) ? parsed : [];
58769
59197
  } catch {
58770
59198
  faces = [];
@@ -58785,9 +59213,9 @@ function buildMergedContract(options) {
58785
59213
  merged.branding = branding;
58786
59214
  const body = `${JSON.stringify(merged, null, 2)}
58787
59215
  `;
58788
- const path2 = join60(packageDir, "provisioning.contract.json");
59216
+ const path2 = join61(packageDir, "provisioning.contract.json");
58789
59217
  writeFileSync27(path2, body);
58790
- const localPath = join60(appDir2, LOCAL_CONTRACT_REL);
59218
+ const localPath = join61(appDir2, LOCAL_CONTRACT_REL);
58791
59219
  mkdirSync30(dirname33(localPath), { recursive: true });
58792
59220
  writeFileSync27(localPath, body);
58793
59221
  staged.push(localPath);
@@ -58822,10 +59250,10 @@ function catalogTypography(overlay, template) {
58822
59250
  function staticFaqFor(overlay, overlayPath) {
58823
59251
  const own3 = overlay.faq;
58824
59252
  if (own3 !== void 0) return deriveStaticFaq(own3);
58825
- const bookPath = join60(dirname33(resolve45(overlayPath)), "book.json");
58826
- if (!existsSync61(bookPath)) return void 0;
59253
+ const bookPath = join61(dirname33(resolve45(overlayPath)), "book.json");
59254
+ if (!existsSync62(bookPath)) return void 0;
58827
59255
  try {
58828
- const book = JSON.parse(readFileSync72(bookPath, "utf8"));
59256
+ const book = JSON.parse(readFileSync73(bookPath, "utf8"));
58829
59257
  const copy = book?.copy;
58830
59258
  if (!copy || typeof copy !== "object" || copy.unavailable) return void 0;
58831
59259
  const pairs = deriveStaticFaq(copy.faq);
@@ -58839,8 +59267,8 @@ async function applyOverlays(input) {
58839
59267
  const cwd = resolve45(input.cwd);
58840
59268
  const shell = input.shell ?? DEFAULT_BRANDABLE_SHELL;
58841
59269
  assertKnownShell(shell);
58842
- const overlay = JSON.parse(readFileSync72(input.overlayPath, "utf8"));
58843
- const tokens = JSON.parse(readFileSync72(input.tokensPath, "utf8"));
59270
+ const overlay = JSON.parse(readFileSync73(input.overlayPath, "utf8"));
59271
+ const tokens = JSON.parse(readFileSync73(input.tokensPath, "utf8"));
58844
59272
  const faq = staticFaqFor(overlay, input.overlayPath);
58845
59273
  if (faq !== void 0) overlay.faq = faq;
58846
59274
  const wrote = [];
@@ -58852,7 +59280,7 @@ async function applyOverlays(input) {
58852
59280
  `shell "${shell}" cannot bake a customer brand \u2014 it ships no prepare:customer script`
58853
59281
  );
58854
59282
  }
58855
- const appDir2 = join60(repoRoot2, shellAppDir(shell));
59283
+ const appDir2 = join61(repoRoot2, shellAppDir(shell));
58856
59284
  const packageDir = dirname33(resolve45(input.overlayPath));
58857
59285
  const { path: contract, staged } = buildMergedContract({ appDir: appDir2, packageDir, overlay });
58858
59286
  wrote.push(...staged);
@@ -58874,11 +59302,11 @@ async function applyOverlays(input) {
58874
59302
  note: `prepare:customer --contract with a merged contract (committed provisioning.json untouched)${unpairedInRepo ? ` \xB7 ${unpairedInRepo}` : ""}`
58875
59303
  };
58876
59304
  }
58877
- const pkgProv = join60(cwd, "provisioning.json");
59305
+ const pkgProv = join61(cwd, "provisioning.json");
58878
59306
  assertNotCommitted(pkgProv, input.forbiddenProvisioning);
58879
59307
  let packageFaq = faq;
58880
- if (existsSync61(pkgProv)) {
58881
- const cur = JSON.parse(readFileSync72(pkgProv, "utf8"));
59308
+ if (existsSync62(pkgProv)) {
59309
+ const cur = JSON.parse(readFileSync73(pkgProv, "utf8"));
58882
59310
  const merged = deepMerge4(cur, overlay);
58883
59311
  packageFaq = merged.faq;
58884
59312
  writeFileSync27(pkgProv, JSON.stringify(merged, null, 2) + "\n");
@@ -58887,9 +59315,9 @@ async function applyOverlays(input) {
58887
59315
  writeFileSync27(pkgProv, JSON.stringify(overlay, null, 2) + "\n");
58888
59316
  wrote.push(pkgProv);
58889
59317
  }
58890
- const pkgTokens = join60(cwd, "tokens.json");
58891
- if (existsSync61(pkgTokens) && (tokens.colors || tokens.typography)) {
58892
- const cur = JSON.parse(readFileSync72(pkgTokens, "utf8"));
59318
+ const pkgTokens = join61(cwd, "tokens.json");
59319
+ if (existsSync62(pkgTokens) && (tokens.colors || tokens.typography)) {
59320
+ const cur = JSON.parse(readFileSync73(pkgTokens, "utf8"));
58893
59321
  let tokensIn = tokens;
58894
59322
  if (tokens.typography) {
58895
59323
  const fonts = catalogTypography(tokens.typography, cur.typography);
@@ -58914,9 +59342,9 @@ async function applyOverlays(input) {
58914
59342
  wrote.push(pkgTokens);
58915
59343
  }
58916
59344
  const homeAction = homeRefreshActionId(shell);
58917
- const homeSrc = join60(cwd, "dev/index.ts");
58918
- if (homeAction && existsSync61(homeSrc)) {
58919
- const prev = readFileSync72(homeSrc, "utf8");
59345
+ const homeSrc = join61(cwd, "dev/index.ts");
59346
+ if (homeAction && existsSync62(homeSrc)) {
59347
+ const prev = readFileSync73(homeSrc, "utf8");
58920
59348
  const patched = patchBrandParams(prev, overlay, homeAction);
58921
59349
  let next = patched.src;
58922
59350
  if (!patched.foreign && faq?.length && prev.includes('"tier5.himiBrandedFaq.load"') && !prev.includes('"staticFaq"')) {
@@ -58947,13 +59375,13 @@ async function applyOverlays(input) {
58947
59375
  notes.push(`${patched.unparsed} onAppear params block(s) in ${homeSrc} could not be read \u2014 any brand values inside them were not applied`);
58948
59376
  }
58949
59377
  }
58950
- if (!input.iconPath || !existsSync61(input.iconPath)) {
59378
+ if (!input.iconPath || !existsSync62(input.iconPath)) {
58951
59379
  const book = input.iconPath ? dirname33(dirname33(resolve45(input.iconPath))) : void 0;
58952
59380
  notes.push(
58953
59381
  book ? `no icon installed \u2014 ${siteLogoAbsence(book).hint}` : "no icon installed and no business book to say why \u2014 `himi icon generate` draws one from the accent, or `himi icon set <file.png>` installs the customer's own mark"
58954
59382
  );
58955
59383
  }
58956
- if (input.iconPath && existsSync61(input.iconPath)) {
59384
+ if (input.iconPath && existsSync62(input.iconPath)) {
58957
59385
  try {
58958
59386
  const installed = await installIcon(cwd, input.iconPath, { derivePlay: true });
58959
59387
  wrote.push(...installed.wrote);
@@ -63140,7 +63568,7 @@ __export(cli_exports, {
63140
63568
  runSiteAnalyze: () => runSiteAnalyze,
63141
63569
  runSiteFunctionCli: () => runSiteFunctionCli
63142
63570
  });
63143
- import { readFileSync as readFileSync73 } from "node:fs";
63571
+ import { readFileSync as readFileSync74 } from "node:fs";
63144
63572
  import { resolve as resolve46 } from "node:path";
63145
63573
  function assetFetchFor(_opts) {
63146
63574
  return pinnedFetch2;
@@ -63239,7 +63667,7 @@ async function runSiteFunctionCli(opts) {
63239
63667
  let source;
63240
63668
  if (opts.sourcePath) {
63241
63669
  const p = resolve46(opts.cwd ?? process.cwd(), opts.sourcePath);
63242
- source = readFileSync73(p, "utf8");
63670
+ source = readFileSync74(p, "utf8");
63243
63671
  }
63244
63672
  let body;
63245
63673
  if (opts.bodyRaw != null) {
@@ -63498,7 +63926,7 @@ __export(site_create_exports, {
63498
63926
  runSiteList: () => runSiteList,
63499
63927
  writeProvisioningBinding: () => writeProvisioningBinding
63500
63928
  });
63501
- import { existsSync as existsSync62, readFileSync as readFileSync74, writeFileSync as writeFileSync28 } from "node:fs";
63929
+ import { existsSync as existsSync63, readFileSync as readFileSync75, writeFileSync as writeFileSync28 } from "node:fs";
63502
63930
  import { resolve as resolvePath2 } from "node:path";
63503
63931
  function oauthClientName(projectName) {
63504
63932
  return `${projectName} client`;
@@ -63647,7 +64075,7 @@ async function runSiteCreate(opts) {
63647
64075
  if (!wantOauthApp) return { ...base, nextSteps: nextSteps(metaSiteId) };
63648
64076
  return { ...base, clientId, redirectUri, nextSteps: nextSteps(metaSiteId, clientId) };
63649
64077
  }
63650
- function writeProvisioningBinding(target, binding2, fs = { existsSync: existsSync62, readFileSync: readFileSync74, writeFileSync: writeFileSync28 }) {
64078
+ function writeProvisioningBinding(target, binding2, fs = { existsSync: existsSync63, readFileSync: readFileSync75, writeFileSync: writeFileSync28 }) {
63651
64079
  const msid = binding2.siteId;
63652
64080
  const path2 = resolvePath2(target);
63653
64081
  const committed = committedShellProvisioning(path2);
@@ -64320,11 +64748,11 @@ var init_context = __esm({
64320
64748
  });
64321
64749
 
64322
64750
  // src/cli.ts
64323
- import { mkdtempSync as mkdtempSync4, readFileSync as readFileSync75, mkdirSync as mkdirSync31, writeFileSync as writeFileSync29, existsSync as existsSync63, rmSync as rmSync14, readdirSync as readdirSync29, statSync as statSync17, lstatSync as lstatSync11, accessSync, constants as constants2, renameSync as renameSync7, cpSync as cpSync3 } from "node:fs";
64751
+ import { mkdtempSync as mkdtempSync4, readFileSync as readFileSync76, mkdirSync as mkdirSync31, writeFileSync as writeFileSync29, existsSync as existsSync64, rmSync as rmSync14, readdirSync as readdirSync30, statSync as statSync17, lstatSync as lstatSync11, accessSync, constants as constants2, renameSync as renameSync7, cpSync as cpSync3 } from "node:fs";
64324
64752
  import { execFile, execFileSync as execFileSync7, spawnSync as spawnSync5, spawn as spawn6 } from "node:child_process";
64325
64753
  import { createHash as createHash28, randomUUID as randomUUID5 } from "node:crypto";
64326
64754
  import { tmpdir as tmpdir5, homedir as homedir7, hostname } from "node:os";
64327
- import { join as join61, resolve as resolve47, relative as relative11, basename as basename15, dirname as dirname34 } from "node:path";
64755
+ import { join as join62, resolve as resolve47, relative as relative11, basename as basename15, dirname as dirname34 } from "node:path";
64328
64756
  import { fileURLToPath as fileURLToPath24 } from "node:url";
64329
64757
 
64330
64758
  // ../serve-cli/src/client.ts
@@ -65525,14 +65953,14 @@ async function seedCms(opts) {
65525
65953
  report2.ok = false;
65526
65954
  continue;
65527
65955
  }
65528
- const count = await countItems(call, id);
65529
- if (count === null && !force) {
65956
+ const count2 = await countItems(call, id);
65957
+ if (count2 === null && !force) {
65530
65958
  report2.skipped.push({ collection: id, reason: "could not read the current row count, so seeding might bury existing content. Re-run, or pass --force if you are sure." });
65531
65959
  report2.ok = false;
65532
65960
  continue;
65533
65961
  }
65534
- if (count !== null && count > 0 && !force) {
65535
- report2.skipped.push({ collection: id, reason: `already has ${count} items \u2014 seeding an already-populated collection needs --force so real content is never buried under sample rows` });
65962
+ if (count2 !== null && count2 > 0 && !force) {
65963
+ report2.skipped.push({ collection: id, reason: `already has ${count2} items \u2014 seeding an already-populated collection needs --force so real content is never buried under sample rows` });
65536
65964
  continue;
65537
65965
  }
65538
65966
  if (!dryRun) {
@@ -65623,7 +66051,7 @@ function placeholder(type, key2, i) {
65623
66051
  }
65624
66052
  }
65625
66053
  async function writeSeedRows(opts) {
65626
- const { app, cms, dir, count = 8 } = opts;
66054
+ const { app, cms, dir, count: count2 = 8 } = opts;
65627
66055
  const file = join8(dir, "cms-seed.json");
65628
66056
  const owned = (cms?.collections ?? []).filter((s) => s.id?.startsWith(namespacePrefix(app)));
65629
66057
  if (!owned.length) {
@@ -65654,10 +66082,10 @@ async function writeSeedRows(opts) {
65654
66082
  const collections = {};
65655
66083
  for (const spec of owned) {
65656
66084
  existing[spec.id] = Array.from(
65657
- { length: count },
66085
+ { length: count2 },
65658
66086
  (_, i) => Object.fromEntries(spec.fields.map((f) => [f.key, placeholder(f.type, f.displayName || f.key, i)]))
65659
66087
  );
65660
- collections[spec.id] = count;
66088
+ collections[spec.id] = count2;
65661
66089
  }
65662
66090
  writeFileSync6(file, `${JSON.stringify(existing, null, 2)}
65663
66091
  `);
@@ -65672,7 +66100,7 @@ ${s.fields.map((f) => ` ${f.key} (${f.type})`).join("\n")}`
65672
66100
  schema,
65673
66101
  ``,
65674
66102
  `Rules:`,
65675
- ` - Keep the exact same JSON shape: one key per collection id, an array of ${count} row objects.`,
66103
+ ` - Keep the exact same JSON shape: one key per collection id, an array of ${count2} row objects.`,
65676
66104
  ` - Use ONLY the field keys listed above, and keep each value's JSON type (TEXT\u2192string, NUMBER\u2192number, BOOLEAN\u2192boolean, DATE/DATETIME\u2192ISO string, IMAGE/VIDEO/AUDIO/DOCUMENT\u2192an https URL).`,
65677
66105
  ` - Make the content specific and plausible for this app's domain \u2014 real-sounding names, sensible prices, varied categories. No "Lorem ipsum", no "Sample item 1".`,
65678
66106
  ` - Leave any collection key already in the file that is not listed above untouched.`,
@@ -66440,8 +66868,8 @@ var str6 = (v) => typeof v === "string" ? v : void 0;
66440
66868
  var num2 = (v) => typeof v === "string" && v !== "" && Number.isFinite(Number(v)) ? Number(v) : void 0;
66441
66869
  function embeddedTemplateCatalog() {
66442
66870
  return availableTemplates().map((name) => {
66443
- const path2 = join61(templateSourceDir(name), TEMPLATE_META_FILE);
66444
- const meta = JSON.parse(readFileSync75(path2, "utf8"));
66871
+ const path2 = join62(templateSourceDir(name), TEMPLATE_META_FILE);
66872
+ const meta = JSON.parse(readFileSync76(path2, "utf8"));
66445
66873
  if (!meta || typeof meta.title !== "string" || typeof meta.description !== "string" || !Array.isArray(meta.tags)) {
66446
66874
  throw new Error(`${path2} must be { title: string, description: string, tags: string[], category?: string }`);
66447
66875
  }
@@ -67120,7 +67548,7 @@ var COMMANDS = [
67120
67548
  ];
67121
67549
  function cliVersion() {
67122
67550
  try {
67123
- return JSON.parse(readFileSync75(new URL("../package.json", import.meta.url), "utf8")).version ?? "unknown";
67551
+ return JSON.parse(readFileSync76(new URL("../package.json", import.meta.url), "utf8")).version ?? "unknown";
67124
67552
  } catch {
67125
67553
  return "unknown";
67126
67554
  }
@@ -67238,7 +67666,7 @@ function ageInDays(publishedAt, now = Date.now()) {
67238
67666
  }
67239
67667
  function packageVersion(path2) {
67240
67668
  try {
67241
- const version2 = JSON.parse(readFileSync75(path2, "utf8")).version;
67669
+ const version2 = JSON.parse(readFileSync76(path2, "utf8")).version;
67242
67670
  return typeof version2 === "string" && version2 ? version2 : void 0;
67243
67671
  } catch {
67244
67672
  return void 0;
@@ -67247,7 +67675,7 @@ function packageVersion(path2) {
67247
67675
  function resolvedSdkVersion(fromDir = process.cwd(), bundledManifest = new URL("./authoring-runtime/node_modules/@wix/himalaya/package.json", import.meta.url)) {
67248
67676
  let dir = resolve47(fromDir);
67249
67677
  for (; ; ) {
67250
- const version2 = packageVersion(join61(dir, "node_modules", "@wix", "himalaya", "package.json"));
67678
+ const version2 = packageVersion(join62(dir, "node_modules", "@wix", "himalaya", "package.json"));
67251
67679
  if (version2) return { version: version2, source: "local" };
67252
67680
  const parent = dirname34(dir);
67253
67681
  if (parent === dir) break;
@@ -67257,7 +67685,7 @@ function resolvedSdkVersion(fromDir = process.cwd(), bundledManifest = new URL("
67257
67685
  if (bundled) return { version: bundled, source: "bundled" };
67258
67686
  try {
67259
67687
  const root = execFileSync7("npm", ["root", "-g"], { stdio: ["ignore", "pipe", "ignore"] }).toString().trim();
67260
- const version2 = root ? packageVersion(join61(root, "@wix", "himalaya", "package.json")) : void 0;
67688
+ const version2 = root ? packageVersion(join62(root, "@wix", "himalaya", "package.json")) : void 0;
67261
67689
  return version2 ? { version: version2, source: "global" } : {};
67262
67690
  } catch {
67263
67691
  return {};
@@ -67304,11 +67732,11 @@ var UPDATE_RETRY_AFTER_FAILURE_MS = 60 * 60 * 1e3;
67304
67732
  var UPDATE_SNOOZE_MS = 24 * 60 * 60 * 1e3;
67305
67733
  var UPDATE_REEXEC_ENV = "HIMI_UPDATED_ONCE";
67306
67734
  function updateCachePath(env = process.env) {
67307
- return env.HIMI_UPDATE_CACHE_FILE ?? join61(homedir7(), ".himalaya", "update-check.json");
67735
+ return env.HIMI_UPDATE_CACHE_FILE ?? join62(homedir7(), ".himalaya", "update-check.json");
67308
67736
  }
67309
67737
  function readUpdateCache(path2) {
67310
67738
  try {
67311
- const parsed = JSON.parse(readFileSync75(path2, "utf8"));
67739
+ const parsed = JSON.parse(readFileSync76(path2, "utf8"));
67312
67740
  return parsed && typeof parsed === "object" ? parsed : {};
67313
67741
  } catch {
67314
67742
  return {};
@@ -67376,12 +67804,12 @@ function createUpdateRunner(spawn7 = spawnSync5) {
67376
67804
  },
67377
67805
  installedVersion: () => {
67378
67806
  const root = globalNodeModulesRoot();
67379
- return root ? packageVersion(join61(root, "@wix", "himalaya-cli", "package.json")) : void 0;
67807
+ return root ? packageVersion(join62(root, "@wix", "himalaya-cli", "package.json")) : void 0;
67380
67808
  },
67381
67809
  reexec: (argv) => {
67382
67810
  const root = globalNodeModulesRoot();
67383
- const entry = root ? join61(root, "@wix", "himalaya-cli", "dist", "cli.mjs") : void 0;
67384
- if (!entry || !existsSync63(entry)) return void 0;
67811
+ const entry = root ? join62(root, "@wix", "himalaya-cli", "dist", "cli.mjs") : void 0;
67812
+ if (!entry || !existsSync64(entry)) return void 0;
67385
67813
  const result = spawnSync5(process.execPath, [entry, ...argv], {
67386
67814
  // inherit keeps PIPED STDIN working — commands that read stdin must survive a re-exec.
67387
67815
  stdio: "inherit",
@@ -67428,7 +67856,7 @@ function staleUpdateLockFile(cachePath, now) {
67428
67856
  for (const path2 of [`${cachePath}.lock.break`, `${cachePath}.lock`]) {
67429
67857
  let content;
67430
67858
  try {
67431
- content = readFileSync75(path2, "utf8");
67859
+ content = readFileSync76(path2, "utf8");
67432
67860
  } catch {
67433
67861
  continue;
67434
67862
  }
@@ -67451,7 +67879,7 @@ function acquireUpdateLock(cachePath, now, hooks = {}) {
67451
67879
  const mine = lockStamp(now);
67452
67880
  const readLock = (path2) => {
67453
67881
  try {
67454
- return readFileSync75(path2, "utf8");
67882
+ return readFileSync76(path2, "utf8");
67455
67883
  } catch {
67456
67884
  return void 0;
67457
67885
  }
@@ -68035,7 +68463,7 @@ function executableOnPath(name, env = process.env) {
68035
68463
  const suffixes = process.platform === "win32" ? (env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD").split(";").filter(Boolean) : [""];
68036
68464
  for (const dir of entries) {
68037
68465
  for (const suffix of suffixes) {
68038
- const candidate = join61(dir, `${name}${suffix}`);
68466
+ const candidate = join62(dir, `${name}${suffix}`);
68039
68467
  try {
68040
68468
  if (!statSync17(candidate).isFile()) continue;
68041
68469
  if (process.platform !== "win32") accessSync(candidate, constants2.X_OK);
@@ -68144,7 +68572,7 @@ async function nativeConfigAdvisories(app, configSource) {
68144
68572
  const { iosProjectYml: iosProjectYml2 } = await Promise.resolve().then(() => (init_paths2(), paths_exports));
68145
68573
  const { androidOptedIn: androidOptedIn3, LABEL_IOS_SPLASH: LABEL_IOS_SPLASH2 } = await Promise.resolve().then(() => (init_splash(), splash_exports));
68146
68574
  const iosYmlPath = iosProjectYml2(app);
68147
- const iosYml = existsSync63(iosYmlPath) ? readFileSync75(iosYmlPath, "utf8") : "";
68575
+ const iosYml = existsSync64(iosYmlPath) ? readFileSync76(iosYmlPath, "utf8") : "";
68148
68576
  let optedIn = iosYml.includes(LABEL_IOS_SPLASH2);
68149
68577
  if (!optedIn) {
68150
68578
  const { androidManifestPath: androidManifestPath3 } = await Promise.resolve().then(() => (init_gen_native_config(), gen_native_config_exports));
@@ -68158,7 +68586,7 @@ async function nativeConfigAdvisories(app, configSource) {
68158
68586
  }
68159
68587
  function readConfigSource(dir) {
68160
68588
  try {
68161
- return readFileSync75(join61(dir, "config.ts"), "utf8");
68589
+ return readFileSync76(join62(dir, "config.ts"), "utf8");
68162
68590
  } catch {
68163
68591
  return null;
68164
68592
  }
@@ -68194,7 +68622,7 @@ function keptPlan(kept) {
68194
68622
  }
68195
68623
  async function initFromRemoteTemplate(name, parentDir, template, c) {
68196
68624
  const dir = resolve47(parentDir, name);
68197
- if (existsSync63(join61(dir, "himalaya.content.json"))) {
68625
+ if (existsSync64(join62(dir, "himalaya.content.json"))) {
68198
68626
  throw new Error(`content package already exists at ${dir}`);
68199
68627
  }
68200
68628
  const r = await c.fetchTemplateSource(TEMPLATE_APP_PREFIX + template);
@@ -68237,11 +68665,11 @@ function symlinkDestinationIssue(dest) {
68237
68665
  function restoreDestinationIssue(dest, force) {
68238
68666
  const link = symlinkDestinationIssue(dest);
68239
68667
  if (link) return link;
68240
- if (!existsSync63(dest)) return null;
68668
+ if (!existsSync64(dest)) return null;
68241
68669
  if (!statSync17(dest).isDirectory()) {
68242
68670
  return { error: `${dest} exists and is not a directory`, hint: "pass --out/--name to land somewhere else, or remove the file" };
68243
68671
  }
68244
- if (readdirSync29(dest).length > 0 && !force) {
68672
+ if (readdirSync30(dest).length > 0 && !force) {
68245
68673
  return {
68246
68674
  error: `${dest} already exists and is not empty`,
68247
68675
  hint: "pass --force to write into it anyway, or --out/--name to land somewhere else"
@@ -68252,7 +68680,7 @@ function restoreDestinationIssue(dest, force) {
68252
68680
  function isHimalayaMonorepoCheckout(start) {
68253
68681
  let dir = resolve47(start);
68254
68682
  for (; ; ) {
68255
- if (existsSync63(join61(dir, "tools", "himi-cli", "src", "cli.ts")) && existsSync63(join61(dir, "core", "serve", "src", "server.ts"))) return true;
68683
+ if (existsSync64(join62(dir, "tools", "himi-cli", "src", "cli.ts")) && existsSync64(join62(dir, "core", "serve", "src", "server.ts"))) return true;
68256
68684
  const parent = dirname34(dir);
68257
68685
  if (parent === dir) return false;
68258
68686
  dir = parent;
@@ -68313,7 +68741,7 @@ function materializeForPreview(appDir2, slug, displayName) {
68313
68741
  Object.entries(files).map(([rel, body]) => [rel, rel === "himalaya.content.json" ? substituteIdentity(body, slug) : body])
68314
68742
  );
68315
68743
  const repoRoot2 = resolve47(fileURLToPath24(new URL("../../..", import.meta.url)));
68316
- const root = join61(repoRoot2, "node_modules", ".cache", "himi-template-preview");
68744
+ const root = join62(repoRoot2, "node_modules", ".cache", "himi-template-preview");
68317
68745
  const stillRunning = (pid) => {
68318
68746
  try {
68319
68747
  process.kill(pid, 0);
@@ -68323,16 +68751,16 @@ function materializeForPreview(appDir2, slug, displayName) {
68323
68751
  }
68324
68752
  };
68325
68753
  try {
68326
- for (const entry of readdirSync29(root, { withFileTypes: true })) {
68754
+ for (const entry of readdirSync30(root, { withFileTypes: true })) {
68327
68755
  if (!entry.isDirectory()) continue;
68328
68756
  const pid = Number(entry.name.startsWith(`${slug}-`) ? entry.name.slice(slug.length + 1) : NaN);
68329
68757
  if (Number.isInteger(pid) && pid > 0 && pid !== process.pid && !stillRunning(pid)) {
68330
- rmSync14(join61(root, entry.name), { recursive: true, force: true });
68758
+ rmSync14(join62(root, entry.name), { recursive: true, force: true });
68331
68759
  }
68332
68760
  }
68333
68761
  } catch {
68334
68762
  }
68335
- const dest = join61(root, `${slug}-${process.pid}`);
68763
+ const dest = join62(root, `${slug}-${process.pid}`);
68336
68764
  rmSync14(dest, { recursive: true, force: true });
68337
68765
  mkdirSync31(dest, { recursive: true });
68338
68766
  try {
@@ -68345,8 +68773,8 @@ function materializeForPreview(appDir2, slug, displayName) {
68345
68773
  }
68346
68774
  async function appId(dir) {
68347
68775
  try {
68348
- const { readFileSync: readFileSync76 } = await import("node:fs");
68349
- const m = JSON.parse(readFileSync76(join61(dir, "himalaya.content.json"), "utf8"));
68776
+ const { readFileSync: readFileSync77 } = await import("node:fs");
68777
+ const m = JSON.parse(readFileSync77(join62(dir, "himalaya.content.json"), "utf8"));
68350
68778
  if (m.name) return m.name;
68351
68779
  } catch {
68352
68780
  }
@@ -68354,8 +68782,8 @@ async function appId(dir) {
68354
68782
  }
68355
68783
  async function contentVisibility(dir) {
68356
68784
  try {
68357
- const { readFileSync: readFileSync76 } = await import("node:fs");
68358
- const m = JSON.parse(readFileSync76(join61(dir, "himalaya.content.json"), "utf8"));
68785
+ const { readFileSync: readFileSync77 } = await import("node:fs");
68786
+ const m = JSON.parse(readFileSync77(join62(dir, "himalaya.content.json"), "utf8"));
68359
68787
  if (m.visibility === "public" || m.visibility === "unlisted") return m.visibility;
68360
68788
  } catch {
68361
68789
  }
@@ -68579,7 +69007,7 @@ Seed your coding agent with \`himi agent-init\`. Guides: ${idx.portalUrl}`);
68579
69007
  ...res.removed.length ? { removed: res.removed, note: "older copies at the app root, moved to .himi/legacy-agent-files/ (a backup, in case one became app code) \u2014 the current ones live in .agents/skills/himalaya/" } : {},
68580
69008
  // In a git repo the refresh moves the diff identity an earlier `himi test` attested (that
68581
69009
  // hash is shared with the repo gates and deliberately exempts nothing but its own metadata).
68582
- ...existsSync63(join61(dir, ".himi", "attestations", "latest.json")) ? { next: "if `himi push` then refuses the attestation as stale, re-run `himi test` \u2014 in a git checkout this refresh changed the tree it covered" } : {},
69010
+ ...existsSync64(join62(dir, ".himi", "attestations", "latest.json")) ? { next: "if `himi push` then refuses the attestation as stale, re-run `himi test` \u2014 in a git checkout this refresh changed the tree it covered" } : {},
68583
69011
  ...res.skipped.length ? { skipped: res.skipped } : {},
68584
69012
  portalUrl: idx.portalUrl
68585
69013
  });
@@ -68602,7 +69030,7 @@ async function recordedReleaseForContentQuietly(dir, resolution, onDrift) {
68602
69030
  try {
68603
69031
  const { buildContentForValidation: buildContentForValidation2, buildReleaseFromContent: buildReleaseFromContent2, bundleOutDir: bundleOutDir2 } = await loadAuthoring();
68604
69032
  const bres = await buildContentForValidation2(dir, { resolution });
68605
- out = mkdtempSync4(join61(tmpdir5(), "himi-ux-record-"));
69033
+ out = mkdtempSync4(join62(tmpdir5(), "himi-ux-record-"));
68606
69034
  const hasWorkers = bres.built.length > 0 || bres.compileErrors.length > 0;
68607
69035
  const outcome = await buildReleaseFromContent2(dir, out, { ...hasWorkers ? { tier5Dir: bundleOutDir2(dir) } : {}, collect: { lint: true } });
68608
69036
  const releaseId = outcome.manifest.releaseId;
@@ -68769,7 +69197,7 @@ async function producedWidgetFamilies(dir) {
68769
69197
  }
68770
69198
  const outDir = tier5Dir.bundleOutDir(dir);
68771
69199
  try {
68772
- readdirSync29(outDir);
69200
+ readdirSync30(outDir);
68773
69201
  } catch {
68774
69202
  return { families: void 0, source: "unbuilt" };
68775
69203
  }
@@ -68780,7 +69208,7 @@ async function producedWidgetFamilies(dir) {
68780
69208
  let js;
68781
69209
  for (const candidate of [`${name}.bundle.js`, `${name}.js`, name]) {
68782
69210
  try {
68783
- js = readFileSync75(join61(outDir, candidate), "utf8");
69211
+ js = readFileSync76(join62(outDir, candidate), "utf8");
68784
69212
  break;
68785
69213
  } catch {
68786
69214
  }
@@ -68811,12 +69239,27 @@ async function producedWidgetFamilies(dir) {
68811
69239
  }
68812
69240
  return { families, source: "dist/bundles" };
68813
69241
  }
68814
- var boardOpen = (dir) => existsSync63(join61(dir, ".himi", "studio", "index.html"));
69242
+ var boardOpen = (dir) => existsSync64(join62(dir, ".himi", "studio", "index.html"));
68815
69243
  var BOARD_REFRESHERS = /* @__PURE__ */ new Set(["render", "test", "validate", "build", "push", "icon", "site", "ux", "progress"]);
68816
69244
  async function leaveForBoard(dir, record2, value) {
68817
69245
  if (!boardOpen(dir)) return;
68818
- const { writeStudioRecord: writeStudioRecord2, VALIDATE_RECORD: VALIDATE_RECORD2, PUSH_RECORD: PUSH_RECORD2, EDGES_RECORD: EDGES_RECORD2 } = await Promise.resolve().then(() => (init_studio(), studio_exports));
68819
- writeStudioRecord2(dir, { validate: VALIDATE_RECORD2, push: PUSH_RECORD2, edges: EDGES_RECORD2 }[record2], value);
69246
+ const { writeStudioRecord: writeStudioRecord2, VALIDATE_RECORD: VALIDATE_RECORD2, PUSH_RECORD: PUSH_RECORD2, EDGES_RECORD: EDGES_RECORD2, MAP_RECORD: MAP_RECORD2 } = await Promise.resolve().then(() => (init_studio(), studio_exports));
69247
+ writeStudioRecord2(dir, { validate: VALIDATE_RECORD2, push: PUSH_RECORD2, edges: EDGES_RECORD2, map: MAP_RECORD2 }[record2], value);
69248
+ }
69249
+ async function liveMapFor(dir) {
69250
+ if (!boardOpen(dir)) return void 0;
69251
+ const { writeStudioRecord: writeStudioRecord2, refreshStudio: refreshStudio2, MAP_RECORD: MAP_RECORD2 } = await Promise.resolve().then(() => (init_studio(), studio_exports));
69252
+ let last = 0;
69253
+ return (map) => {
69254
+ const now = Date.now();
69255
+ if (now - last < 1e3) return;
69256
+ last = now;
69257
+ try {
69258
+ writeStudioRecord2(dir, MAP_RECORD2, map);
69259
+ refreshStudio2(dir);
69260
+ } catch {
69261
+ }
69262
+ };
68820
69263
  }
68821
69264
  async function run(argv, io = defaultIO, deps = {}) {
68822
69265
  const code = await runCommand(argv, io, deps);
@@ -69091,7 +69534,7 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
69091
69534
  const owed = openApprovals2(readApprovals2(dir).events).length;
69092
69535
  next.push(`carried the planning journal into the app (${carried.join(", ")}${owed ? `; ${owed} approval${owed === 1 ? "" : "s"} still owed` : ""}) \u2014 \`himi progress --brief\` there shows it`);
69093
69536
  }
69094
- const planInParent = ["SPEC.md", "MOBILE-UX.md"].filter((f) => existsSync63(join61(parentDir, f)));
69537
+ const planInParent = ["SPEC.md", "MOBILE-UX.md"].filter((f) => existsSync64(join62(parentDir, f)));
69095
69538
  if (planInParent.length) {
69096
69539
  next.push(`${planInParent.join(" and ")} in ${parentDir} ${planInParent.length > 1 ? "are" : "is"} not the app's \u2014 if that is the approved plan, move it over the template's boilerplate in ${dir}`);
69097
69540
  }
@@ -69218,7 +69661,7 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
69218
69661
  const sourceDir = resolve47(str6(flags["source-dir"]) ?? appDir2);
69219
69662
  let meta;
69220
69663
  try {
69221
- meta = JSON.parse(readFileSync75(join61(sourceDir, TEMPLATE_META_FILE), "utf8"));
69664
+ meta = JSON.parse(readFileSync76(join62(sourceDir, TEMPLATE_META_FILE), "utf8"));
69222
69665
  } catch {
69223
69666
  io.error(JSON.stringify({ error: `no ${TEMPLATE_META_FILE} in ${sourceDir} \u2014 a template must declare { title, description, tags } to be published`, hint: `add ${TEMPLATE_META_FILE} next to himalaya.content.json (or pass --source-dir <dir>)` }));
69224
69667
  return 2;
@@ -69228,7 +69671,7 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
69228
69671
  return 2;
69229
69672
  }
69230
69673
  try {
69231
- const appearance = extractTemplateAppearance(JSON.parse(readFileSync75(join61(sourceDir, "tokens.json"), "utf8")));
69674
+ const appearance = extractTemplateAppearance(JSON.parse(readFileSync76(join62(sourceDir, "tokens.json"), "utf8")));
69232
69675
  meta = { ...appearance, ...meta };
69233
69676
  } catch {
69234
69677
  }
@@ -69250,7 +69693,7 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
69250
69693
  const force = flags.force === true;
69251
69694
  const { buildContentForValidation: buildContentForValidation2, buildReleaseFromContent: buildReleaseFromContent2, bundleOutDir: bundleOutDir2 } = await loadAuthoring();
69252
69695
  const bres = await buildContentForValidation2(buildDir, { resolution: resolutionFlag(flags) });
69253
- const out = mkdtempSync4(join61(tmpdir5(), "himi-template-"));
69696
+ const out = mkdtempSync4(join62(tmpdir5(), "himi-template-"));
69254
69697
  publishScratch.push(out);
69255
69698
  const hasWorkers = bres.built.length > 0 || bres.compileErrors.length > 0;
69256
69699
  const outcome = await buildReleaseFromContent2(buildDir, out, {
@@ -69304,7 +69747,7 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
69304
69747
  let iconHash;
69305
69748
  if (iconPath) {
69306
69749
  try {
69307
- const bytes2 = readFileSync75(iconPath);
69750
+ const bytes2 = readFileSync76(iconPath);
69308
69751
  payload.icon = bytes2.toString("base64");
69309
69752
  iconHash = createHash28("sha256").update(bytes2).digest("hex");
69310
69753
  } catch {
@@ -69510,12 +69953,12 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
69510
69953
  print({ ok: false, error: "eject needs a module", hint: `one of: ${EJECTABLE2.join(", ")}` });
69511
69954
  return 2;
69512
69955
  }
69513
- const { readdirSync: readdirSync30, existsSync: fileExists } = await import("node:fs");
69956
+ const { readdirSync: readdirSync31, existsSync: fileExists } = await import("node:fs");
69514
69957
  const { join: pathJoin } = await import("node:path");
69515
69958
  const targets = [];
69516
69959
  const t5 = pathJoin(dir, "tier5-src");
69517
69960
  if (fileExists(t5)) {
69518
- for (const d of readdirSync30(t5, { withFileTypes: true })) {
69961
+ for (const d of readdirSync31(t5, { withFileTypes: true })) {
69519
69962
  if (d.isDirectory()) targets.push(pathJoin(t5, d.name, "index.ts"));
69520
69963
  }
69521
69964
  }
@@ -69547,7 +69990,7 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
69547
69990
  const { navIssues: navIssues2 } = await Promise.resolve().then(() => (init_nav_lint(), nav_lint_exports));
69548
69991
  const { buildContentForValidation: buildContentForValidation2, buildReleaseFromContent: buildReleaseFromContent2, bundleOutDir: bundleOutDir2 } = await loadAuthoring();
69549
69992
  const bres = await buildContentForValidation2(dir, { resolution: resolutionFlag(flags) });
69550
- const out = mkdtempSync4(join61(tmpdir5(), "himi-validate-"));
69993
+ const out = mkdtempSync4(join62(tmpdir5(), "himi-validate-"));
69551
69994
  const hasWorkers = bres.built.length > 0 || bres.compileErrors.length > 0;
69552
69995
  const outcome = await buildReleaseFromContent2(dir, out, {
69553
69996
  ...hasWorkers ? { tier5Dir: bundleOutDir2(dir) } : {},
@@ -69590,7 +70033,7 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
69590
70033
  const path2 = dpxSessionTokenPath();
69591
70034
  let raw;
69592
70035
  try {
69593
- raw = readFileSync75(path2, "utf8");
70036
+ raw = readFileSync76(path2, "utf8");
69594
70037
  } catch {
69595
70038
  throw new Error(`no DPX BO session at ${path2}; run dpx auth login, write dpx auth token to that path, then himi login --dpx --target bo`);
69596
70039
  }
@@ -69710,13 +70153,13 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
69710
70153
  case "studio": {
69711
70154
  const { openStudio: openStudio2 } = await Promise.resolve().then(() => (init_studio(), studio_exports));
69712
70155
  const dir = contentDir(flags);
69713
- if (!existsSync63(join61(dir, "himalaya.content.json"))) {
70156
+ if (!existsSync64(join62(dir, "himalaya.content.json"))) {
69714
70157
  io.error(JSON.stringify({ ok: false, error: "not_a_content_package", dir, hint: "run from the app directory (or pass --content <dir>) \u2014 the board reads that package's .himi/" }));
69715
70158
  return 2;
69716
70159
  }
69717
70160
  if (pos[1] === "reel") {
69718
70161
  const { buildReel: buildReel2 } = await Promise.resolve().then(() => (init_studio_reel(), studio_reel_exports));
69719
- const out = resolve47(str6(flags.out) ?? join61(dir, ".himi", "making-of.html"));
70162
+ const out = resolve47(str6(flags.out) ?? join62(dir, ".himi", "making-of.html"));
69720
70163
  const { html, plan } = buildReel2(dir);
69721
70164
  mkdirSync31(dirname34(out), { recursive: true });
69722
70165
  writeFileSync29(out, html);
@@ -69783,7 +70226,7 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
69783
70226
  } = await Promise.resolve().then(() => (init_progress(), progress_exports));
69784
70227
  const dir = contentDir(flags);
69785
70228
  const journal = journalPath2(dir);
69786
- const isContentPackage = existsSync63(join61(dir, "himalaya.content.json"));
70229
+ const isContentPackage = existsSync64(join62(dir, "himalaya.content.json"));
69787
70230
  const hint = isContentPackage ? {} : { hint: "no himalaya.content.json here \u2014 run from the app directory (or pass --content <dir>) so the build board finds this journal" };
69788
70231
  const message = pos.slice(1).join(" ").trim();
69789
70232
  const approvalFlags = ["approve", "done", "drop"].filter((f) => flags[f] !== void 0);
@@ -69855,8 +70298,8 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
69855
70298
  try {
69856
70299
  const entry = appendProgress2(dir, { message, ...step ? { step } : {}, images });
69857
70300
  const notified = flags.notify === true ? await notifyDesktop2(`himi \xB7 ${appLabel2(dir) ?? "your app"}`, message) : void 0;
69858
- const { count } = summarizeProgress2(readProgress2(dir).entries);
69859
- print({ ok: true, entry, journal, count, ...notified === void 0 ? {} : { notified }, ...hint });
70301
+ const { count: count2 } = summarizeProgress2(readProgress2(dir).entries);
70302
+ print({ ok: true, entry, journal, count: count2, ...notified === void 0 ? {} : { notified }, ...hint });
69860
70303
  return 0;
69861
70304
  } catch (e) {
69862
70305
  if (e instanceof ProgressError2) {
@@ -69919,7 +70362,7 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
69919
70362
  case "test": {
69920
70363
  const dir = contentDir(flags);
69921
70364
  const structuredOutput = flags.json === true || io.isTTY !== true;
69922
- if (existsSync63(join61(dir, "functions"))) {
70365
+ if (existsSync64(join62(dir, "functions"))) {
69923
70366
  const { invokeLocalFunction: invokeLocalFunction2, loadFunctionsApp: loadFunctionsApp2 } = await Promise.resolve().then(() => (init_functions(), functions_exports));
69924
70367
  const loaded = await loadFunctionsApp2(dir);
69925
70368
  const results = [];
@@ -70006,7 +70449,7 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
70006
70449
  }
70007
70450
  }
70008
70451
  const bres = await buildContentBundles2(dir, { resolution: resolutionFlag(flags) });
70009
- const out = mkdtempSync4(join61(tmpdir5(), "himi-test-"));
70452
+ const out = mkdtempSync4(join62(tmpdir5(), "himi-test-"));
70010
70453
  const hasWorkers = bres.built.length > 0 || bres.compileErrors.length > 0;
70011
70454
  const outcome = await buildReleaseFromContent2(dir, out, {
70012
70455
  ...hasWorkers ? { tier5Dir: bundleOutDir2(dir) } : {},
@@ -70051,7 +70494,7 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
70051
70494
  io.error(JSON.stringify({ error: `screen "${screen.screenId}" has no Tier-5 worker, so there is nothing for the browser loop to build` }));
70052
70495
  return 2;
70053
70496
  }
70054
- const sources = collectWorkerSources2(join61(dir, "tier5-src"), screen.bundleName);
70497
+ const sources = collectWorkerSources2(join62(dir, "tier5-src"), screen.bundleName);
70055
70498
  if (!sources) {
70056
70499
  io.error(JSON.stringify({ error: `no worker source found at tier5-src/${screen.bundleName} \u2014 the browser loop compiles SOURCE, not the built bundle` }));
70057
70500
  return 2;
@@ -70152,6 +70595,8 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
70152
70595
  const cmsSeed = offline ? null : await cmsSeedRows(dir, app);
70153
70596
  const canned = offline ? null : readCannedResponses2();
70154
70597
  const tabRoots = await tabRootScreens(dir);
70598
+ const onScreen = await liveMapFor(dir);
70599
+ const mapEntries = await navEntryScreens(dir, outcome.homeScreenId);
70155
70600
  let result;
70156
70601
  try {
70157
70602
  result = await runCrawl2({
@@ -70168,6 +70613,8 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
70168
70613
  ...cmsSeed ? { cmsSeed } : {},
70169
70614
  ...canned ? { canned } : {},
70170
70615
  ...tabRoots.size ? { tabRoots } : {},
70616
+ ...onScreen ? { onScreen } : {},
70617
+ ...mapEntries.size ? { mapEntries } : {},
70171
70618
  // The app's exact linkable set, straight off the build outcome (#2512) — arms the
70172
70619
  // nav-target check. Omitted when the build reported none, so the crawl stays silent
70173
70620
  // rather than judging every target against an empty set.
@@ -70212,7 +70659,7 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
70212
70659
  const sp = await runShotsPass2({
70213
70660
  descriptors: scopeDescriptors2(outcome.descriptors ?? [], pos.slice(1), invalidScreens),
70214
70661
  tokens: outcome.tokens,
70215
- outDir: join61(dir, ".himi", "shots"),
70662
+ outDir: join62(dir, ".himi", "shots"),
70216
70663
  width: 390,
70217
70664
  layers: { rules: shotRules, ...cmsSeed ? { cmsSeed } : {}, ...canned ? { canned: canned.byKey } : {} },
70218
70665
  ...budgetMs ? { budgetMs } : {},
@@ -70250,11 +70697,11 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
70250
70697
  releaseId: result.releaseId,
70251
70698
  releasePayload: release,
70252
70699
  descriptors: attestedDescriptors,
70253
- screenshots: Object.fromEntries(writtenShots.filter((name) => crawledScreens.has(name.replace(/\.png$/, ""))).map((name) => [name.replace(/\.png$/, ""), join61(dir, ".himi", "shots", name)])),
70700
+ screenshots: Object.fromEntries(writtenShots.filter((name) => crawledScreens.has(name.replace(/\.png$/, ""))).map((name) => [name.replace(/\.png$/, ""), join62(dir, ".himi", "shots", name)])),
70254
70701
  coverage: {
70255
70702
  screens: crawledScreens.size,
70256
70703
  actionsDispatched: result.actions.exercised,
70257
- uncoveredFns: (result.crawled ?? []).reduce((count, screen) => count + (screen.uncoveredFunctions?.length ?? 0), 0),
70704
+ uncoveredFns: (result.crawled ?? []).reduce((count2, screen) => count2 + (screen.uncoveredFunctions?.length ?? 0), 0),
70258
70705
  gaps: ledger.length
70259
70706
  },
70260
70707
  report: {
@@ -70271,6 +70718,7 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
70271
70718
  }));
70272
70719
  } else io.log(formatReport2(result) + formatDelta2(delta) + formatLedger2(ledger, trend));
70273
70720
  await leaveForBoard(dir, "edges", result.edges ?? []);
70721
+ if (result.navGraph) await leaveForBoard(dir, "map", result.navGraph);
70274
70722
  return result.ok ? 0 : 1;
70275
70723
  }
70276
70724
  case "render": {
@@ -70340,7 +70788,7 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
70340
70788
  return 2;
70341
70789
  }
70342
70790
  try {
70343
- const raw = stateArg.trim().startsWith("{") ? stateArg : readFileSync75(stateArg.replace(/^@/, ""), "utf8");
70791
+ const raw = stateArg.trim().startsWith("{") ? stateArg : readFileSync76(stateArg.replace(/^@/, ""), "utf8");
70344
70792
  injectedState = JSON.parse(raw);
70345
70793
  } catch (e) {
70346
70794
  io.error(JSON.stringify({ error: `--state must be inline JSON or a path to a .json file \u2014 ${e.message}` }));
@@ -70355,7 +70803,7 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
70355
70803
  return 2;
70356
70804
  }
70357
70805
  try {
70358
- const raw = inputArg.trim().startsWith("{") ? inputArg : readFileSync75(inputArg.replace(/^@/, ""), "utf8");
70806
+ const raw = inputArg.trim().startsWith("{") ? inputArg : readFileSync76(inputArg.replace(/^@/, ""), "utf8");
70359
70807
  const parsed = JSON.parse(raw);
70360
70808
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error("must be a JSON object");
70361
70809
  injectedInput = parsed;
@@ -70404,7 +70852,7 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
70404
70852
  }
70405
70853
  const { buildContentBundles: buildContentBundles2, buildReleaseFromContent: buildReleaseFromContent2, bundleOutDir: bundleOutDir2 } = await loadAuthoring();
70406
70854
  const bres = await buildContentBundles2(dir, { resolution: resolutionFlag(flags) });
70407
- const out = mkdtempSync4(join61(tmpdir5(), "himi-render-"));
70855
+ const out = mkdtempSync4(join62(tmpdir5(), "himi-render-"));
70408
70856
  const hasWorkers = bres.built.length > 0 || bres.compileErrors.length > 0;
70409
70857
  const outcome = await buildReleaseFromContent2(dir, out, {
70410
70858
  ...hasWorkers ? { tier5Dir: bundleOutDir2(dir) } : {},
@@ -70418,7 +70866,7 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
70418
70866
  const diagnostics = (outcome.lint ?? []).find((s) => s.screenId === screenId)?.issues ?? [];
70419
70867
  let pngFile;
70420
70868
  let attestationPngFile;
70421
- const shotPath = join61(dir, ".himi", "shots", `${screenId}.png`);
70869
+ const shotPath = join62(dir, ".himi", "shots", `${screenId}.png`);
70422
70870
  const pngTarget = typeof flags.png === "string" ? str6(flags.png) : flags.png === true ? shotPath : void 0;
70423
70871
  if (pngTarget && r.pngBase64) {
70424
70872
  pngFile = writePngFile(pngTarget, r.pngBase64);
@@ -70754,7 +71202,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
70754
71202
  io.error(JSON.stringify(linked));
70755
71203
  return 1;
70756
71204
  }
70757
- if (existsSync63(dest)) {
71205
+ if (existsSync64(dest)) {
70758
71206
  if (!statSync17(dest).isDirectory()) {
70759
71207
  io.error(JSON.stringify({
70760
71208
  error: `${dest} exists and is not a directory`,
@@ -70762,12 +71210,12 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
70762
71210
  }));
70763
71211
  return 1;
70764
71212
  }
70765
- const occupied = readdirSync29(dest).length > 0;
71213
+ const occupied = readdirSync30(dest).length > 0;
70766
71214
  if (occupied && !force) {
70767
71215
  io.error(JSON.stringify({ error: `${dest} already exists and is not empty`, hint: "pass --force to write into it anyway (merging, and naming what it keeps), --replace to restore exactly what was archived, or --out/--name to land somewhere else" }));
70768
71216
  return 1;
70769
71217
  }
70770
- if (replace && occupied && !existsSync63(join61(dest, "himalaya.content.json"))) {
71218
+ if (replace && occupied && !existsSync64(join62(dest, "himalaya.content.json"))) {
70771
71219
  io.error(JSON.stringify({
70772
71220
  error: `${dest} is not a content package \u2014 refusing to --replace it`,
70773
71221
  hint: "--replace only clears a directory that holds himalaya.content.json. Check --out/--name, or pass --force to merge into it instead."
@@ -70908,7 +71356,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
70908
71356
  const force = flags.force === true;
70909
71357
  const { buildContentForValidation: buildContentForValidation2, buildReleaseFromContent: buildReleaseFromContent2, bundleOutDir: bundleOutDir2 } = await loadAuthoring();
70910
71358
  const bres = await buildContentForValidation2(dir, { resolution: resolutionFlag(flags) });
70911
- const out = mkdtempSync4(join61(tmpdir5(), "himi-push-"));
71359
+ const out = mkdtempSync4(join62(tmpdir5(), "himi-push-"));
70912
71360
  const hasWorkers = bres.built.length > 0 || bres.compileErrors.length > 0;
70913
71361
  const outcome = await buildReleaseFromContent2(dir, out, {
70914
71362
  ...hasWorkers ? { tier5Dir: bundleOutDir2(dir) } : {},
@@ -71429,7 +71877,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
71429
71877
  }
71430
71878
  const { buildContentForValidation: buildContentForValidation2, buildReleaseFromContent: buildReleaseFromContent2, bundleOutDir: bundleOutDir2 } = await loadAuthoring();
71431
71879
  const bres = await buildContentForValidation2(dir, { resolution: resolutionFlag(flags) });
71432
- const built = mkdtempSync4(join61(tmpdir5(), "himi-pack-"));
71880
+ const built = mkdtempSync4(join62(tmpdir5(), "himi-pack-"));
71433
71881
  const hasWorkers = bres.built.length > 0 || bres.compileErrors.length > 0;
71434
71882
  const outcome = await buildReleaseFromContent2(dir, built, {
71435
71883
  ...hasWorkers ? { tier5Dir: bundleOutDir2(dir) } : {},
@@ -71634,7 +72082,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
71634
72082
  }));
71635
72083
  return 1;
71636
72084
  }
71637
- const archive = readFileSync75(resolve47(file));
72085
+ const archive = readFileSync76(resolve47(file));
71638
72086
  const result = await client2().createPackageShare({
71639
72087
  app: opened.header.app,
71640
72088
  releaseId: opened.header.releaseId,
@@ -71694,11 +72142,11 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
71694
72142
  const app = await appId(dir2);
71695
72143
  const { buildContentBundles: buildContentBundles2, buildReleaseFromContent: buildReleaseFromContent2, bundleOutDir: bundleOutDir2 } = await loadAuthoring();
71696
72144
  const bres = await buildContentBundles2(dir2, { resolution: resolutionFlag(flags) });
71697
- temp = mkdtempSync4(join61(tmpdir5(), "himi-run-build-"));
72145
+ temp = mkdtempSync4(join62(tmpdir5(), "himi-run-build-"));
71698
72146
  const hasWorkers = bres.built.length > 0 || bres.compileErrors.length > 0;
71699
72147
  const initial = await buildReleaseFromContent2(dir2, temp, { ...hasWorkers ? { tier5Dir: bundleOutDir2(dir2) } : {} });
71700
72148
  servedMissing = initial.missingBundles;
71701
- source = join61(temp, app);
72149
+ source = join62(temp, app);
71702
72150
  }
71703
72151
  const chromeArg = str6(flags.chrome) ?? "preview";
71704
72152
  if (chromeArg !== "preview" && chromeArg !== "app") {
@@ -71734,7 +72182,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
71734
72182
  if (!recordKit) return;
71735
72183
  startedAt = (/* @__PURE__ */ new Date()).toISOString();
71736
72184
  const failure = record2(served2, { ok: true, at: startedAt });
71737
- if (!recorded) io.error(JSON.stringify({ run: "run_record_skipped", message: `${failure ?? `${join61(dir, ".himi")} is not a real directory`}, so the build board will not frame this preview.` }));
72185
+ if (!recorded) io.error(JSON.stringify({ run: "run_record_skipped", message: `${failure ?? `${join62(dir, ".himi")} is not a real directory`}, so the build board will not frame this preview.` }));
71738
72186
  await refreshBoard();
71739
72187
  };
71740
72188
  const retract = async () => {
@@ -71765,7 +72213,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
71765
72213
  }
71766
72214
  const watchKit = watch2 ? await Promise.resolve().then(() => (init_run_watch(), run_watch_exports)) : null;
71767
72215
  const bus = watchKit ? new watchKit.ReloadBus() : void 0;
71768
- if (watch2) served = mkdtempSync4(join61(tmpdir5(), "himi-run-served-"));
72216
+ if (watch2) served = mkdtempSync4(join62(tmpdir5(), "himi-run-served-"));
71769
72217
  const running = await runPackage2(source, {
71770
72218
  port,
71771
72219
  chrome: chromeArg,
@@ -71787,8 +72235,8 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
71787
72235
  await announce(running);
71788
72236
  if (watchKit && bus) {
71789
72237
  const { buildContentBundles: buildContentBundles2, buildReleaseFromContent: buildReleaseFromContent2, bundleOutDir: bundleOutDir2 } = await loadAuthoring();
71790
- const live2 = join61(running.releaseRoot, running.app);
71791
- const manifestOf = (tree) => JSON.parse(readFileSync75(join61(tree, "release-manifest.json"), "utf8"));
72238
+ const live2 = join62(running.releaseRoot, running.app);
72239
+ const manifestOf = (tree) => JSON.parse(readFileSync76(join62(tree, "release-manifest.json"), "utf8"));
71792
72240
  let current = manifestOf(live2);
71793
72241
  const failed2 = async (message) => {
71794
72242
  if (stopping) return;
@@ -71799,7 +72247,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
71799
72247
  };
71800
72248
  rebuild = watchKit.serialize(async () => {
71801
72249
  const t0 = Date.now();
71802
- const next = mkdtempSync4(join61(tmpdir5(), "himi-run-build-"));
72250
+ const next = mkdtempSync4(join62(tmpdir5(), "himi-run-build-"));
71803
72251
  try {
71804
72252
  const bres = await buildContentBundles2(dir, { resolution: resolutionFlag(flags) });
71805
72253
  if (bres.compileErrors.length) {
@@ -71816,22 +72264,24 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
71816
72264
  return;
71817
72265
  }
71818
72266
  if (stopping) return;
71819
- const fresh = manifestOf(join61(next, running.app));
72267
+ const fresh = manifestOf(join62(next, running.app));
71820
72268
  const diff = watchKit.diffReleases(current, fresh);
72269
+ const { describeChanges: describeChanges2, headline: headline2 } = await Promise.resolve().then(() => (init_build_changes(), build_changes_exports));
72270
+ const changes = describeChanges2(live2, join62(next, running.app), diff);
71821
72271
  const retired = `${live2}.retired-${Date.now()}`;
71822
72272
  renameSync7(live2, retired);
71823
72273
  try {
71824
- renameSync7(join61(next, running.app), live2);
72274
+ renameSync7(join62(next, running.app), live2);
71825
72275
  } catch {
71826
- cpSync3(join61(next, running.app), live2, { recursive: true });
72276
+ cpSync3(join62(next, running.app), live2, { recursive: true });
71827
72277
  }
71828
72278
  rmSync14(retired, { recursive: true, force: true });
71829
72279
  current = fresh;
71830
72280
  servedMissing = outcome.missingBundles;
71831
72281
  for (const [event, data] of watchKit.reloadEvents(diff)) bus.publish(event, data);
71832
- record2(running, { ok: true, at: (/* @__PURE__ */ new Date()).toISOString(), ms: Date.now() - t0, screens: diff.screens, tokens: diff.tokens, config: diff.config });
72282
+ record2(running, { ok: true, at: (/* @__PURE__ */ new Date()).toISOString(), ms: Date.now() - t0, screens: diff.screens, tokens: diff.tokens, config: diff.config, changes });
71833
72283
  await refreshBoard();
71834
- io.error(JSON.stringify({ watch: "rebuilt", ms: Date.now() - t0, screens: diff.screens, tokens: diff.tokens, config: diff.config, tabs: bus.listeners }));
72284
+ io.error(JSON.stringify({ watch: "rebuilt", ms: Date.now() - t0, screens: diff.screens, tokens: diff.tokens, config: diff.config, ...changes.items.length ? { changed: headline2(changes), changes } : {}, tabs: bus.listeners }));
71835
72285
  } catch (e) {
71836
72286
  await failed2(e.message.split("\n")[0]);
71837
72287
  } finally {
@@ -72433,7 +72883,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
72433
72883
  io.error(JSON.stringify({ error: `patch ${kind} needs --file`, usage: PATCH_USAGE }));
72434
72884
  return 2;
72435
72885
  }
72436
- if (!existsSync63(file)) {
72886
+ if (!existsSync64(file)) {
72437
72887
  io.error(JSON.stringify({ error: `no such file: ${file}` }));
72438
72888
  return 2;
72439
72889
  }
@@ -72489,7 +72939,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
72489
72939
  lintNote: "`himi patch` sends a raw file. Nothing here built or linted your app \u2014 `himi push --only <selector>` is the same edit with the full lint gate in front of it."
72490
72940
  };
72491
72941
  if (kind === "tokens" || kind === "app-config") {
72492
- const body = readFileSync75(file, "utf8");
72942
+ const body = readFileSync76(file, "utf8");
72493
72943
  const patch2 = kind === "tokens" ? { tokens: body } : { appConfig: body };
72494
72944
  return runReleasePatch({
72495
72945
  ...common,
@@ -72504,7 +72954,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
72504
72954
  io.error(JSON.stringify({ error: "patch screen needs a <screenId>", usage: PATCH_USAGE }));
72505
72955
  return 2;
72506
72956
  }
72507
- const descriptor = readFileSync75(file, "utf8");
72957
+ const descriptor = readFileSync76(file, "utf8");
72508
72958
  try {
72509
72959
  JSON.parse(descriptor);
72510
72960
  } catch (e) {
@@ -72544,7 +72994,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
72544
72994
  extra: { kind, screen: id, file: { path: file, bytes: Buffer.byteLength(descriptor, "utf8") } }
72545
72995
  });
72546
72996
  }
72547
- const bytes2 = readFileSync75(file);
72997
+ const bytes2 = readFileSync76(file);
72548
72998
  const sha = createHash28("sha256").update(bytes2).digest("hex");
72549
72999
  const forScreens = (str6(flags["for-screen"]) ?? "").split(",").map((s) => s.trim()).filter(Boolean);
72550
73000
  if (flags["no-rebind"] !== void 0 && flags["no-rebind"] !== true) {
@@ -72688,7 +73138,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
72688
73138
  }
72689
73139
  let def;
72690
73140
  try {
72691
- const parsed = JSON.parse(arg.startsWith("@") ? readFileSync75(arg.slice(1), "utf8") : arg);
73141
+ const parsed = JSON.parse(arg.startsWith("@") ? readFileSync76(arg.slice(1), "utf8") : arg);
72692
73142
  if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
72693
73143
  io.error(JSON.stringify({ error: "invalid_definition", problems: [`--definition must be a JSON object, got ${Array.isArray(parsed) ? "an array" : parsed === null ? "null" : typeof parsed}`] }));
72694
73144
  return 2;
@@ -73152,7 +73602,7 @@ ${label2}`);
73152
73602
  const platform = str6(flags.platform)?.split(",").map((p) => p.trim()).filter(Boolean);
73153
73603
  {
73154
73604
  const distDir = contentDir(flags);
73155
- if (existsSync63(join61(distDir, "himalaya.content.json"))) {
73605
+ if (existsSync64(join62(distDir, "himalaya.content.json"))) {
73156
73606
  const issues = await iconIssues(distDir, flags.strict === true, { requireDeclared: true, requireVerified: true });
73157
73607
  const errors = issues.filter((i) => i.severity === "error");
73158
73608
  for (const i of issues.filter((i2) => i2.severity === "warning")) {
@@ -73421,7 +73871,7 @@ ${label2}`);
73421
73871
  const dpxPath = dpxSessionTokenPath();
73422
73872
  let dpx;
73423
73873
  try {
73424
- dpx = readFileSync75(dpxPath, "utf8").trim();
73874
+ dpx = readFileSync76(dpxPath, "utf8").trim();
73425
73875
  } catch {
73426
73876
  io.error(JSON.stringify({ error: `no dpx session at ${dpxPath} \u2014 run \`dpx auth login\` then \`dpx auth token > ${dpxPath}\` (dpx 2.x keeps its own state in ~/.dpx/auth.json and does NOT write the legacy session file), or omit --dpx` }));
73427
73877
  return 1;
@@ -73829,7 +74279,7 @@ ${report2.ok ? "OK \u2014 data + serve plane reachable." : typeof report2.hint =
73829
74279
  const path2 = dpxSessionTokenPath();
73830
74280
  let raw;
73831
74281
  try {
73832
- raw = readFileSync75(path2, "utf8");
74282
+ raw = readFileSync76(path2, "utf8");
73833
74283
  } catch {
73834
74284
  io.error(JSON.stringify({ error: `no DPX BO session at ${path2}` }));
73835
74285
  return 1;