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