@wix/himalaya-cli 0.1109.0 → 0.1111.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
@@ -994,8 +994,8 @@ function mobileUxDocumentBody(mobile) {
994
994
  }
995
995
  return out2 + mobile.slice(last);
996
996
  }
997
- function mobileUxSection(text6, heading) {
998
- const match = new RegExp(`(?:^|\\n)##[ \\t]+${heading}\\b[^\\n]*([\\s\\S]*?)(?=\\n##[ \\t]|$)`, "i").exec(text6);
997
+ function mobileUxSection(text8, heading) {
998
+ const match = new RegExp(`(?:^|\\n)##[ \\t]+${heading}\\b[^\\n]*([\\s\\S]*?)(?=\\n##[ \\t]|$)`, "i").exec(text8);
999
999
  return match ? match[1] : null;
1000
1000
  }
1001
1001
  function sectionTables(markdown) {
@@ -1024,8 +1024,8 @@ function namesOneCta(cell) {
1024
1024
  if (!cell) return false;
1025
1025
  return !/[,;]|\s\/\s/.test(cell);
1026
1026
  }
1027
- function declaredStates(text6) {
1028
- const contract = section(text6, "Screen contract");
1027
+ function declaredStates(text8) {
1028
+ const contract = section(text8, "Screen contract");
1029
1029
  if (!contract) return [];
1030
1030
  const states = [];
1031
1031
  for (const bullet of contract.matchAll(/^[ \t]*[-*][ \t]*[^:\n]*\bstates?[ \t]*:[ \t]*(\S.*)$/gim)) {
@@ -1035,8 +1035,8 @@ function declaredStates(text6) {
1035
1035
  }
1036
1036
  return [...new Set(states.map((state) => state.toLowerCase()))];
1037
1037
  }
1038
- function primaryCtaStructureGap(text6) {
1039
- const acceptance = section(text6, "Implementation acceptance");
1038
+ function primaryCtaStructureGap(text8) {
1039
+ const acceptance = section(text8, "Implementation acceptance");
1040
1040
  if (acceptance === null) return "There is no `## Implementation acceptance` section to read it from.";
1041
1041
  const tables = sectionTables(acceptance).filter((table) => table.data.length > 0);
1042
1042
  if (!tables.length) return "Its acceptance table has no rows.";
@@ -1049,7 +1049,7 @@ function primaryCtaStructureGap(text6) {
1049
1049
  const data = ctaTables.flatMap(({ table }) => table.data);
1050
1050
  const flat = (value) => value.toLowerCase().replace(/[^a-z0-9]+/g, " ").trim();
1051
1051
  const rowText = data.map((row) => flat(row.join(" ")));
1052
- const missing = declaredStates(text6).filter((state) => !rowText.some((row) => row.includes(flat(state))));
1052
+ const missing = declaredStates(text8).filter((state) => !rowText.some((row) => row.includes(flat(state))));
1053
1053
  if (missing.length) return `The acceptance table has no row for the declared state(s): ${missing.join(", ")}.`;
1054
1054
  return null;
1055
1055
  }
@@ -1064,16 +1064,16 @@ function hasUnterminatedComment(mobile) {
1064
1064
  }
1065
1065
  }
1066
1066
  function mobileUxMarkerFindings(mobile, renderedWidths = []) {
1067
- const text6 = mobileUxDocumentBody(mobile);
1067
+ const text8 = mobileUxDocumentBody(mobile);
1068
1068
  const findings = [];
1069
- if (!/^## Implementation acceptance\b/im.test(text6)) {
1069
+ if (!/^## Implementation acceptance\b/im.test(text8)) {
1070
1070
  findings.push({ code: "missing-acceptance-matrix", message: "MOBILE-UX.md must contain the heading `## Implementation acceptance` at the start of a line. Case is not checked, but use this spelling: other tooling addresses the document by it." });
1071
1071
  }
1072
- if (!/^## Evidence\b/im.test(text6)) {
1072
+ if (!/^## Evidence\b/im.test(text8)) {
1073
1073
  findings.push({ code: "missing-evidence-section", message: "MOBILE-UX.md must contain the heading `## Evidence` at the start of a line. Case is not checked, but use this spelling: other tooling addresses the document by it." });
1074
1074
  }
1075
- if (!/one primary CTA per state/i.test(text6)) {
1076
- const gap = primaryCtaStructureGap(text6);
1075
+ if (!/one primary CTA per state/i.test(text8)) {
1076
+ const gap = primaryCtaStructureGap(text8);
1077
1077
  if (gap) {
1078
1078
  findings.push({
1079
1079
  code: "missing-primary-cta-rule",
@@ -1081,8 +1081,8 @@ function mobileUxMarkerFindings(mobile, renderedWidths = []) {
1081
1081
  });
1082
1082
  }
1083
1083
  }
1084
- if (!hasWidthEvidence(text6, renderedWidths)) {
1085
- findings.push({ code: "missing-width-evidence", message: widthEvidenceMessage(text6, renderedWidths) });
1084
+ if (!hasWidthEvidence(text8, renderedWidths)) {
1085
+ findings.push({ code: "missing-width-evidence", message: widthEvidenceMessage(text8, renderedWidths) });
1086
1086
  }
1087
1087
  if (findings.length && hasUnterminatedComment(mobile)) {
1088
1088
  return [{
@@ -1092,15 +1092,15 @@ function mobileUxMarkerFindings(mobile, renderedWidths = []) {
1092
1092
  }
1093
1093
  return findings;
1094
1094
  }
1095
- function widthMentions(text6) {
1095
+ function widthMentions(text8) {
1096
1096
  const mentions = [];
1097
- for (const match of text6.matchAll(/\d+/g)) {
1097
+ for (const match of text8.matchAll(/\d+/g)) {
1098
1098
  const value = Number(match[0]);
1099
1099
  const start = match.index ?? 0;
1100
1100
  if (!inRange(value)) continue;
1101
1101
  const end = start + match[0].length;
1102
- const after = text6.slice(end, end + EDGE_WINDOW);
1103
- const before = text6.slice(Math.max(0, start - EDGE_WINDOW), start);
1102
+ const after = text8.slice(end, end + EDGE_WINDOW);
1103
+ const before = text8.slice(Math.max(0, start - EDGE_WINDOW), start);
1104
1104
  if (looksLikeYear(value) && !(UNIT_ATTACHED.test(after) || UNIT_WORD_ATTACHED.test(after))) continue;
1105
1105
  const spelledAsWidth = UNIT_ATTACHED.test(after) || UNIT_WORD_ATTACHED.test(after) || FILENAME_AFTER.test(after);
1106
1106
  const vetoed = spelledAsWidth && (ANTI_CUE_BEFORE.test(before) || PAIR_HEIGHT_BEFORE.test(before) || ANTI_CUE_AFTER.test(after));
@@ -2093,7 +2093,7 @@ function foldLegacyReport(legacy, seed2) {
2093
2093
  ...arrayField(legacy.failures),
2094
2094
  ...arrayField(legacy.warnings)
2095
2095
  ];
2096
- const report2 = emptyReport({
2096
+ const report3 = emptyReport({
2097
2097
  ...seed2,
2098
2098
  ok: typeof legacy.ok === "boolean" ? legacy.ok : seed2.ok,
2099
2099
  app: seed2.app ?? stringField(legacy.app),
@@ -2104,12 +2104,12 @@ function foldLegacyReport(legacy, seed2) {
2104
2104
  const oldCounts = recordField(legacy.counts);
2105
2105
  return {
2106
2106
  ...legacy,
2107
- ...report2,
2107
+ ...report3,
2108
2108
  counts: {
2109
2109
  ...oldCounts,
2110
- errors: numberField(oldCounts.errors) ?? report2.counts.errors,
2111
- warnings: numberField(oldCounts.warnings) ?? report2.counts.warnings,
2112
- gaps: report2.counts.gaps
2110
+ errors: numberField(oldCounts.errors) ?? report3.counts.errors,
2111
+ warnings: numberField(oldCounts.warnings) ?? report3.counts.warnings,
2112
+ gaps: report3.counts.gaps
2113
2113
  }
2114
2114
  };
2115
2115
  }
@@ -2773,8 +2773,8 @@ function isAttestationV2(value) {
2773
2773
  if (!value || typeof value !== "object" || Array.isArray(value)) return false;
2774
2774
  const a = value;
2775
2775
  const coverage = a.coverage;
2776
- const report2 = a.report;
2777
- 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");
2776
+ const report3 = a.report;
2777
+ 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 report3?.ok === "boolean" && !!report3.counts && [report3.counts.errors, report3.counts.warnings, report3.counts.gaps].every((count2) => typeof count2 === "number" && Number.isFinite(count2) && count2 >= 0) && (a.prev === null || typeof a.prev === "string");
2778
2778
  }
2779
2779
  function readLatestAttestation(contentDir2) {
2780
2780
  try {
@@ -3170,8 +3170,8 @@ function penalty(m, size) {
3170
3170
  score += Math.floor(Math.abs(dark * 20 - total * 10) / total) * 10;
3171
3171
  return score;
3172
3172
  }
3173
- function encodeQr(text6) {
3174
- const bytes2 = [...new TextEncoder().encode(text6)];
3173
+ function encodeQr(text8) {
3174
+ const bytes2 = [...new TextEncoder().encode(text8)];
3175
3175
  const version2 = fitVersion(bytes2.length);
3176
3176
  if (!version2) {
3177
3177
  throw new RangeError(`QR payload too long: ${bytes2.length} bytes exceeds version ${QR_MAX_VERSION} at EC level L`);
@@ -3742,7 +3742,7 @@ async function mintToken(args, label2, cacheKey, spawnImpl) {
3742
3742
  tokenCache.set(cacheKey, fromEnv);
3743
3743
  return fromEnv;
3744
3744
  }
3745
- const token = await new Promise((resolve52, reject) => {
3745
+ const token = await new Promise((resolve53, reject) => {
3746
3746
  let child;
3747
3747
  try {
3748
3748
  child = spawnImpl("wix", [...args]);
@@ -3771,12 +3771,12 @@ async function mintToken(args, label2, cacheKey, spawnImpl) {
3771
3771
  child.stdout?.on("data", (d) => {
3772
3772
  out2 += String(d);
3773
3773
  const m = out2.match(TOKEN_RE);
3774
- if (m) done(() => resolve52(m[0]));
3774
+ if (m) done(() => resolve53(m[0]));
3775
3775
  });
3776
3776
  child.stderr?.on("data", (d) => {
3777
3777
  out2 += String(d);
3778
3778
  const m = out2.match(TOKEN_RE);
3779
- if (m) done(() => resolve52(m[0]));
3779
+ if (m) done(() => resolve53(m[0]));
3780
3780
  });
3781
3781
  child.on("error", (e) => done(() => reject(new Error(mintHelp(label2, e.message)))));
3782
3782
  child.on("exit", (code) => done(() => reject(new Error(
@@ -3821,10 +3821,10 @@ function ownerTransport(msid, fetchImpl = fetch, timeoutMs = DEFAULT_TIMEOUT_MS2
3821
3821
  }
3822
3822
  throw new Error(`${method} ${path2} failed: ${err?.message ?? String(e)}`);
3823
3823
  }
3824
- const text6 = await res.text();
3825
- let parsed = text6;
3824
+ const text8 = await res.text();
3825
+ let parsed = text8;
3826
3826
  try {
3827
- parsed = text6 ? JSON.parse(text6) : null;
3827
+ parsed = text8 ? JSON.parse(text8) : null;
3828
3828
  } catch {
3829
3829
  }
3830
3830
  if (!res.ok) throw new WixApiError(res.status, method, path2, parsed);
@@ -4486,18 +4486,18 @@ function confinementPlugin(contentRoot) {
4486
4486
  }
4487
4487
  function assertConfinedTsconfig(entry, contentRoot, allows) {
4488
4488
  const seen = /* @__PURE__ */ new Set();
4489
- const refuse4 = (p, why) => {
4489
+ const refuse5 = (p, why) => {
4490
4490
  throw new Error(
4491
4491
  `refused to read ${p}: ${why}. A tsconfig may extend a file from its own package or an installed dependency \u2014 nothing else.`
4492
4492
  );
4493
4493
  };
4494
- const stripJsonc = (text6) => {
4495
- if (text6.charCodeAt(0) === 65279) text6 = text6.slice(1);
4494
+ const stripJsonc = (text8) => {
4495
+ if (text8.charCodeAt(0) === 65279) text8 = text8.slice(1);
4496
4496
  let out2 = "";
4497
4497
  let inStr = false;
4498
4498
  let esc = false;
4499
- for (let i = 0; i < text6.length; i++) {
4500
- const c = text6[i];
4499
+ for (let i = 0; i < text8.length; i++) {
4500
+ const c = text8[i];
4501
4501
  if (inStr) {
4502
4502
  out2 += c;
4503
4503
  if (esc) esc = false;
@@ -4510,13 +4510,13 @@ function assertConfinedTsconfig(entry, contentRoot, allows) {
4510
4510
  out2 += c;
4511
4511
  continue;
4512
4512
  }
4513
- if (c === "/" && text6[i + 1] === "/") {
4514
- while (i < text6.length && text6[i] !== "\n") i++;
4513
+ if (c === "/" && text8[i + 1] === "/") {
4514
+ while (i < text8.length && text8[i] !== "\n") i++;
4515
4515
  continue;
4516
4516
  }
4517
- if (c === "/" && text6[i + 1] === "*") {
4517
+ if (c === "/" && text8[i + 1] === "*") {
4518
4518
  i += 2;
4519
- while (i < text6.length && !(text6[i] === "*" && text6[i + 1] === "/")) i++;
4519
+ while (i < text8.length && !(text8[i] === "*" && text8[i + 1] === "/")) i++;
4520
4520
  i++;
4521
4521
  continue;
4522
4522
  }
@@ -4552,7 +4552,7 @@ function assertConfinedTsconfig(entry, contentRoot, allows) {
4552
4552
  try {
4553
4553
  manifest = JSON.parse(stripJsonc(readFileSync10(pj, "utf8")));
4554
4554
  } catch {
4555
- refuse4(pj, "its package.json could not be parsed, so its `tsconfig` field cannot be checked");
4555
+ refuse5(pj, "its package.json could not be parsed, so its `tsconfig` field cannot be checked");
4556
4556
  }
4557
4557
  const field = manifest?.tsconfig;
4558
4558
  if (typeof field === "string" && field) {
@@ -4568,7 +4568,7 @@ function assertConfinedTsconfig(entry, contentRoot, allows) {
4568
4568
  const real = realpathOrSelf(file);
4569
4569
  if (seen.has(real)) return;
4570
4570
  seen.add(real);
4571
- if (!allows(real)) refuse4(file, "it is outside this content package");
4571
+ if (!allows(real)) refuse5(file, "it is outside this content package");
4572
4572
  let raw;
4573
4573
  try {
4574
4574
  raw = readFileSync10(real, "utf8");
@@ -4579,7 +4579,7 @@ function assertConfinedTsconfig(entry, contentRoot, allows) {
4579
4579
  try {
4580
4580
  parsed = JSON.parse(stripJsonc(raw));
4581
4581
  } catch {
4582
- refuse4(file, "its tsconfig could not be parsed, so its `extends` cannot be checked");
4582
+ refuse5(file, "its tsconfig could not be parsed, so its `extends` cannot be checked");
4583
4583
  }
4584
4584
  const ext = parsed?.extends;
4585
4585
  for (const one of Array.isArray(ext) ? ext : ext ? [ext] : []) {
@@ -6762,6 +6762,7 @@ function walk(node, registry, path2, out2) {
6762
6762
  var SYSTEM_FONT_FAMILY, TONAL_ELEVATION_ALPHA;
6763
6763
  var init_tokens = __esm({
6764
6764
  "../../core/ts/src/tokens.ts"() {
6765
+ "use strict";
6765
6766
  SYSTEM_FONT_FAMILY = "$system";
6766
6767
  TONAL_ELEVATION_ALPHA = [0, 0.05, 0.08, 0.11, 0.12, 0.14];
6767
6768
  }
@@ -8419,8 +8420,8 @@ function pushSpan(out2, s) {
8419
8420
  else out2.push(s);
8420
8421
  }
8421
8422
  function spanOf(textNode) {
8422
- const text6 = str(at(textNode, "textData", "text"));
8423
- if (text6 === "") return null;
8423
+ const text8 = str(at(textNode, "textData", "text"));
8424
+ if (text8 === "") return null;
8424
8425
  let bold = false, italic = false, underline = false;
8425
8426
  let link;
8426
8427
  const decorations = at(textNode, "textData", "decorations");
@@ -8447,7 +8448,7 @@ function spanOf(textNode) {
8447
8448
  }
8448
8449
  }
8449
8450
  }
8450
- const span = { text: text6 };
8451
+ const span = { text: text8 };
8451
8452
  if (bold) span.bold = true;
8452
8453
  if (italic) span.italic = true;
8453
8454
  if (underline) span.underline = true;
@@ -8669,8 +8670,8 @@ function blockOf(ids, n, path2) {
8669
8670
  const { title, url } = cardFields(n, type);
8670
8671
  return [card(ids.claim(n, path2), type, title, url)];
8671
8672
  }
8672
- const text6 = plainText(n);
8673
- return text6.trim() !== "" ? [{ kind: "paragraph", id: ids.claim(n, path2), spans: [{ text: text6 }] }] : [];
8673
+ const text8 = plainText(n);
8674
+ return text8.trim() !== "" ? [{ kind: "paragraph", id: ids.claim(n, path2), spans: [{ text: text8 }] }] : [];
8674
8675
  }
8675
8676
  }
8676
8677
  }
@@ -9941,26 +9942,26 @@ function serviceOnlyBindingValues(props, key2) {
9941
9942
  });
9942
9943
  return out2;
9943
9944
  }
9944
- function matchBraceSafe(text6, open) {
9945
+ function matchBraceSafe(text8, open) {
9945
9946
  let depth = 0;
9946
- for (let i = open; i < text6.length; i++) {
9947
- const c = text6[i];
9947
+ for (let i = open; i < text8.length; i++) {
9948
+ const c = text8[i];
9948
9949
  if (c === '"' || c === "'" || c === "`") {
9949
9950
  const q = c;
9950
9951
  i++;
9951
- while (i < text6.length && text6[i] !== q) {
9952
- if (text6[i] === "\\") i++;
9952
+ while (i < text8.length && text8[i] !== q) {
9953
+ if (text8[i] === "\\") i++;
9953
9954
  i++;
9954
9955
  }
9955
9956
  continue;
9956
9957
  }
9957
- if (c === "/" && text6[i + 1] === "/") {
9958
- while (i < text6.length && text6[i] !== "\n") i++;
9958
+ if (c === "/" && text8[i + 1] === "/") {
9959
+ while (i < text8.length && text8[i] !== "\n") i++;
9959
9960
  continue;
9960
9961
  }
9961
- if (c === "/" && text6[i + 1] === "*") {
9962
+ if (c === "/" && text8[i + 1] === "*") {
9962
9963
  i += 2;
9963
- while (i < text6.length && !(text6[i] === "*" && text6[i + 1] === "/")) i++;
9964
+ while (i < text8.length && !(text8[i] === "*" && text8[i + 1] === "/")) i++;
9964
9965
  i++;
9965
9966
  continue;
9966
9967
  }
@@ -12908,8 +12909,8 @@ var init_owner_claim = __esm({
12908
12909
 
12909
12910
  // ../native-config/car.ts
12910
12911
  function parseCarConfig(configTs) {
12911
- const { text: text6, inString } = scanConfig(configTs);
12912
- for (const m of text6.matchAll(/\bcar\s*[:=]\s*\{([^}]*)\}/g)) {
12912
+ const { text: text8, inString } = scanConfig(configTs);
12913
+ for (const m of text8.matchAll(/\bcar\s*[:=]\s*\{([^}]*)\}/g)) {
12913
12914
  if (inString[m.index]) continue;
12914
12915
  return { category: /category\s*:\s*["']([^"']+)["']/.exec(m[1])?.[1] };
12915
12916
  }
@@ -17995,12 +17996,12 @@ function hostAppliesDeferredWidgetActions(app) {
17995
17996
  }
17996
17997
  return false;
17997
17998
  }
17998
- function hasMarkers(text6, kind, label2) {
17999
- const lines = text6.split("\n");
17999
+ function hasMarkers(text8, kind, label2) {
18000
+ const lines = text8.split("\n");
18000
18001
  return lines.some((l) => isBegin(l, kind, label2)) && lines.some((l) => isEnd(l, kind, label2));
18001
18002
  }
18002
- function injectBlock(text6, kind, label2, render) {
18003
- const lines = text6.split("\n");
18003
+ function injectBlock(text8, kind, label2, render) {
18004
+ const lines = text8.split("\n");
18004
18005
  const beginIdx = lines.findIndex((l) => isBegin(l, kind, label2));
18005
18006
  const endIdx = lines.findIndex((l) => isEnd(l, kind, label2));
18006
18007
  if (beginIdx === -1 || endIdx === -1 || endIdx < beginIdx) {
@@ -18115,8 +18116,8 @@ function genNativeConfig(app, opts = {}) {
18115
18116
  ];
18116
18117
  const rce = declaresRichContentEditor(configTs);
18117
18118
  for (const yml of [iosYml, macosProjectYml(app)]) {
18118
- const text6 = existsSync26(yml) ? readFileSync26(yml, "utf8") : "";
18119
- const r = applyToFile(yml, "yaml", LABEL_LOCALWEB, renderLocalWebYaml(resourcesDirOf(text6), rce), check);
18119
+ const text8 = existsSync26(yml) ? readFileSync26(yml, "utf8") : "";
18120
+ const r = applyToFile(yml, "yaml", LABEL_LOCALWEB, renderLocalWebYaml(resourcesDirOf(text8), rce), check);
18120
18121
  if (existsSync26(yml)) assertLocalWebBlockLanded(app, rce, yml, r.outcome);
18121
18122
  files.push(r);
18122
18123
  }
@@ -18526,21 +18527,21 @@ function carryPlanningJournal(parentDir, appDir3) {
18526
18527
  return carried;
18527
18528
  }
18528
18529
  function formatBrief(input) {
18529
- const { summary, open } = input;
18530
+ const { summary: summary2, open } = input;
18530
18531
  const who = `himi \u2014 ${input.appName ?? "this folder"}`;
18531
18532
  const lines = [];
18532
- if (!summary.count) {
18533
+ if (!summary2.count) {
18533
18534
  lines.push(`${who}: no journey journal yet (${PROGRESS_FILE.split(sep6).join("/")}).`);
18534
18535
  lines.push(
18535
18536
  'A new app starts with discovery (step 1). An app built before journaling existed continues from its current state instead: confirm with the customer that its SPEC.md was approved rather than re-running discovery. Either way, journal each milestone with `himi progress "<what changed>" --step <n>` so the next session can pick up from it.'
18536
18537
  );
18537
18538
  } else {
18538
- const at2 = summary.current ? `journey step ${summary.current.stepNumber}/${summary.current.of} (${summary.current.step})` : "no journey step recorded yet";
18539
- lines.push(`${who}: ${at2} \xB7 ${summary.count} postcard${summary.count === 1 ? "" : "s"} since ${minute(summary.startedAt)}.`);
18540
- if (summary.last) lines.push(`Last postcard (${minute(summary.last.ts)}): ${summary.last.message}`);
18541
- const pastPlan = (summary.current?.stepNumber ?? 0) > JOURNEY_STEPS.indexOf("plan") + 1;
18539
+ const at2 = summary2.current ? `journey step ${summary2.current.stepNumber}/${summary2.current.of} (${summary2.current.step})` : "no journey step recorded yet";
18540
+ lines.push(`${who}: ${at2} \xB7 ${summary2.count} postcard${summary2.count === 1 ? "" : "s"} since ${minute(summary2.startedAt)}.`);
18541
+ if (summary2.last) lines.push(`Last postcard (${minute(summary2.last.ts)}): ${summary2.last.message}`);
18542
+ const pastPlan = (summary2.current?.stepNumber ?? 0) > JOURNEY_STEPS.indexOf("plan") + 1;
18542
18543
  lines.push(
18543
- !summary.current ? "No journey step recorded yet: a new app starts with discovery (step 1); one whose SPEC.md the customer approved continues from its current state. `himi progress` prints every postcard." : pastPlan ? `Resume at step ${summary.current.stepNumber}. Do not restart discovery: the approved plan is SPEC.md + MOBILE-UX.md.` : `Resume at step ${summary.current.stepNumber}. Implementation has not started: treat SPEC.md + MOBILE-UX.md as a draft unless the journal records the customer approving them.`
18544
+ !summary2.current ? "No journey step recorded yet: a new app starts with discovery (step 1); one whose SPEC.md the customer approved continues from its current state. `himi progress` prints every postcard." : pastPlan ? `Resume at step ${summary2.current.stepNumber}. Do not restart discovery: the approved plan is SPEC.md + MOBILE-UX.md.` : `Resume at step ${summary2.current.stepNumber}. Implementation has not started: treat SPEC.md + MOBILE-UX.md as a draft unless the journal records the customer approving them.`
18544
18545
  );
18545
18546
  }
18546
18547
  if (open.length) {
@@ -18620,8 +18621,8 @@ var init_progress = __esm({
18620
18621
  "src/progress.ts"() {
18621
18622
  JOURNEY_STEPS = ["discover", "classify", "plan", "implement", "brand", "validate", "review", "download"];
18622
18623
  CLI_AUTHOR = "himi";
18623
- AUTO_MILESTONES = ["first-look", "test", "draft"];
18624
- MILESTONE_STEP = { "first-look": "implement", test: "validate", draft: "review" };
18624
+ AUTO_MILESTONES = ["first-look", "test", "draft", "still-building"];
18625
+ MILESTONE_STEP = { "first-look": "implement", test: "validate", draft: "review", "still-building": "implement" };
18625
18626
  AGENT_WINS_WITHIN_MS = 5 * 60 * 1e3;
18626
18627
  isCliEntry = (e) => e.by === CLI_AUTHOR;
18627
18628
  shasOf = (images) => new Set(Array.isArray(images) ? images.flatMap((i) => i && typeof i === "object" && typeof i.sha256 === "string" ? [i.sha256] : []) : []);
@@ -19328,13 +19329,13 @@ async function recordState(dir, r, deps) {
19328
19329
  if (who.root === "") return "legacy";
19329
19330
  return who.root === runRootId(dir) ? "live" : "stale";
19330
19331
  }
19331
- function openPreviewLog(dir) {
19332
+ function openPreviewLog(dir, name = "preview.log") {
19332
19333
  const himiDir2 = join31(dir, ".himi");
19333
19334
  if (existsSync28(himiDir2) && !lstatSync6(himiDir2).isDirectory()) {
19334
19335
  throw new PreviewStartError(`${himiDir2} is not a real directory (a symlink?) \u2014 refusing to write the preview log through it`, "");
19335
19336
  }
19336
19337
  mkdirSync14(himiDir2, { recursive: true });
19337
- const path2 = join31(himiDir2, "preview.log");
19338
+ const path2 = join31(himiDir2, name);
19338
19339
  let existing;
19339
19340
  try {
19340
19341
  existing = lstatSync6(path2);
@@ -19464,10 +19465,10 @@ var init_preview = __esm({
19464
19465
  }
19465
19466
  },
19466
19467
  kill: (pid, signal) => process.kill(pid, signal),
19467
- portFree: (port) => new Promise((resolve52) => {
19468
+ portFree: (port) => new Promise((resolve53) => {
19468
19469
  const s = createServer2();
19469
- s.once("error", () => resolve52(false));
19470
- s.listen(port, "127.0.0.1", () => s.close(() => resolve52(true)));
19470
+ s.once("error", () => resolve53(false));
19471
+ s.listen(port, "127.0.0.1", () => s.close(() => resolve53(true)));
19471
19472
  }),
19472
19473
  sleep: (ms) => new Promise((r) => setTimeout(r, ms)),
19473
19474
  servingIdentity: async (origin) => {
@@ -19651,17 +19652,17 @@ ${BLOCK_END}`;
19651
19652
  ${block}
19652
19653
  `;
19653
19654
  }
19654
- function markersIntact(text6) {
19655
- const begins = text6.split(BLOCK_BEGIN).length - 1;
19656
- const ends = text6.split(BLOCK_END).length - 1;
19655
+ function markersIntact(text8) {
19656
+ const begins = text8.split(BLOCK_BEGIN).length - 1;
19657
+ const ends = text8.split(BLOCK_END).length - 1;
19657
19658
  if (begins === 0 && ends === 0) return true;
19658
- return begins === 1 && ends === 1 && text6.indexOf(BLOCK_BEGIN) < text6.indexOf(BLOCK_END) && BLOCK_RE.test(text6);
19659
+ return begins === 1 && ends === 1 && text8.indexOf(BLOCK_BEGIN) < text8.indexOf(BLOCK_END) && BLOCK_RE.test(text8);
19659
19660
  }
19660
- function withoutBlock(text6) {
19661
- const m = BLOCK_RE.exec(text6);
19662
- if (!m) return text6;
19663
- const before = text6.slice(0, m.index).replace(/\n+$/, "");
19664
- const after = text6.slice(m.index + m[0].length).replace(/^\n+/, "");
19661
+ function withoutBlock(text8) {
19662
+ const m = BLOCK_RE.exec(text8);
19663
+ if (!m) return text8;
19664
+ const before = text8.slice(0, m.index).replace(/\n+$/, "");
19665
+ const after = text8.slice(m.index + m[0].length).replace(/^\n+/, "");
19665
19666
  if (!before) return after;
19666
19667
  if (!after) return `${before}
19667
19668
  `;
@@ -19669,8 +19670,8 @@ function withoutBlock(text6) {
19669
19670
 
19670
19671
  ${after}`;
19671
19672
  }
19672
- function blockVersion(text6) {
19673
- return STAMP_RE.exec(text6)?.[1] ?? null;
19673
+ function blockVersion(text8) {
19674
+ return STAMP_RE.exec(text8)?.[1] ?? null;
19674
19675
  }
19675
19676
  function agentsMdBody(appName2) {
19676
19677
  const gates = APPROVAL_GATES.map((g) => `- \`${g.id}\` \u2014 ${g.label}`).join("\n");
@@ -20611,7 +20612,7 @@ async function injectRequest(listener, request) {
20611
20612
  req.headers.host ??= url.host;
20612
20613
  if (body && body.length > 0) req.push(body);
20613
20614
  req.push(null);
20614
- return await new Promise((resolve52, reject) => {
20615
+ return await new Promise((resolve53, reject) => {
20615
20616
  const streamed = () => {
20616
20617
  queueMicrotask(() => {
20617
20618
  req.emit("close");
@@ -20627,7 +20628,7 @@ async function injectRequest(listener, request) {
20627
20628
  else headers2.set(name, value);
20628
20629
  }
20629
20630
  const bodyless = out2.status === 204 || out2.status === 304 || request.method === "HEAD";
20630
- resolve52(new Response(bodyless ? null : out2.body, { status: out2.status, headers: headers2 }));
20631
+ resolve53(new Response(bodyless ? null : out2.body, { status: out2.status, headers: headers2 }));
20631
20632
  }, streamed);
20632
20633
  res.on("inject-error", reject);
20633
20634
  try {
@@ -21165,8 +21166,8 @@ async function postJson(path2, body, auth) {
21165
21166
  headers: { "Content-Type": "application/json", ...auth ? { Authorization: auth } : {} },
21166
21167
  body: JSON.stringify(body)
21167
21168
  });
21168
- const text6 = await res.text();
21169
- return { status: res.status, json: text6 ? JSON.parse(text6) : {} };
21169
+ const text8 = await res.text();
21170
+ return { status: res.status, json: text8 ? JSON.parse(text8) : {} };
21170
21171
  }
21171
21172
  async function mintVisitor(clientId) {
21172
21173
  const r = await postJson("/oauth2/token", { clientId, grantType: "anonymous" });
@@ -21412,13 +21413,13 @@ function recordWixTraffic(gw, app, adminUrl, ev) {
21412
21413
  full.respBody = truncate(redactBodyString(full.respBody));
21413
21414
  gw.traffic.push(full);
21414
21415
  if (gw.traffic.length > TRAFFIC_CAP) gw.traffic.splice(0, gw.traffic.length - TRAFFIC_CAP);
21415
- const text6 = `event: wix.traffic
21416
+ const text8 = `event: wix.traffic
21416
21417
  data: ${JSON.stringify(full)}
21417
21418
 
21418
21419
  `;
21419
21420
  for (const res of [...gw.clients]) {
21420
21421
  try {
21421
- res.write(text6);
21422
+ res.write(text8);
21422
21423
  } catch {
21423
21424
  gw.clients.delete(res);
21424
21425
  }
@@ -21780,10 +21781,10 @@ async function mintAnonymousVisitor(clientId, fetchImpl = fetch) {
21780
21781
  headers: { "Content-Type": "application/json" },
21781
21782
  body: JSON.stringify({ clientId, grantType: "anonymous" })
21782
21783
  });
21783
- const text6 = await res.text();
21784
+ const text8 = await res.text();
21784
21785
  let j = {};
21785
21786
  try {
21786
- j = text6 ? JSON.parse(text6) : {};
21787
+ j = text8 ? JSON.parse(text8) : {};
21787
21788
  } catch {
21788
21789
  }
21789
21790
  if (res.status !== 200 || !j.access_token) throw new Error(`visitor mint failed (${res.status})`);
@@ -21947,10 +21948,10 @@ ${session}`);
21947
21948
  }),
21948
21949
  timedOut
21949
21950
  ]);
21950
- const text6 = await Promise.race([res.text(), timedOut]);
21951
+ const text8 = await Promise.race([res.text(), timedOut]);
21951
21952
  let j = {};
21952
21953
  try {
21953
- j = text6 ? JSON.parse(text6) : {};
21954
+ j = text8 ? JSON.parse(text8) : {};
21954
21955
  } catch {
21955
21956
  }
21956
21957
  if (res.status < 200 || res.status >= 300 || !j.signedInstance)
@@ -22301,9 +22302,9 @@ var init_images = __esm({
22301
22302
  });
22302
22303
 
22303
22304
  // ../../core/dev-server/src/l10n-catalog.ts
22304
- function parseCatalog(text6) {
22305
+ function parseCatalog(text8) {
22305
22306
  try {
22306
- const c = JSON.parse(text6);
22307
+ const c = JSON.parse(text8);
22307
22308
  return c && typeof c === "object" && c.messages && typeof c.messages === "object" ? c : void 0;
22308
22309
  } catch {
22309
22310
  return void 0;
@@ -22784,9 +22785,9 @@ async function getAndroidPixelDims(serial) {
22784
22785
  try {
22785
22786
  const args = serial ? ["-s", serial, "shell", "wm", "size"] : ["shell", "wm", "size"];
22786
22787
  const out2 = await runner.capture(ADB_BIN, args);
22787
- const text6 = out2.toString("utf8");
22788
- const override = /Override size:\s*(\d+)x(\d+)/.exec(text6);
22789
- const physical = /Physical size:\s*(\d+)x(\d+)/.exec(text6);
22788
+ const text8 = out2.toString("utf8");
22789
+ const override = /Override size:\s*(\d+)x(\d+)/.exec(text8);
22790
+ const physical = /Physical size:\s*(\d+)x(\d+)/.exec(text8);
22790
22791
  const m = override ?? physical;
22791
22792
  if (!m) throw new Error("could not parse wm size");
22792
22793
  const dims = { width: parseInt(m[1], 10), height: parseInt(m[2], 10) };
@@ -23161,12 +23162,12 @@ function finalizeRecording(key2, rec) {
23161
23162
  if (rec.finalize) return rec.finalize;
23162
23163
  rec.finalize = (async () => {
23163
23164
  clearTimeout(rec.autoStop);
23164
- await new Promise((resolve52) => {
23165
+ await new Promise((resolve53) => {
23165
23166
  let done = false;
23166
23167
  const finish2 = () => {
23167
23168
  if (!done) {
23168
23169
  done = true;
23169
- resolve52();
23170
+ resolve53();
23170
23171
  }
23171
23172
  };
23172
23173
  rec.child.once("close", finish2);
@@ -23277,7 +23278,7 @@ var init_preview2 = __esm({
23277
23278
  DEVICE_LIST_TIMEOUT_MS = 5e3;
23278
23279
  defaultRunner2 = {
23279
23280
  capture(cmd, args, timeoutMs = DEFAULT_CAPTURE_TIMEOUT_MS) {
23280
- return new Promise((resolve52, reject) => {
23281
+ return new Promise((resolve53, reject) => {
23281
23282
  const child = spawn4(cmd, args, { stdio: ["ignore", "pipe", "pipe"] });
23282
23283
  const out2 = [];
23283
23284
  const err = [];
@@ -23299,7 +23300,7 @@ var init_preview2 = __esm({
23299
23300
  child.stderr.on("data", (b) => err.push(b));
23300
23301
  child.on("error", (e) => settle(reject, e));
23301
23302
  child.on("close", (code) => {
23302
- if (code === 0) settle(resolve52, Buffer.concat(out2));
23303
+ if (code === 0) settle(resolve53, Buffer.concat(out2));
23303
23304
  else settle(reject, new Error(`${cmd} exited ${code}: ${Buffer.concat(err).toString("utf8")}`));
23304
23305
  });
23305
23306
  });
@@ -23719,7 +23720,7 @@ function buildOpenApi(descriptors) {
23719
23720
  // Widgets
23720
23721
  "/__dev/widget/preview": ["get", "Render a frozen widget snapshot."]
23721
23722
  };
23722
- for (const [p, [method, summary]] of Object.entries(control)) {
23723
+ for (const [p, [method, summary2]] of Object.entries(control)) {
23723
23724
  const responses = p === "/__dev/render" ? {
23724
23725
  "200": { description: "completed render (legacy mode, or clean envelope verdict)" },
23725
23726
  "400": { description: "invalid_json" },
@@ -23727,7 +23728,7 @@ function buildOpenApi(descriptors) {
23727
23728
  "422": { description: "completed render with a failing envelope:1 verdict" },
23728
23729
  "503": { description: "dev plane unavailable" }
23729
23730
  } : { "200": { description: "ok" } };
23730
- paths[p] = { [method]: { summary, responses } };
23731
+ paths[p] = { [method]: { summary: summary2, responses } };
23731
23732
  }
23732
23733
  for (const scope of ["net", "wix"]) {
23733
23734
  paths[`/__dev/${scope}/rules`] = {
@@ -24155,9 +24156,9 @@ function mdScan(src, linkStyle = "") {
24155
24156
  }
24156
24157
  return out2 + mdEsc(src.slice(last));
24157
24158
  }
24158
- function mdInline(text6, linkColor) {
24159
+ function mdInline(text8, linkColor) {
24159
24160
  const safe = typeof linkColor === "string" && /^[#a-zA-Z0-9(),.%\s-]+$/.test(linkColor) ? linkColor : "";
24160
- return { __html: mdScan(text6, safe ? ` style="color:${safe}"` : "") };
24161
+ return { __html: mdScan(text8, safe ? ` style="color:${safe}"` : "") };
24161
24162
  }
24162
24163
  function sevColor(ctx, sev) {
24163
24164
  switch (sev) {
@@ -27526,9 +27527,9 @@ function createRicosHostSession(o) {
27526
27527
  if (!state.ready || state.unavailable) return;
27527
27528
  o.send({ v: 1, type: "command", name, ...arg?.level ? { level: arg.level } : {}, ...arg?.value ? { value: arg.value } : {} });
27528
27529
  },
27529
- insertLink(url, text6) {
27530
+ insertLink(url, text8) {
27530
27531
  if (!state.ready || state.unavailable || !isSafeEditorLink(url)) return false;
27531
- o.send({ v: 1, type: "insertLink", url: url.trim(), ...text6 ? { text: text6 } : {} });
27532
+ o.send({ v: 1, type: "insertLink", url: url.trim(), ...text8 ? { text: text8 } : {} });
27532
27533
  return true;
27533
27534
  },
27534
27535
  removeLink() {
@@ -27551,12 +27552,12 @@ function createRicosHostSession(o) {
27551
27552
  flush() {
27552
27553
  if (!state.ready || state.unavailable || disposed) return Promise.resolve(null);
27553
27554
  const requestId2 = newId();
27554
- return new Promise((resolve52) => {
27555
+ return new Promise((resolve53) => {
27555
27556
  const timer = t.setTimeout(() => {
27556
27557
  flushes.delete(requestId2);
27557
- resolve52(null);
27558
+ resolve53(null);
27558
27559
  }, flushMs);
27559
- flushes.set(requestId2, { resolve: resolve52, timer });
27560
+ flushes.set(requestId2, { resolve: resolve53, timer });
27560
27561
  o.send({ v: 1, type: "requestContent", requestId: requestId2 });
27561
27562
  });
27562
27563
  },
@@ -27609,7 +27610,7 @@ async function runRicosImageUpload(o) {
27609
27610
  };
27610
27611
  session.insertMediaPlaceholder(requestId2);
27611
27612
  try {
27612
- const entry = await new Promise((resolve52) => {
27613
+ const entry = await new Promise((resolve53) => {
27613
27614
  let done = false;
27614
27615
  let unsub = () => {
27615
27616
  };
@@ -27618,7 +27619,7 @@ async function runRicosImageUpload(o) {
27618
27619
  done = true;
27619
27620
  t.clearTimeout(timer);
27620
27621
  queueMicrotask(() => unsub());
27621
- resolve52(v);
27622
+ resolve53(v);
27622
27623
  };
27623
27624
  const timer = t.setTimeout(() => finish2(null), o.timeoutMs ?? RICOS_UPLOAD_TIMEOUT_MS);
27624
27625
  unsub = deps.subscribe(`${svc}.uploads.${requestId2}`, (v) => {
@@ -29225,13 +29226,13 @@ function NamedImage({ node, alt, env }) {
29225
29226
  }
29226
29227
  function Block({ b, env, depth }) {
29227
29228
  const { ctx } = env;
29228
- const text6 = ctx.color("textPrimary", "#13171e");
29229
+ const text8 = ctx.color("textPrimary", "#13171e");
29229
29230
  const secondary = ctx.color("textSecondary", "#5c6370");
29230
29231
  const border = ctx.color("border", "#e5e5e5");
29231
29232
  switch (b.kind) {
29232
29233
  case "paragraph":
29233
29234
  return /* @__PURE__ */ jsx7("p", { ...tid(env, b.id, b.kind), style: {
29234
- ...textStyle(ctx, "body", text6),
29235
+ ...textStyle(ctx, "body", text8),
29235
29236
  textAlign: b.align,
29236
29237
  paddingInlineStart: b.indent ? Math.min(b.indent, 8) * 16 : void 0,
29237
29238
  minHeight: b.spans.length ? void 0 : "1.5em"
@@ -29239,7 +29240,7 @@ function Block({ b, env, depth }) {
29239
29240
  case "heading":
29240
29241
  return React6.createElement(
29241
29242
  `h${b.level}`,
29242
- { ...tid(env, b.id, b.kind), style: textStyle(ctx, RICH_CONTENT_HEADING_TOKENS[b.level - 1], text6) },
29243
+ { ...tid(env, b.id, b.kind), style: textStyle(ctx, RICH_CONTENT_HEADING_TOKENS[b.level - 1], text8) },
29243
29244
  /* @__PURE__ */ jsx7(Spans, { spans: b.spans, env })
29244
29245
  );
29245
29246
  case "list": {
@@ -29249,7 +29250,7 @@ function Block({ b, env, depth }) {
29249
29250
  {
29250
29251
  ...tid(env, b.id, b.kind),
29251
29252
  start: b.ordered ? b.start : void 0,
29252
- style: { ...textStyle(ctx, "body", text6), paddingInlineStart: 24, listStyleType: b.ordered ? "decimal" : depth % 2 ? "circle" : "disc" },
29253
+ style: { ...textStyle(ctx, "body", text8), paddingInlineStart: 24, listStyleType: b.ordered ? "decimal" : depth % 2 ? "circle" : "disc" },
29253
29254
  children: b.items.map((item, i) => /* @__PURE__ */ jsx7("li", { "data-component-id": `${env.componentId}/${item.id}`, id: `${env.componentId}/${item.id}`, style: { marginBottom: 6 }, children: /* @__PURE__ */ jsx7("div", { style: { display: "flex", flexDirection: "column", gap: 6 }, children: item.blocks.map((child, j) => /* @__PURE__ */ jsx7(Block, { b: child, env, depth: depth + 1 }, `${j}:${child.id}`)) }) }, `${i}:${item.id}`))
29254
29255
  }
29255
29256
  );
@@ -29263,7 +29264,7 @@ function Block({ b, env, depth }) {
29263
29264
  overflowWrap: "anywhere",
29264
29265
  fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace",
29265
29266
  fontSize: "0.9em",
29266
- color: text6,
29267
+ color: text8,
29267
29268
  background: ctx.color("surfaceSecondary", "#f5f5f7"),
29268
29269
  padding: 10,
29269
29270
  borderRadius: 6
@@ -29282,11 +29283,11 @@ function Block({ b, env, depth }) {
29282
29283
  case "button":
29283
29284
  return /* @__PURE__ */ jsx7(LinkButton, { label: b.label, url: b.url, env, testId: `${env.componentId}/${b.id}` });
29284
29285
  case "table":
29285
- return /* @__PURE__ */ jsx7("table", { ...tid(env, b.id, b.kind), style: { width: "100%", borderCollapse: "collapse", ...textStyle(ctx, "body", text6) }, children: /* @__PURE__ */ jsx7("tbody", { children: b.rows.map((row, r) => /* @__PURE__ */ jsx7("tr", { children: row.map((cell, c) => /* @__PURE__ */ jsx7("td", { style: { border: `1px solid ${border}`, padding: 6, whiteSpace: "pre-wrap", verticalAlign: "top" }, children: cell }, c)) }, r)) }) });
29286
+ return /* @__PURE__ */ jsx7("table", { ...tid(env, b.id, b.kind), style: { width: "100%", borderCollapse: "collapse", ...textStyle(ctx, "body", text8) }, children: /* @__PURE__ */ jsx7("tbody", { children: b.rows.map((row, r) => /* @__PURE__ */ jsx7("tr", { children: row.map((cell, c) => /* @__PURE__ */ jsx7("td", { style: { border: `1px solid ${border}`, padding: 6, whiteSpace: "pre-wrap", verticalAlign: "top" }, children: cell }, c)) }, r)) }) });
29286
29287
  case "card": {
29287
29288
  const title = richContentCardTitle(b);
29288
29289
  return /* @__PURE__ */ jsxs4("div", { ...tid(env, b.id, b.kind), role: "group", "aria-label": title, style: { border: `1px solid ${border}`, borderRadius: 8, padding: 12, display: "flex", flexDirection: "column", gap: 6 }, children: [
29289
- /* @__PURE__ */ jsx7("div", { style: textStyle(ctx, "bodyEmphasis", text6), children: title }),
29290
+ /* @__PURE__ */ jsx7("div", { style: textStyle(ctx, "bodyEmphasis", text8), children: title }),
29290
29291
  b.url ? /* @__PURE__ */ jsxs4(Fragment3, { children: [
29291
29292
  /* @__PURE__ */ jsx7("div", { style: textStyle(ctx, "caption", secondary), children: richContentHost(b.url) }),
29292
29293
  /* @__PURE__ */ jsx7(LinkButton, { label: RICH_CONTENT_OPEN_LABEL, url: b.url, env, testId: `${env.componentId}/${b.id}/open` })
@@ -30882,8 +30883,8 @@ function applyA11ySemantics(node, ctx, extra) {
30882
30883
  extra["aria-live"] = str9(announce.politeness) === "assertive" ? "assertive" : "polite";
30883
30884
  extra["aria-atomic"] = "true";
30884
30885
  extra["aria-relevant"] = trigger === "on_appear" ? "additions" : trigger === "both" ? "additions text" : "text";
30885
- const text6 = str9(announce.text);
30886
- if (text6 && extra["aria-label"] == null) extra["aria-label"] = text6;
30886
+ const text8 = str9(announce.text);
30887
+ if (text8 && extra["aria-label"] == null) extra["aria-label"] = text8;
30887
30888
  }
30888
30889
  }
30889
30890
  return hint;
@@ -32473,13 +32474,13 @@ var init_renderNode = __esm({
32473
32474
  const C = ctx.color;
32474
32475
  const isBoundText = typeof props.textPath === "string" && isBinding(props.textPath);
32475
32476
  const bound = useResolvedScalar(isBoundText ? props.textPath : void 0, ctx);
32476
- const text6 = isBoundText ? typeof bound === "string" || typeof bound === "number" ? String(bound) : "" : literalErrorText;
32477
+ const text8 = isBoundText ? typeof bound === "string" || typeof bound === "number" ? String(bound) : "" : literalErrorText;
32477
32478
  if (props.visible === false) return null;
32478
- if (isBoundText && !text6) return null;
32479
+ if (isBoundText && !text8) return null;
32479
32480
  const c = (props.severity ?? "error") === "warning" ? C("warning", "#E89F00") : C("danger", "#E62214");
32480
32481
  return /* @__PURE__ */ jsxs5("div", { role: "alert", style: { display: "flex", gap: 6, alignItems: "center", color: c, fontSize: 13, fontFamily: bodyFont(ctx), ...wrap }, children: [
32481
32482
  /* @__PURE__ */ jsx8("span", { "aria-hidden": "true", children: "\u26A0" }),
32482
- text6
32483
+ text8
32483
32484
  ] });
32484
32485
  });
32485
32486
  FormView = withExtras("FormView", ({ props, ctx, wrap }) => {
@@ -33966,8 +33967,8 @@ var init_renderNode = __esm({
33966
33967
  const base = invalidIcon ? ICON_FALLBACK : (typeof props.icon === "string" && props.icon ? props.icon : "star").replace(/_fill$/, "");
33967
33968
  const filledCount = Math.round(val);
33968
33969
  const onClickStar = (i) => ctx.onAction && props.interactive && props.onChange ? () => ctx.onAction({ ...props.onChange, params: { ...props.onChange?.params ?? {}, value: String(i + 1) } }) : void 0;
33969
- const cv = Number.isFinite(val) ? Math.max(0, Math.min(max, val)) : 0, summary = `${Number.isInteger(cv) ? cv : cv.toFixed(1)} of ${max}`;
33970
- return /* @__PURE__ */ jsx8("span", { style: { display: "inline-flex", alignItems: "center", gap: m.gap, ...wrap }, role: props.interactive ? "group" : "img", "aria-label": `Rating, ${summary}`, "data-a11y-value": summary, children: Array.from({ length: max }).map((_, i) => {
33970
+ const cv = Number.isFinite(val) ? Math.max(0, Math.min(max, val)) : 0, summary2 = `${Number.isInteger(cv) ? cv : cv.toFixed(1)} of ${max}`;
33971
+ return /* @__PURE__ */ jsx8("span", { style: { display: "inline-flex", alignItems: "center", gap: m.gap, ...wrap }, role: props.interactive ? "group" : "img", "aria-label": `Rating, ${summary2}`, "data-a11y-value": summary2, children: Array.from({ length: max }).map((_, i) => {
33971
33972
  const filled = i < filledCount;
33972
33973
  return /* @__PURE__ */ jsx8("span", { onClick: onClickStar(i), style: { display: "inline-grid", placeItems: "center", width: m.cell, height: m.cell, cursor: props.interactive ? "pointer" : "default", opacity: filled ? 1 : 0.28 }, children: iconSvg(filled ? `${base}_fill` : base, { size: m.glyph, color: col }) }, i);
33973
33974
  }) });
@@ -35719,8 +35720,8 @@ function buildRenderTree(node, path2 = "$", provider = null, itemScope = void 0,
35719
35720
  const asText3 = VALUE_PATH_TEXT.get(type);
35720
35721
  if (typeof valuePath === "string" && asText3 && (valuePath.startsWith("$service:") || /^\$item\.[^.]/.test(valuePath))) {
35721
35722
  const r = resolveTreeScalar(valuePath, provider, itemScope);
35722
- const text6 = asText3(r, props);
35723
- if (text6 !== void 0) values.value = text6;
35723
+ const text8 = asText3(r, props);
35724
+ if (text8 !== void 0) values.value = text8;
35724
35725
  else if (r !== void 0 && r !== null) {
35725
35726
  delete values.value;
35726
35727
  }
@@ -35901,7 +35902,7 @@ async function stabilizePngPage(page, scheme) {
35901
35902
  const browser = globalThis;
35902
35903
  browser.document.documentElement.style.colorScheme = selected;
35903
35904
  await browser.document.fonts?.ready;
35904
- await new Promise((resolve52) => browser.requestAnimationFrame(() => browser.requestAnimationFrame(() => resolve52())));
35905
+ await new Promise((resolve53) => browser.requestAnimationFrame(() => browser.requestAnimationFrame(() => resolve53())));
35905
35906
  }, scheme);
35906
35907
  }
35907
35908
  async function renderPngBatch(items) {
@@ -37915,14 +37916,14 @@ function discoverNativeActions(appDir3) {
37915
37916
  const roots = NATIVE_SOURCE_ROOTS.filter(({ directory }) => existsSync38(resolve31(appDir3, directory)));
37916
37917
  const sources = roots.flatMap(({ directory, extension, buildDirMaxDepth }) => nativeSourceFiles(resolve31(appDir3, directory), extension, buildDirMaxDepth));
37917
37918
  const registered = sources.flatMap((source) => {
37918
- let text6;
37919
+ let text8;
37919
37920
  try {
37920
- text6 = readFileSync40(source, "utf8");
37921
+ text8 = readFileSync40(source, "utf8");
37921
37922
  } catch (err) {
37922
37923
  if (!vanished(err)) throw err;
37923
37924
  return [];
37924
37925
  }
37925
- return registeredActions(text6);
37926
+ return registeredActions(text8);
37926
37927
  });
37927
37928
  return {
37928
37929
  registered: new Set(registered),
@@ -38021,11 +38022,11 @@ function strip(line) {
38021
38022
  function scalar(v) {
38022
38023
  return v.trim().replace(/^["']|["']$/g, "");
38023
38024
  }
38024
- function parseRolloutYaml(text6) {
38025
+ function parseRolloutYaml(text8) {
38025
38026
  const cfg = { app: "", rings: [] };
38026
38027
  let cur = null;
38027
38028
  let inRings = false;
38028
- for (const raw of text6.split("\n")) {
38029
+ for (const raw of text8.split("\n")) {
38029
38030
  const line = strip(raw);
38030
38031
  if (!line.trim()) continue;
38031
38032
  const top = /^([a-zA-Z_]+):\s*(.*)$/.exec(line);
@@ -38056,10 +38057,10 @@ function assignRing(r, key2, val) {
38056
38057
  else if (key2 === "percent") r.percent = Number(v);
38057
38058
  else if (key2 === "minBinaryVersion") r.minBinaryVersion = v;
38058
38059
  }
38059
- function parseRevocationsYaml(text6) {
38060
+ function parseRevocationsYaml(text8) {
38060
38061
  const out2 = { app: "", revoked: [] };
38061
38062
  let inList = false;
38062
- for (const raw of text6.split("\n")) {
38063
+ for (const raw of text8.split("\n")) {
38063
38064
  const line = strip(raw);
38064
38065
  if (!line.trim()) continue;
38065
38066
  const top = /^([a-zA-Z_]+):\s*(.*)$/.exec(line);
@@ -38416,7 +38417,7 @@ async function runScreenDiagnostics(state, screenId, descriptor) {
38416
38417
  state.app?.module.config,
38417
38418
  state.tier5Dir ?? TIER5_ROOT2
38418
38419
  );
38419
- const report2 = validateScreenDescriptor2(screenId, descriptor, {
38420
+ const report3 = validateScreenDescriptor2(screenId, descriptor, {
38420
38421
  tokens,
38421
38422
  allowedComponents,
38422
38423
  bundleJs,
@@ -38429,7 +38430,7 @@ async function runScreenDiagnostics(state, screenId, descriptor) {
38429
38430
  });
38430
38431
  const telemetry = state.telemetryEvents.filter((e) => e.screenId === screenId && (/^worker_/.test(e.type) || e.type === "bundle_load_revoked")).slice(-20);
38431
38432
  const lastError = [...telemetry].reverse().find((e) => /crash|revoked|error|fail/i.test(e.type)) ?? null;
38432
- const legacy = { ...report2, bundle: bundleName ?? null, telemetry, lastError };
38433
+ const legacy = { ...report3, bundle: bundleName ?? null, telemetry, lastError };
38433
38434
  return foldLegacyReport(legacy, {
38434
38435
  source: "diagnostics",
38435
38436
  screenId,
@@ -38638,11 +38639,11 @@ async function handleOwnerChatRequest(req, res) {
38638
38639
  return sendJson(res, 400, { error: "invalid_json" }, cors);
38639
38640
  }
38640
38641
  const chatroomId = (parsed.chatroomId ?? "").trim();
38641
- const text6 = (parsed.text ?? "").trim();
38642
- if (!chatroomId || !text6) return sendJson(res, 400, { error: "chatroomId_and_text_required" }, cors);
38642
+ const text8 = (parsed.text ?? "").trim();
38643
+ if (!chatroomId || !text8) return sendJson(res, 400, { error: "chatroomId_and_text_required" }, cors);
38643
38644
  const metaSiteId = (parsed.metaSiteId ?? "").trim() || await resolveMetaSiteId();
38644
38645
  if (!metaSiteId) return sendJson(res, 400, { error: "no_meta_site" }, cors);
38645
- const result = await sendChatMessageLive(metaSiteId, chatroomId, text6);
38646
+ const result = await sendChatMessageLive(metaSiteId, chatroomId, text8);
38646
38647
  if (result.ok) return sendJson(res, 200, { ok: true, message: result.message }, cors);
38647
38648
  return sendJson(res, result.status >= 400 && result.status < 600 ? result.status : 502, { ok: false, status: result.status, error: result.error }, cors);
38648
38649
  }
@@ -38903,13 +38904,13 @@ function invalidate(state, ...screenIds) {
38903
38904
  publishSSE(state, "screen.invalidated", { screenIds });
38904
38905
  }
38905
38906
  function publishSSE(state, type, payload) {
38906
- const text6 = `event: ${type}
38907
+ const text8 = `event: ${type}
38907
38908
  data: ${JSON.stringify(payload)}
38908
38909
 
38909
38910
  `;
38910
38911
  for (const res of [...state.sseClients]) {
38911
38912
  try {
38912
- res.write(text6);
38913
+ res.write(text8);
38913
38914
  } catch {
38914
38915
  state.sseClients.delete(res);
38915
38916
  }
@@ -38925,13 +38926,13 @@ function publishAdminSSE(state, type, payload) {
38925
38926
  state.lastDeviceScreen.set(key2, screenId);
38926
38927
  }
38927
38928
  }
38928
- const text6 = `event: ${type}
38929
+ const text8 = `event: ${type}
38929
38930
  data: ${JSON.stringify(payload)}
38930
38931
 
38931
38932
  `;
38932
38933
  for (const res of [...state.adminClients]) {
38933
38934
  try {
38934
- res.write(text6);
38935
+ res.write(text8);
38935
38936
  } catch {
38936
38937
  state.adminClients.delete(res);
38937
38938
  }
@@ -38952,12 +38953,12 @@ function publishDevReady(state, res, extra = {}) {
38952
38953
  hotReloadEnabled: state.hotReloadEnabled,
38953
38954
  ...extra
38954
38955
  };
38955
- const text6 = `event: dev.ready
38956
+ const text8 = `event: dev.ready
38956
38957
  data: ${JSON.stringify(payload)}
38957
38958
 
38958
38959
  `;
38959
38960
  if (res) {
38960
- res.write(text6);
38961
+ res.write(text8);
38961
38962
  } else {
38962
38963
  publishSSE(state, "dev.ready", payload);
38963
38964
  }
@@ -39542,7 +39543,7 @@ async function handleWebWix(req, res, state) {
39542
39543
  }
39543
39544
  if (match) {
39544
39545
  const mocked = match.rule.response;
39545
- if (mocked.delayMs) await new Promise((resolve52) => setTimeout(resolve52, mocked.delayMs));
39546
+ if (mocked.delayMs) await new Promise((resolve53) => setTimeout(resolve53, mocked.delayMs));
39546
39547
  const responseBody = mocked.template ? renderTemplate(mocked.body, match.captures) : mocked.body;
39547
39548
  record2("mock", mocked.status, responseBody, match.rule.id, mocked.headers);
39548
39549
  return reply(200, { status: mocked.status, body: responseBody ?? "" });
@@ -39708,7 +39709,7 @@ async function handleDev(req, res, state) {
39708
39709
  idiomContext
39709
39710
  );
39710
39711
  const legacy = { screenId, scheme, ...render, diagnostics };
39711
- const report2 = foldLegacyReport(legacy, {
39712
+ const report3 = foldLegacyReport(legacy, {
39712
39713
  source: "render",
39713
39714
  screenId,
39714
39715
  ok: diagnostics.ok !== false && render.renderable !== false,
@@ -39720,8 +39721,8 @@ async function handleDev(req, res, state) {
39720
39721
  ],
39721
39722
  evidence: { runId: `dev:${state.startedAt}:${screenId}`, ts: (/* @__PURE__ */ new Date()).toISOString() }
39722
39723
  });
39723
- const status = body.envelope === 1 && !report2.ok ? 422 : 200;
39724
- return sendJson(res, status, report2);
39724
+ const status = body.envelope === 1 && !report3.ok ? 422 : 200;
39725
+ return sendJson(res, status, report3);
39725
39726
  }
39726
39727
  {
39727
39728
  const diagMatch = /^\/__dev\/screen\/(.+?)\/diagnostics(?:\?|$)/.exec(url);
@@ -40182,7 +40183,7 @@ async function handleAdminPlayground(req, res, state) {
40182
40183
  state.app?.module.config,
40183
40184
  state.tier5Dir ?? TIER5_ROOT2
40184
40185
  );
40185
- const report2 = validateScreenDescriptor2(screenId, descriptor, {
40186
+ const report3 = validateScreenDescriptor2(screenId, descriptor, {
40186
40187
  tokens,
40187
40188
  allowedComponents,
40188
40189
  bundleJs,
@@ -40192,7 +40193,7 @@ async function handleAdminPlayground(req, res, state) {
40192
40193
  workerSubtreeScreens: new Set(uiConfig(state.app?.module.config).workerSubtreeScreens ?? []),
40193
40194
  a11y: uiConfig(state.app?.module.config).a11y
40194
40195
  });
40195
- return sendJson(res, report2.ok ? 200 : 422, report2);
40196
+ return sendJson(res, report3.ok ? 200 : 422, report3);
40196
40197
  }
40197
40198
  const detailMatch = /^\/v1\/admin\/screens\/([^/]+)$/.exec(path2);
40198
40199
  if (detailMatch && req.method === "GET") {
@@ -40647,9 +40648,9 @@ async function handleLiveSubscription(req, res, state) {
40647
40648
  },
40648
40649
  body: JSON.stringify({ ...body, app })
40649
40650
  });
40650
- const text6 = await upstream.text();
40651
+ const text8 = await upstream.text();
40651
40652
  res.writeHead(upstream.status, { "Content-Type": "application/json", ...cors });
40652
- res.end(text6 || JSON.stringify({ ok: upstream.ok }));
40653
+ res.end(text8 || JSON.stringify({ ok: upstream.ok }));
40653
40654
  } catch (error) {
40654
40655
  sendJson(res, 502, { error: "admin_server_unreachable", detail: String(error) }, cors);
40655
40656
  }
@@ -41641,12 +41642,12 @@ var init_query = __esm({
41641
41642
  });
41642
41643
 
41643
41644
  // ../../core/dev-server/src/crawl-input.ts
41644
- function resolved(v, resolve52) {
41645
- if (typeof v === "string") return v.startsWith("$service:") ? resolve52(v) : v;
41645
+ function resolved(v, resolve53) {
41646
+ if (typeof v === "string") return v.startsWith("$service:") ? resolve53(v) : v;
41646
41647
  if (Array.isArray(v)) {
41647
41648
  const out2 = v.flatMap((el) => {
41648
41649
  if (typeof el !== "string" || !el.startsWith("$service:")) return [el];
41649
- const r = resolve52(el);
41650
+ const r = resolve53(el);
41650
41651
  return Array.isArray(r) ? r : [r];
41651
41652
  });
41652
41653
  return out2;
@@ -41660,9 +41661,9 @@ function hasRowBinding(props) {
41660
41661
  return itemToken(v) || Array.isArray(v) && v.some(itemToken);
41661
41662
  });
41662
41663
  }
41663
- function declaredOptionIds(props, resolve52) {
41664
+ function declaredOptionIds(props, resolve53) {
41664
41665
  const literal = props.options ?? props.tabs ?? props.chips;
41665
- const bound = props.optionsPath !== void 0 ? resolved(props.optionsPath, resolve52) : void 0;
41666
+ const bound = props.optionsPath !== void 0 ? resolved(props.optionsPath, resolve53) : void 0;
41666
41667
  const raw = Array.isArray(bound) ? bound : literal;
41667
41668
  if (!Array.isArray(raw)) return [];
41668
41669
  const out2 = [];
@@ -41705,24 +41706,24 @@ function seedTokens(state, limit = 48) {
41705
41706
  walk2(state, 0);
41706
41707
  return out2;
41707
41708
  }
41708
- function synthesizeInput(control, resolve52, tokens) {
41709
+ function synthesizeInput(control, resolve53, tokens) {
41709
41710
  const id = `${control.type}.${control.callback}`;
41710
41711
  const row = SYNTHESIZED_CONTROLS[id];
41711
41712
  if (!row) return null;
41712
41713
  const props = control.props;
41713
41714
  if (hasRowBinding(props)) return null;
41714
41715
  if (row.from === "declared-options") {
41715
- const ids = declaredOptionIds(props, resolve52);
41716
+ const ids = declaredOptionIds(props, resolve53);
41716
41717
  if (!ids.length) return null;
41717
41718
  if (row.encoding === "string-array") {
41718
- const currentRaw2 = resolved(props.selectedIds, resolve52);
41719
+ const currentRaw2 = resolved(props.selectedIds, resolve53);
41719
41720
  const current3 = (Array.isArray(currentRaw2) ? currentRaw2 : []).filter((x) => typeof x === "string");
41720
41721
  const pick4 = ids.find((o) => !current3.includes(o));
41721
41722
  if (pick4 === void 0) return null;
41722
41723
  const value = props.multiSelect === true ? [...current3, pick4] : [pick4];
41723
41724
  return { key: row.key, value, from: row.from, current: current3, control: id };
41724
41725
  }
41725
- const currentRaw = resolved(props.selectedId, resolve52);
41726
+ const currentRaw = resolved(props.selectedId, resolve53);
41726
41727
  const current2 = typeof currentRaw === "string" ? currentRaw : void 0;
41727
41728
  const pick3 = ids.find((o) => o !== current2);
41728
41729
  if (pick3 === void 0) return null;
@@ -41730,7 +41731,7 @@ function synthesizeInput(control, resolve52, tokens) {
41730
41731
  }
41731
41732
  if (!acceptsWords(props) || !acceptsWords(control.wrapper ?? {})) return null;
41732
41733
  const binding2 = typeof props.valuePath === "string" ? props.valuePath : typeof props.value === "string" && props.value.startsWith("$service:") ? props.value : void 0;
41733
- const bound = binding2 === void 0 ? void 0 : resolve52(binding2);
41734
+ const bound = binding2 === void 0 ? void 0 : resolve53(binding2);
41734
41735
  const literal = typeof props.value === "string" && !props.value.startsWith("$") ? props.value : void 0;
41735
41736
  const current = typeof bound === "string" ? bound : typeof bound === "number" ? String(bound) : literal ?? "";
41736
41737
  const max = typeof props.maxLength === "number" && props.maxLength > 0 ? props.maxLength : void 0;
@@ -42094,10 +42095,10 @@ function netTransport(rules) {
42094
42095
  if (res.needsUpstream) {
42095
42096
  throw new Error(`net fixture rule ${res.ruleId} is a \`transform\` rule, which needs a real upstream \u2014 not usable in a hermetic crawl`);
42096
42097
  }
42097
- const { status, body: text6 } = res.resolved;
42098
+ const { status, body: text8 } = res.resolved;
42098
42099
  if (status < 200 || status >= 300) throw new Error(`Wix ${status} for ${method} ${bare}`);
42099
42100
  try {
42100
- return text6 ? JSON.parse(text6) : {};
42101
+ return text8 ? JSON.parse(text8) : {};
42101
42102
  } catch {
42102
42103
  return {};
42103
42104
  }
@@ -42141,10 +42142,10 @@ function layeredTransport(layers2) {
42141
42142
  });
42142
42143
  if (res.matched) {
42143
42144
  if (res.needsUpstream) throw new Error(`net fixture rule ${res.ruleId} is a \`transform\` rule, which needs a real upstream \u2014 not usable in a hermetic crawl`);
42144
- const { status, body: text6 } = res.resolved;
42145
+ const { status, body: text8 } = res.resolved;
42145
42146
  if (status < 200 || status >= 300) throw new Error(`Wix ${status} for ${m} ${bare}`);
42146
42147
  try {
42147
- return text6 ? JSON.parse(text6) : {};
42148
+ return text8 ? JSON.parse(text8) : {};
42148
42149
  } catch {
42149
42150
  return {};
42150
42151
  }
@@ -44166,13 +44167,13 @@ var init_build2 = __esm({
44166
44167
  });
44167
44168
 
44168
44169
  // ../mobile-dist/release-pin.ts
44169
- function withPinnedRelease(text6, releaseId) {
44170
- if (!releaseId) return text6;
44170
+ function withPinnedRelease(text8, releaseId) {
44171
+ if (!releaseId) return text8;
44171
44172
  let cfg;
44172
44173
  try {
44173
- cfg = JSON.parse(text6);
44174
+ cfg = JSON.parse(text8);
44174
44175
  } catch {
44175
- return text6;
44176
+ return text8;
44176
44177
  }
44177
44178
  cfg.releaseId = releaseId;
44178
44179
  cfg.releasePinned = true;
@@ -44290,10 +44291,10 @@ async function bakeCatalogs(app, locale, fetchCatalog, putDoc) {
44290
44291
  if (!declared.length) return out2;
44291
44292
  const tags = [.../* @__PURE__ */ new Set([...locale?.default ? [locale.default] : [], ...declared])];
44292
44293
  for (const tag of tags) {
44293
- const text6 = await fetchCatalog(tag);
44294
- if (!text6) continue;
44295
- const hash = sha2564(text6);
44296
- await putDoc(app, hash, text6);
44294
+ const text8 = await fetchCatalog(tag);
44295
+ if (!text8) continue;
44296
+ const hash = sha2564(text8);
44297
+ await putDoc(app, hash, text8);
44297
44298
  out2[tag] = hash;
44298
44299
  }
44299
44300
  return out2;
@@ -44385,9 +44386,9 @@ async function snapshotTokens(app, base, fetchTokens, put) {
44385
44386
  }
44386
44387
  };
44387
44388
  }
44388
- const text6 = await res.text();
44389
- await put(text6);
44390
- return { hash: sha2564(text6) };
44389
+ const text8 = await res.text();
44390
+ await put(text8);
44391
+ return { hash: sha2564(text8) };
44391
44392
  }
44392
44393
  function tokensSnapshotMessage(app, base, status) {
44393
44394
  const why = status === 400 ? `the dev-server rejected the app id as malformed (400 bad_app). It must be lowercase letters, digits and hyphens, starting with a letter or digit, at most 63 characters, with at most one interior dot \u2014 so a leading underscore or an uppercase letter fails, on every build of that app. A single dot is FINE (an owner-namespaced slug), so do not remove one to get past this` : `${status} from the tokens route`;
@@ -44481,23 +44482,23 @@ function maximumBinaryVersion(current, candidate) {
44481
44482
  }
44482
44483
  return current;
44483
44484
  }
44484
- async function withStoreNavBundle(text6, address) {
44485
+ async function withStoreNavBundle(text8, address) {
44485
44486
  let cfg;
44486
44487
  try {
44487
- cfg = JSON.parse(text6);
44488
+ cfg = JSON.parse(text8);
44488
44489
  } catch {
44489
- return text6;
44490
+ return text8;
44490
44491
  }
44491
44492
  const refs = [cfg?.navigation?.navLogicBundle, cfg?.navLogicBundle].filter(
44492
44493
  (r) => !!r && typeof r === "object" && typeof r.url === "string"
44493
44494
  );
44494
- if (refs.length === 0) return text6;
44495
+ if (refs.length === 0) return text8;
44495
44496
  const resolved2 = [];
44496
44497
  for (const ref of refs) {
44497
44498
  const name = bundleNameFromUrl(ref.url);
44498
- if (!name) return text6;
44499
+ if (!name) return text8;
44499
44500
  const addressed = await address(name);
44500
- if (!addressed) return text6;
44501
+ if (!addressed) return text8;
44501
44502
  resolved2.push({ ref, url: addressed.url, sri: addressed.sri });
44502
44503
  }
44503
44504
  for (const { ref, url, sri: integrity2 } of resolved2) {
@@ -44506,10 +44507,10 @@ async function withStoreNavBundle(text6, address) {
44506
44507
  }
44507
44508
  return JSON.stringify(cfg);
44508
44509
  }
44509
- function linkableScreenIdsFrom(text6) {
44510
+ function linkableScreenIdsFrom(text8) {
44510
44511
  let cfg;
44511
44512
  try {
44512
- cfg = JSON.parse(text6);
44513
+ cfg = JSON.parse(text8);
44513
44514
  } catch {
44514
44515
  return [];
44515
44516
  }
@@ -44521,24 +44522,24 @@ function linkableScreenIdsFrom(text6) {
44521
44522
  }
44522
44523
  return [...ids];
44523
44524
  }
44524
- function withoutDevOnlyAppConfig(text6) {
44525
+ function withoutDevOnlyAppConfig(text8) {
44525
44526
  let cfg;
44526
44527
  try {
44527
- cfg = JSON.parse(text6);
44528
+ cfg = JSON.parse(text8);
44528
44529
  } catch {
44529
- return text6;
44530
+ return text8;
44530
44531
  }
44531
- if (!cfg || typeof cfg !== "object" || !("devGatewayConfig" in cfg)) return text6;
44532
+ if (!cfg || typeof cfg !== "object" || !("devGatewayConfig" in cfg)) return text8;
44532
44533
  delete cfg.devGatewayConfig;
44533
44534
  return JSON.stringify(cfg);
44534
44535
  }
44535
- function withProdServeAppConfig(text6, prod) {
44536
- if (!prod?.serve) return text6;
44536
+ function withProdServeAppConfig(text8, prod) {
44537
+ if (!prod?.serve) return text8;
44537
44538
  let cfg;
44538
44539
  try {
44539
- cfg = JSON.parse(text6);
44540
+ cfg = JSON.parse(text8);
44540
44541
  } catch {
44541
- return text6;
44542
+ return text8;
44542
44543
  }
44543
44544
  cfg.useProdServe = true;
44544
44545
  cfg.prodServeBaseUrl = prod.serve;
@@ -44759,10 +44760,10 @@ async function buildFromLoaded(preloaded, dest, opts = {}) {
44759
44760
  if (bytes2.length === 0 || bytes2.length > MAX_AUTHORING_DOC_BYTES) {
44760
44761
  throw new Error(`${file} must be non-empty and at most ${MAX_AUTHORING_DOC_BYTES} bytes to ship with a release.`);
44761
44762
  }
44762
- const text6 = bytes2.toString("utf8");
44763
- if (!Buffer.from(text6, "utf8").equals(bytes2)) throw new Error(`${file} must be canonical UTF-8 Markdown.`);
44763
+ const text8 = bytes2.toString("utf8");
44764
+ if (!Buffer.from(text8, "utf8").equals(bytes2)) throw new Error(`${file} must be canonical UTF-8 Markdown.`);
44764
44765
  const hash = sha2564(bytes2);
44765
- await store.putDoc(app, hash, text6);
44766
+ await store.putDoc(app, hash, text8);
44766
44767
  authoringDocs[name] = { sha256: hash, sri: sri2(bytes2), bytes: bytes2.length, contentType: "text/markdown; charset=utf-8" };
44767
44768
  }
44768
44769
  }
@@ -44961,7 +44962,7 @@ async function buildFromLoaded(preloaded, dest, opts = {}) {
44961
44962
  }
44962
44963
  if (collect2?.lint) {
44963
44964
  const bundleJs = bundleName ? readBuiltBundle(tier5Dir, bundleName) : void 0;
44964
- const report2 = validateScreenDescriptor(screenId, descriptor, {
44965
+ const report3 = validateScreenDescriptor(screenId, descriptor, {
44965
44966
  tokens: lintTokens,
44966
44967
  allowedComponents,
44967
44968
  allowedIcons: KNOWN_ICONS,
@@ -44975,7 +44976,7 @@ async function buildFromLoaded(preloaded, dest, opts = {}) {
44975
44976
  // default, "error"; an in-repo app with ratcheted debt declares "warn".
44976
44977
  a11y: config.a11y
44977
44978
  });
44978
- lint.push({ screenId, ok: report2.ok, issues: report2.issues });
44979
+ lint.push({ screenId, ok: report3.ok, issues: report3.issues });
44979
44980
  }
44980
44981
  if (collect2?.render && (!collect2.render.screenId || collect2.render.screenId === screenId)) {
44981
44982
  const { services, liveCalls, liveReturned, liveErrors } = await renderServicesFor(bundleName, descriptor);
@@ -44993,7 +44994,7 @@ async function buildFromLoaded(preloaded, dest, opts = {}) {
44993
44994
  const bundleName = lb ? bundleNameFromUrl(lb.url) : null;
44994
44995
  if (collect2?.lint) {
44995
44996
  const bundleJs = bundleName ? readBuiltBundle(tier5Dir, bundleName) : void 0;
44996
- const report2 = validateScreenDescriptor(wantedRender, descriptor, {
44997
+ const report3 = validateScreenDescriptor(wantedRender, descriptor, {
44997
44998
  tokens: lintTokens,
44998
44999
  allowedComponents,
44999
45000
  allowedIcons: KNOWN_ICONS,
@@ -45004,7 +45005,7 @@ async function buildFromLoaded(preloaded, dest, opts = {}) {
45004
45005
  workerSubtreeScreens: new Set(config.workerSubtreeScreens ?? []),
45005
45006
  a11y: config.a11y
45006
45007
  });
45007
- lint.push({ screenId: wantedRender, ok: report2.ok, issues: report2.issues });
45008
+ lint.push({ screenId: wantedRender, ok: report3.ok, issues: report3.issues });
45008
45009
  }
45009
45010
  const { services, liveCalls, liveReturned, liveErrors } = await renderServicesFor(bundleName, descriptor);
45010
45011
  const rr = await renderDescriptorWeb(descriptor, renderTokens, renderScheme, collect2.render.png === true, services, collect2.render.width, collect2.render.rtl === true);
@@ -45122,11 +45123,11 @@ async function buildFromLoaded(preloaded, dest, opts = {}) {
45122
45123
  ok: res.ok,
45123
45124
  status: res.status,
45124
45125
  text: async () => {
45125
- const text6 = await res.text();
45126
+ const text8 = await res.text();
45126
45127
  try {
45127
- const doc = JSON.parse(text6);
45128
+ const doc = JSON.parse(text8);
45128
45129
  if (!publishedFontPlan) {
45129
- if (!("fontFaces" in doc)) return text6;
45130
+ if (!("fontFaces" in doc)) return text8;
45130
45131
  const kept = publishableFontFaces(doc.fontFaces);
45131
45132
  if (kept) doc.fontFaces = kept;
45132
45133
  else delete doc.fontFaces;
@@ -45135,12 +45136,12 @@ async function buildFromLoaded(preloaded, dest, opts = {}) {
45135
45136
  doc.fontFaces = servedProjection(publishedFontPlan);
45136
45137
  return JSON.stringify(doc);
45137
45138
  } catch {
45138
- return text6;
45139
+ return text8;
45139
45140
  }
45140
45141
  }
45141
45142
  };
45142
45143
  },
45143
- (text6) => store.putTokens(app, text6)
45144
+ (text8) => store.putTokens(app, text8)
45144
45145
  );
45145
45146
  if (snapshot2.hash) tokensHash = snapshot2.hash;
45146
45147
  if (snapshot2.failure) {
@@ -45170,14 +45171,14 @@ async function buildFromLoaded(preloaded, dest, opts = {}) {
45170
45171
  if (streamPolicyErrors.length) {
45171
45172
  throw new Error(`invalid streamPolicy for release ${app}: ${streamPolicyErrors.join("; ")}`);
45172
45173
  }
45173
- const text6 = await withStoreNavBundle(prodText, async (name) => {
45174
+ const text8 = await withStoreNavBundle(prodText, async (name) => {
45174
45175
  const hash = await addressBundle(name) ?? null;
45175
45176
  const entry = hash ? bundles[name] : void 0;
45176
45177
  return entry ? { url: store.bundleUrl(app, entry.sha256), sri: entry.sri } : null;
45177
45178
  });
45178
- await store.putAppConfig(app, text6);
45179
- appConfigHash = sha2564(text6);
45180
- if (collect2?.descriptors) for (const id of linkableScreenIdsFrom(text6)) linkable.add(id);
45179
+ await store.putAppConfig(app, text8);
45180
+ appConfigHash = sha2564(text8);
45181
+ if (collect2?.descriptors) for (const id of linkableScreenIdsFrom(text8)) linkable.add(id);
45181
45182
  }
45182
45183
  const ahRes = await devFetch(`${base}/v1/action-handlers/${encodeURIComponent(app)}`, { headers: { "X-Himalaya-App": app } });
45183
45184
  if (ahRes.ok) {
@@ -46043,14 +46044,14 @@ function headline(changes) {
46043
46044
  function boardLines(changes) {
46044
46045
  const lines = [];
46045
46046
  for (const c of changes.items) {
46046
- const text6 = lineOf(c);
46047
- const same3 = lines.find((l) => l.text === text6);
46047
+ const text8 = lineOf(c);
46048
+ const same3 = lines.find((l) => l.text === text8);
46048
46049
  if (same3) {
46049
46050
  same3.n++;
46050
46051
  continue;
46051
46052
  }
46052
46053
  lines.push({
46053
- text: text6,
46054
+ text: text8,
46054
46055
  n: 1,
46055
46056
  ...c.kind === "token" && c.from && HEX.test(c.from) ? { from: c.from } : {},
46056
46057
  ...c.kind === "token" && c.to && HEX.test(c.to) ? { to: c.to } : {}
@@ -46370,12 +46371,12 @@ function testRuns(dir) {
46370
46371
  if (!f.endsWith(".json") || f === "latest.json") continue;
46371
46372
  const r = obj(readJson3(join41(att, f)));
46372
46373
  if (!r || r.tool !== "himi-test" || typeof r.runId !== "string") continue;
46373
- const report2 = obj(r.report);
46374
+ const report3 = obj(r.report);
46374
46375
  const coverage = obj(r.coverage);
46375
46376
  runs.push({
46376
46377
  runId: r.runId,
46377
- ok: report2?.ok === true,
46378
- errors: num(obj(report2?.counts)?.errors),
46378
+ ok: report3?.ok === true,
46379
+ errors: num(obj(report3?.counts)?.errors),
46379
46380
  screens: num(coverage?.screens),
46380
46381
  actions: num(coverage?.actionsDispatched),
46381
46382
  gaps: num(coverage?.gaps)
@@ -46494,7 +46495,7 @@ function focusScreen(memory) {
46494
46495
  }
46495
46496
  function buildView(dir, memory, now) {
46496
46497
  const { entries } = readProgress(dir);
46497
- const summary = summarizeProgress(entries);
46498
+ const summary2 = summarizeProgress(entries);
46498
46499
  const { name, iconFile } = appIdentity(dir);
46499
46500
  const iconArchived = iconFile ? archiveFile(dir, memory, iconFile) : null;
46500
46501
  const icon = iconArchived ? historyUrl(iconArchived.file) : null;
@@ -46523,12 +46524,12 @@ function buildView(dir, memory, now) {
46523
46524
  }
46524
46525
  }
46525
46526
  const firstShot = screens.map((s) => s.firstSeen).sort()[0];
46526
- const startedAt = [summary.startedAt, firstShot, memory.createdAt].filter((x) => !!x).sort()[0] ?? null;
46527
+ const startedAt = [summary2.startedAt, firstShot, memory.createdAt].filter((x) => !!x).sort()[0] ?? null;
46527
46528
  const view = {
46528
46529
  generatedAt: now,
46529
46530
  app: { name, icon, palette: palette(dir) },
46530
46531
  steps: JOURNEY_STEPS,
46531
- current: summary.current ?? null,
46532
+ current: summary2.current ?? null,
46532
46533
  startedAt,
46533
46534
  journal: entries.slice(-MAX_JOURNAL).reverse().map((e) => ({
46534
46535
  ts: e.ts,
@@ -47193,16 +47194,16 @@ function pushSetupBodyBytes(body) {
47193
47194
  return Buffer.byteLength(JSON.stringify(body), "utf8");
47194
47195
  }
47195
47196
  async function pushSetupRequest(options) {
47196
- const refuse4 = (error) => ({ ok: false, status: 0, body: null, error });
47197
- if (!options.target.controlToken) return refuse4("control_token_required");
47198
- if (!options.wixToken) return refuse4("wix_account_token_required");
47199
- if (credentialHostRefusal(options.target.control)) return refuse4("insecure_target");
47200
- if (options.previewTag !== void 0 && !PREVIEW_TAG.test(options.previewTag)) return refuse4("invalid_preview_tag");
47197
+ const refuse5 = (error) => ({ ok: false, status: 0, body: null, error });
47198
+ if (!options.target.controlToken) return refuse5("control_token_required");
47199
+ if (!options.wixToken) return refuse5("wix_account_token_required");
47200
+ if (credentialHostRefusal(options.target.control)) return refuse5("insecure_target");
47201
+ if (options.previewTag !== void 0 && !PREVIEW_TAG.test(options.previewTag)) return refuse5("invalid_preview_tag");
47201
47202
  const setup = options.operation === "setup";
47202
- if (setup && !options.body) return refuse4("push_setup_body_required");
47203
- if (!setup && (!options.query?.app || !options.query?.site)) return refuse4("push_setup_query_required");
47203
+ if (setup && !options.body) return refuse5("push_setup_body_required");
47204
+ if (!setup && (!options.query?.app || !options.query?.site)) return refuse5("push_setup_query_required");
47204
47205
  if (setup && pushSetupBodyBytes(options.body) > MAX_PUSH_SETUP_BODY_BYTES) {
47205
- return refuse4("push_setup_body_too_large");
47206
+ return refuse5("push_setup_body_too_large");
47206
47207
  }
47207
47208
  const base = options.target.control.replace(/\/+$/, "");
47208
47209
  let path2 = "/v1/admin/push/setup";
@@ -47227,10 +47228,10 @@ async function pushSetupRequest(options) {
47227
47228
  signal: AbortSignal.timeout(options.timeoutMs ?? (setup ? 12e4 : 3e4)),
47228
47229
  ...setup ? { body: JSON.stringify(options.body) } : {}
47229
47230
  });
47230
- const text6 = await response.text();
47231
+ const text8 = await response.text();
47231
47232
  let parsed = null;
47232
47233
  try {
47233
- const value = text6 ? JSON.parse(text6) : null;
47234
+ const value = text8 ? JSON.parse(text8) : null;
47234
47235
  parsed = value && typeof value === "object" && !Array.isArray(value) ? value : null;
47235
47236
  } catch {
47236
47237
  parsed = null;
@@ -47270,6 +47271,41 @@ var init_push_setup_apple_credentials = __esm({
47270
47271
  }
47271
47272
  });
47272
47273
 
47274
+ // src/hosted-owner.ts
47275
+ async function hostedOwnerSession(flags, deps = {}) {
47276
+ let target;
47277
+ try {
47278
+ target = resolveTarget({ target: text3(flags.target), defaultTarget: "wix", controlUrl: text3(flags["control-url"]), controlToken: text3(flags["control-token"]), ownerIssueUrl: text3(flags["owner-issue-url"]) });
47279
+ } catch {
47280
+ return refused("unknown_target", "Select a configured serve target.");
47281
+ }
47282
+ if (credentialHostRefusal(target.control)) return refused("insecure_target");
47283
+ const previewTag = text3(flags["preview-tag"]) ?? process.env.HIMI_SERVE_PREVIEW_TAG;
47284
+ if (previewTag !== void 0 && !/^dp-[a-z0-9]+$/.test(previewTag)) return refused("invalid_preview_tag");
47285
+ const controlToken = target.controlToken;
47286
+ if (!controlToken || !decodeControlTokenClaims(controlToken)?.sub) return refused("owner_login_required", "Run himi login --browser --target wix.");
47287
+ let wixToken;
47288
+ try {
47289
+ wixToken = await (deps.wixToken ?? accountToken)();
47290
+ } catch {
47291
+ return refused("wix_account_token_required", "Browser login supplies a Himi control token only. Sign into the Wix CLI with wix login, using the same Wix account, then retry. You can continue building without push setup.", 1);
47292
+ }
47293
+ if (!wixToken) return refused("wix_account_token_required", "Run wix login using the same Wix account.", 1);
47294
+ if (!target.ownerIssue) return refused("owner_issuer_required");
47295
+ return { ok: true, session: { target, controlToken, wixToken, ownerIssue: target.ownerIssue, ...previewTag ? { previewTag } : {} } };
47296
+ }
47297
+ var text3, refused;
47298
+ var init_hosted_owner = __esm({
47299
+ "src/hosted-owner.ts"() {
47300
+ init_config();
47301
+ init_credential_hosts();
47302
+ init_owner_claim();
47303
+ init_wix_owner();
47304
+ text3 = (v) => typeof v === "string" && v.length > 0 ? v : void 0;
47305
+ refused = (error, hint, exit = 2) => ({ ok: false, error, ...hint ? { hint } : {}, exit });
47306
+ }
47307
+ });
47308
+
47273
47309
  // src/push-onboarding.ts
47274
47310
  import { existsSync as existsSync44, mkdirSync as mkdirSync20, readFileSync as readFileSync48, statSync as statSync9, writeFileSync as writeFileSync15, chmodSync as chmodSync2 } from "node:fs";
47275
47311
  import { basename as basename12, dirname as dirname25, join as join42, resolve as resolve37 } from "node:path";
@@ -47293,26 +47329,26 @@ function pushInputsFromContent(dir) {
47293
47329
  // autoClaimSlug's docblock describes as the project-declared slug. Without the fallback a
47294
47330
  // package with no `name` sent a body missing `app` and earned `400 invalid_app_id` with both
47295
47331
  // private keys already on the wire.
47296
- app: text3(content.name) ?? basename12(root),
47297
- ...text3(provisioning.siteId) ? { site: text3(provisioning.siteId) } : {},
47298
- ...text3(native.iosBundleId) ? { bundleId: text3(native.iosBundleId) } : {},
47299
- ...text3(native.androidApplicationId) ? { androidApplicationId: text3(native.androidApplicationId) } : {}
47332
+ app: text4(content.name) ?? basename12(root),
47333
+ ...text4(provisioning.siteId) ? { site: text4(provisioning.siteId) } : {},
47334
+ ...text4(native.iosBundleId) ? { bundleId: text4(native.iosBundleId) } : {},
47335
+ ...text4(native.androidApplicationId) ? { androidApplicationId: text4(native.androidApplicationId) } : {}
47300
47336
  };
47301
47337
  }
47302
47338
  function mergePushInputs(content, flags) {
47303
47339
  const merged = { ...content };
47304
47340
  for (const [flag, apply] of FLAG_SETTERS) {
47305
- const value = text3(flags[flag]);
47341
+ const value = text4(flags[flag]);
47306
47342
  if (value !== void 0) apply(merged, value);
47307
47343
  }
47308
47344
  return merged;
47309
47345
  }
47310
47346
  function missingPushInputs(i, scope) {
47311
47347
  const needed = scope === "push_and_signing" ? [...REQUIRED_ORDER, ...ASC_ORDER] : REQUIRED_ORDER;
47312
- return needed.filter((name) => !text3(i[name]));
47348
+ return needed.filter((name) => !text4(i[name]));
47313
47349
  }
47314
47350
  function ascGroupError(i) {
47315
- const present = ASC_ORDER.filter((name) => text3(i[name])).length;
47351
+ const present = ASC_ORDER.filter((name) => text4(i[name])).length;
47316
47352
  return present === 0 || present === ASC_ORDER.length ? null : "incomplete_asc_group";
47317
47353
  }
47318
47354
  function localIdentityRefusal(i) {
@@ -47381,8 +47417,8 @@ function validateApnsAuthKey(body) {
47381
47417
  return { ok: true };
47382
47418
  }
47383
47419
  function buildPushSetupBody(i, keys) {
47384
- const missing = REQUIRED_ORDER.filter((name) => !text3(i[name]));
47385
- const app = text3(i.app);
47420
+ const missing = REQUIRED_ORDER.filter((name) => !text4(i[name]));
47421
+ const app = text4(i.app);
47386
47422
  if (!app) return { ok: false, code: "app_not_initialized", missing };
47387
47423
  if (missing.length > 0) return { ok: false, code: "needs_input", missing };
47388
47424
  if (i.apnsEnvironment !== "development" && i.apnsEnvironment !== "production") {
@@ -47462,7 +47498,7 @@ function savePushSelection(dir, input) {
47462
47498
  if (input.androidApplicationId) selection.androidApplicationId = input.androidApplicationId;
47463
47499
  writeFile(dir, { ...readPushOnboarding(dir), selection });
47464
47500
  }
47465
- var PUSH_ONBOARDING_PATH, MAX_KEY_FILE_BYTES, SITE_RE, APP_RE, ANDROID_ID_RE, PLACEHOLDER_BUNDLE_RE, text3, FLAG_SETTERS, REQUIRED_ORDER, ASC_ORDER, NOTE;
47501
+ var PUSH_ONBOARDING_PATH, MAX_KEY_FILE_BYTES, SITE_RE, APP_RE, ANDROID_ID_RE, PLACEHOLDER_BUNDLE_RE, text4, FLAG_SETTERS, REQUIRED_ORDER, ASC_ORDER, NOTE;
47466
47502
  var init_push_onboarding = __esm({
47467
47503
  "src/push-onboarding.ts"() {
47468
47504
  PUSH_ONBOARDING_PATH = ".himi/push-onboarding.json";
@@ -47471,7 +47507,7 @@ var init_push_onboarding = __esm({
47471
47507
  APP_RE = /^[a-z0-9][a-z0-9._-]{0,63}$/i;
47472
47508
  ANDROID_ID_RE = /^[A-Za-z][A-Za-z0-9_]*(\.[A-Za-z][A-Za-z0-9_]*)+$/;
47473
47509
  PLACEHOLDER_BUNDLE_RE = /^com\.example\.|^__HIMI_APP_/;
47474
- text3 = (v) => typeof v === "string" && v.length > 0 ? v : void 0;
47510
+ text4 = (v) => typeof v === "string" && v.length > 0 ? v : void 0;
47475
47511
  FLAG_SETTERS = [
47476
47512
  ["app", (m, v) => {
47477
47513
  m.app = v;
@@ -47603,7 +47639,7 @@ async function runNotifySetup(pos, flags, io, deps = {}) {
47603
47639
  if (operation !== "setup" && operation !== "status") return refuse2(io, "invalid_notify_operation");
47604
47640
  const flagError = pushSetupFlagError(flags);
47605
47641
  if (flagError) return refuse2(io, flagError);
47606
- const dir = resolve38(text4(flags.content) ?? process.cwd());
47642
+ const dir = resolve38(text5(flags.content) ?? process.cwd());
47607
47643
  const local = readPushOnboarding(dir);
47608
47644
  const progress = local.push;
47609
47645
  const content = pushInputsFromContent(dir);
@@ -47635,26 +47671,10 @@ async function runNotifySetup(pos, flags, io, deps = {}) {
47635
47671
  return 2;
47636
47672
  }
47637
47673
  if (operation === "setup" && ascGroupError(input)) return refuse2(io, "incomplete_asc_group");
47638
- let target;
47639
- try {
47640
- target = resolveTarget({ target: text4(flags.target), defaultTarget: "wix", controlUrl: text4(flags["control-url"]), controlToken: text4(flags["control-token"]), ownerIssueUrl: text4(flags["owner-issue-url"]) });
47641
- } catch {
47642
- return refuse2(io, "unknown_target", "Select a configured serve target.");
47643
- }
47644
- if (credentialHostRefusal(target.control)) return refuse2(io, "insecure_target");
47645
- const previewTag = text4(flags["preview-tag"]) ?? process.env.HIMI_SERVE_PREVIEW_TAG;
47646
- if (previewTag !== void 0 && !/^dp-[a-z0-9]+$/.test(previewTag)) return refuse2(io, "invalid_preview_tag");
47647
- const token = target.controlToken;
47648
- if (!token || !decodeControlTokenClaims(token)?.sub) return refuse2(io, "owner_login_required", "Run himi login --browser --target wix.");
47649
- let wixToken;
47650
- try {
47651
- wixToken = await (deps.wixToken ?? accountToken)();
47652
- } catch {
47653
- return refuse2(io, "wix_account_token_required", "Browser login supplies a Himi control token only. Sign into the Wix CLI with wix login, using the same Wix account, then retry. You can continue building without push setup.", 1);
47654
- }
47655
- if (!wixToken) return refuse2(io, "wix_account_token_required", "Run wix login using the same Wix account.", 1);
47656
- if (!target.ownerIssue) return refuse2(io, "owner_issuer_required");
47657
- const ownershipArgs = { ownerIssue: target.ownerIssue, controlToken: token, declaredName: input.app, fetchImpl: deps.fetchImpl };
47674
+ const owner = await hostedOwnerSession(flags, deps);
47675
+ if (!owner.ok) return refuse2(io, owner.error, owner.hint, owner.exit);
47676
+ const { target, controlToken: token, wixToken, previewTag, ownerIssue } = owner.session;
47677
+ const ownershipArgs = { ownerIssue, controlToken: token, declaredName: input.app, fetchImpl: deps.fetchImpl };
47658
47678
  let ownership = await verifyAppOwnership(ownershipArgs);
47659
47679
  logOwnershipFailure(io, ownership);
47660
47680
  if (ownership.state !== "owned" && ownership.state !== "not_claimed") return refuse2(io, `app_ownership_${ownership.state}`, "Setup requires the app owner; retry after ownership is verified.", 1);
@@ -47700,7 +47720,7 @@ async function runNotifySetup(pos, flags, io, deps = {}) {
47700
47720
  }
47701
47721
  }
47702
47722
  if (ownership.state === "not_claimed") {
47703
- const claim = await autoClaimSlug({ ownerIssue: target.ownerIssue, controlToken: token, app: input.app, accountId: text4(flags["account-id"]), fetchImpl: deps.fetchImpl });
47723
+ const claim = await autoClaimSlug({ ownerIssue, controlToken: token, app: input.app, accountId: text5(flags["account-id"]), fetchImpl: deps.fetchImpl });
47704
47724
  if (claim.fatal) return refuse2(io, "app_claim_refused", "Check app ownership and the selected account.", 1);
47705
47725
  ownership = await verifyAppOwnership({ ...ownershipArgs, declaredName: claim.slug ?? input.app });
47706
47726
  logOwnershipFailure(io, ownership);
@@ -47750,17 +47770,15 @@ async function offerPushSetup(options) {
47750
47770
  return { status: "needs_attention", resume: resume(contentDir2) };
47751
47771
  }
47752
47772
  }
47753
- var text4, obj2, STATES, CHECKS, CHECK_STATES, STAGES, ERROR_CODES, PROMPTS, later, resume;
47773
+ var text5, obj2, STATES, CHECKS, CHECK_STATES, STAGES, ERROR_CODES, PROMPTS, later, resume;
47754
47774
  var init_notify_setup = __esm({
47755
47775
  "src/notify-setup.ts"() {
47756
- init_config();
47757
- init_credential_hosts();
47758
47776
  init_push_setup();
47759
47777
  init_push_setup_apple_credentials();
47760
- init_wix_owner();
47761
47778
  init_owner_claim();
47779
+ init_hosted_owner();
47762
47780
  init_push_onboarding();
47763
- text4 = (v) => typeof v === "string" && v.length > 0 ? v : void 0;
47781
+ text5 = (v) => typeof v === "string" && v.length > 0 ? v : void 0;
47764
47782
  obj2 = (v) => v && typeof v === "object" && !Array.isArray(v) ? v : {};
47765
47783
  STATES = /* @__PURE__ */ new Set(["not_configured", "provisioning", "ready", "needs_attention"]);
47766
47784
  CHECKS = ["brandedRecord", "pingApns", "pingFcm", "appleSigning", "nativeBuild", "himiRouting", "deviceDelivery"];
@@ -48455,7 +48473,7 @@ function thin(items, max) {
48455
48473
  function planReel(dir) {
48456
48474
  const memory = loadMemory2(dir);
48457
48475
  const { entries } = readProgress(dir);
48458
- const summary = summarizeProgress(entries);
48476
+ const summary2 = summarizeProgress(entries);
48459
48477
  const manifest = readJson5(join45(dir, "himalaya.content.json"));
48460
48478
  const tokens = readJson5(join45(dir, "tokens.json"));
48461
48479
  const accentToken = tokens?.colors?.accentPrimary;
@@ -48475,9 +48493,9 @@ function planReel(dir) {
48475
48493
  by: isCliEntry(e) ? CLI_AUTHOR : null,
48476
48494
  files: (Array.isArray(e.images) ? e.images : []).map((i) => isObj3(i) && typeof i.file === "string" ? basename13(i.file) : "").filter((f) => HISTORY_NAME.test(f))
48477
48495
  }));
48478
- const times = [summary.startedAt, ...timeline.map((t) => t.ts), memory?.createdAt].filter((x) => !!x).sort();
48496
+ const times = [summary2.startedAt, ...timeline.map((t) => t.ts), memory?.createdAt].filter((x) => !!x).sort();
48479
48497
  const startedAt = times[0] ?? null;
48480
- const endedAt = [summary.lastAt, ...timeline.map((t) => t.ts)].filter((x) => !!x).sort().pop() ?? null;
48498
+ const endedAt = [summary2.lastAt, ...timeline.map((t) => t.ts)].filter((x) => !!x).sort().pop() ?? null;
48481
48499
  return {
48482
48500
  app: typeof manifest?.name === "string" && manifest.name ? manifest.name : basename13(dir),
48483
48501
  accent,
@@ -48660,13 +48678,21 @@ __export(milestones_exports, {
48660
48678
  CONSENT_PREFLIGHT_STEP: () => CONSENT_PREFLIGHT_STEP,
48661
48679
  FIRST_LOOK_BUDGET_MS: () => FIRST_LOOK_BUDGET_MS,
48662
48680
  INIT_SOURCE_FILE: () => INIT_SOURCE_FILE,
48681
+ INIT_WATCH_IDLE_MINUTES: () => INIT_WATCH_IDLE_MINUTES,
48663
48682
  REMOTE_FIRST_LOOK_SKIP: () => REMOTE_FIRST_LOOK_SKIP,
48683
+ REMOTE_WATCH_SKIP: () => REMOTE_WATCH_SKIP,
48664
48684
  STARTING_POINT_REF: () => STARTING_POINT_REF,
48685
+ STILL_BUILDING_AFTER_MS: () => STILL_BUILDING_AFTER_MS,
48686
+ STILL_BUILDING_MESSAGE: () => STILL_BUILDING_MESSAGE,
48687
+ WATCH_LOG: () => WATCH_LOG,
48665
48688
  WATCH_REMINDER_EVERY_MS: () => WATCH_REMINDER_EVERY_MS,
48689
+ absoluteLoaderArgs: () => absoluteLoaderArgs,
48666
48690
  afterBrowserTestRun: () => afterBrowserTestRun,
48667
48691
  afterDraftPush: () => afterDraftPush,
48692
+ afterHandoverPostcard: () => afterHandoverPostcard,
48668
48693
  afterRenderPng: () => afterRenderPng,
48669
48694
  afterTestRun: () => afterTestRun,
48695
+ defaultStartWatchDeps: () => defaultStartWatchDeps,
48670
48696
  displayPath: () => displayPath,
48671
48697
  doNowBlock: () => doNowBlock,
48672
48698
  draftPictureGap: () => draftPictureGap,
@@ -48675,21 +48701,25 @@ __export(milestones_exports, {
48675
48701
  homeShotForPostcard: () => homeShotForPostcard,
48676
48702
  homeShotPath: () => homeShotPath,
48677
48703
  initDivergence: () => initDivergence,
48704
+ liveWatch: () => liveWatch,
48678
48705
  liveWatchRunning: () => liveWatchRunning,
48679
48706
  openBoardForSession: () => openBoardForSession,
48680
48707
  progressCommand: () => progressCommand,
48681
48708
  recordInitSource: () => recordInitSource,
48682
48709
  sessionSteps: () => sessionSteps,
48710
+ startWatchAtInit: () => startWatchAtInit,
48683
48711
  startingPointMessage: () => startingPointMessage,
48684
48712
  testChangeNote: () => testChangeNote,
48685
48713
  testPostcardCounts: () => testPostcardCounts,
48686
48714
  testPostcardMessage: () => testPostcardMessage,
48687
48715
  watchReminder: () => watchReminder,
48716
+ watchUrlFor: () => watchUrlFor,
48688
48717
  writeHandoverReel: () => writeHandoverReel
48689
48718
  });
48690
48719
  import { createHash as createHash26, randomUUID as randomUUID3 } from "node:crypto";
48691
48720
  import { spawn as spawn5 } from "node:child_process";
48692
- import { existsSync as existsSync47, lstatSync as lstatSync11, readFileSync as readFileSync52, statSync as statSync11 } from "node:fs";
48721
+ import { closeSync as closeSync3, existsSync as existsSync47, lstatSync as lstatSync11, readFileSync as readFileSync52, statSync as statSync11 } from "node:fs";
48722
+ import { createServer as createServer4 } from "node:net";
48693
48723
  import { basename as basename14, dirname as dirname27, isAbsolute as isAbsolute4, join as join46, relative as relative9, resolve as resolve41, sep as sep12 } from "node:path";
48694
48724
  function homeScreenIdOf(dir) {
48695
48725
  let src;
@@ -48765,9 +48795,9 @@ async function firstLookAtInit(dir, opts) {
48765
48795
  child.on("error", (e) => finish2({ skipped: `the render did not start: ${e.message}` }));
48766
48796
  child.on("close", (code) => {
48767
48797
  clearTimeout(hardKill);
48768
- let report2 = null;
48798
+ let report3 = null;
48769
48799
  try {
48770
- report2 = JSON.parse(Buffer.concat(out2).toString("utf8"));
48800
+ report3 = JSON.parse(Buffer.concat(out2).toString("utf8"));
48771
48801
  } catch {
48772
48802
  }
48773
48803
  let fresh = false;
@@ -48776,7 +48806,7 @@ async function firstLookAtInit(dir, opts) {
48776
48806
  } catch {
48777
48807
  }
48778
48808
  if (code === 0 && fresh) return finish2({ image: `.himi/shots/${home}.png` });
48779
- if (report2?.pngUnavailable) return finish2({ skipped: "no headless browser to draw the PNG \u2014 `npx playwright install chromium`, then run the render step below" });
48809
+ if (report3?.pngUnavailable) return finish2({ skipped: "no headless browser to draw the PNG \u2014 `npx playwright install chromium`, then run the render step below" });
48780
48810
  let refusal = null;
48781
48811
  for (const line of errTail.trim().split("\n").reverse()) {
48782
48812
  try {
@@ -48786,13 +48816,14 @@ async function firstLookAtInit(dir, opts) {
48786
48816
  }
48787
48817
  if (refusal) break;
48788
48818
  }
48789
- const why = typeof report2?.reason === "string" ? report2.reason : typeof report2?.error === "string" ? report2.error : refusal;
48819
+ const why = typeof report3?.reason === "string" ? report3.reason : typeof report3?.error === "string" ? report3.error : refusal;
48790
48820
  finish2({ skipped: `the render made no picture (exit ${code ?? "killed"})${why ? `: ${why.slice(0, 200)}` : ""}` });
48791
48821
  });
48792
48822
  });
48793
48823
  }
48794
48824
  function sessionSteps(opts) {
48795
48825
  const content = displayPath(opts.dir, opts.cwd);
48826
+ const running = watchRunning(opts.watch);
48796
48827
  const home = opts.home ?? "<homeScreenId>";
48797
48828
  const shot = `.himi/shots/${home}.png`;
48798
48829
  const when = opts.source === "remote" ? "the first picture, once you have read the template's dev/, config.ts and tier5-src/ (it came off the serve plane, and a render runs its dev/index.ts on this machine)" : "the first picture, now";
@@ -48802,7 +48833,7 @@ function sessionSteps(opts) {
48802
48833
  `${progressCommand({ what: "where the build starts", step: "implement", image: shot, content })} \u2014 your postcard for the owner, with that picture`
48803
48834
  ],
48804
48835
  CONSENT_PREFLIGHT_STEP,
48805
- `himi run --watch${contentArg(content)} & \u2014 after the consent preflight: keep it running in the background. The live preview repaints on every save and serves the build board at <its url>/__himi/studio/`
48836
+ running ? `the live preview is already running at ${running.url} \u2014 ${running.by === "init" ? `init started it in the background (pid ${running.pid}, log ${running.log}; it stops itself after ${INIT_WATCH_IDLE_MINUTES} minutes with no save)` : `pid ${running.pid}`}; do not start another. It repaints on every save and serves the build board at ${running.board}. Open it in a browser only after the consent preflight` : `himi run --watch${contentArg(content)} & \u2014 ${opts.source === "remote" ? "once you have read the template's code (init never starts it for a --remote template: the preview runs that code on this machine), and " : ""}after the consent preflight: keep it running in the background. The live preview repaints on every save and serves the build board at <its url>/__himi/studio/`
48806
48837
  ];
48807
48838
  }
48808
48839
  async function openBoardForSession(dir, cwd = process.cwd()) {
@@ -48819,13 +48850,32 @@ function journal(dir, milestone, message, extra) {
48819
48850
  const r = journalMilestone(dir, { milestone, message, ...extra });
48820
48851
  return r.journaled ? { journaled: true, milestone, message: r.entry.message } : void 0;
48821
48852
  }
48853
+ function stillBuilding(dir, image, now) {
48854
+ try {
48855
+ const entries = readProgress(dir, { all: true }).entries;
48856
+ if (!entries.length) return void 0;
48857
+ const times = entries.map((e) => Date.parse(e.ts)).filter(Number.isFinite);
48858
+ if (!times.length || !(now.getTime() - Math.max(...times) > STILL_BUILDING_AFTER_MS)) return void 0;
48859
+ const sha = sha256Of(image);
48860
+ const latest = entries[entries.length - 1];
48861
+ if (Array.isArray(latest.images) && latest.images.some((i) => !!i && typeof i === "object" && i.sha256 === sha)) return void 0;
48862
+ return journal(dir, "still-building", STILL_BUILDING_MESSAGE, { ref: `home-${sha.slice(0, 16)}`, image, now });
48863
+ } catch {
48864
+ return void 0;
48865
+ }
48866
+ }
48867
+ function firstLookBeforeOwnPostcard(dir, image, now) {
48868
+ if (!image || !existsSync47(image) || initDivergence(dir).state !== "diverged") return void 0;
48869
+ return journal(dir, "first-look", yourFirstLookMessage(dir), { image, ...now ? { now } : {} });
48870
+ }
48822
48871
  function afterRenderPng(dir, input) {
48823
48872
  const isHome = input.renderable && input.homeScreenId !== null && input.screenId === input.homeScreenId;
48824
48873
  const at2 = input.now ? { now: input.now } : {};
48825
48874
  let postcard;
48826
48875
  if (isHome) {
48827
48876
  const d = initDivergence(dir);
48828
- postcard = d.state === "unchanged" ? journal(dir, "first-look", startingPointMessage(d.template), { ref: STARTING_POINT_REF, image: input.pngFile, ...at2 }) : d.state === "diverged" ? journal(dir, "first-look", `First look at your app: ${appName(dir)}'s home screen, with your changes.`, { image: input.pngFile, ...at2 }) : journal(dir, "first-look", `First look at ${appName(dir)}: the home screen, where the build starts.`, { image: input.pngFile, ...at2 });
48877
+ postcard = d.state === "unchanged" ? journal(dir, "first-look", startingPointMessage(d.template), { ref: STARTING_POINT_REF, image: input.pngFile, ...at2 }) : d.state === "diverged" ? journal(dir, "first-look", yourFirstLookMessage(dir), { image: input.pngFile, ...at2 }) : journal(dir, "first-look", `First look at ${appName(dir)}: the home screen, where the build starts.`, { image: input.pngFile, ...at2 });
48878
+ postcard ??= stillBuilding(dir, input.pngFile, input.now ?? /* @__PURE__ */ new Date());
48829
48879
  }
48830
48880
  return {
48831
48881
  ...postcard ? { postcard } : {},
@@ -48878,6 +48928,7 @@ function testChangeNote(headline2, change) {
48878
48928
  return picture ? `Since the last crawl: ${picture}.` : void 0;
48879
48929
  }
48880
48930
  function afterTestRun(dir, input) {
48931
+ const firstLook = firstLookBeforeOwnPostcard(dir, input.homeShot, input.now);
48881
48932
  const headline2 = testPostcardMessage(input);
48882
48933
  const postcard = journal(dir, "test", headline2, {
48883
48934
  ref: input.runId,
@@ -48888,6 +48939,7 @@ function afterTestRun(dir, input) {
48888
48939
  });
48889
48940
  return {
48890
48941
  ...postcard ? { postcard } : {},
48942
+ ...firstLook ? { firstLook } : {},
48891
48943
  next: [
48892
48944
  input.browserScreen !== void 0 ? "this run crawled one screen in the browser; `himi test` without --browser crawls every screen and draws the build board's App map \u2014 how they link, and any broken link" : input.boardOpen ? "the build board's App map now shows every screen this crawl reached, how they link, and any broken link \u2014 .himi/studio/index.html, or /__himi/studio/ under `himi run --watch`" : "`himi studio` opens the build board, whose App map draws every screen this crawl reached, how they link, and any broken link"
48893
48945
  ]
@@ -48929,6 +48981,7 @@ function draftPictureGap(input) {
48929
48981
  }
48930
48982
  function afterDraftPush(dir, input) {
48931
48983
  const current = !!input.homeShot && shotIsCurrent(input.homeShot, input.sourceChangedAtMs);
48984
+ const firstLook = current ? firstLookBeforeOwnPostcard(dir, input.homeShot, input.now) : void 0;
48932
48985
  const gap = current || input.home === void 0 ? null : draftPictureGap(input);
48933
48986
  const why = gap ? ` No picture: ${gap.reason}${gap.render ? ` \u2014 \`himi render ${shellQuote(input.home)} --png\` makes a current one` : ""}.` : "";
48934
48987
  const postcard = journal(dir, "draft", `Draft pushed: release ${input.releaseId.slice(0, 12)} of ${input.app} \u2014 owner-private, not live.${why}`, {
@@ -48941,6 +48994,7 @@ function afterDraftPush(dir, input) {
48941
48994
  const postcardLine = `${progressCommand({ what: "what the owner can review in this draft", step: "review", content: input.content })} \u2014 your postcard for the draft; share the preview link in chat, never in the journal. ${handover}`;
48942
48995
  return {
48943
48996
  ...postcard ? { postcard } : {},
48997
+ ...firstLook ? { firstLook } : {},
48944
48998
  ...reel ? { reel } : {},
48945
48999
  ...gap ? { picture: { skipped: gap.reason, ...gap.render ? { render: gap.render } : {} } } : {},
48946
49000
  // With a render to run, it comes first: then the agent's postcard carries the current picture itself.
@@ -48989,7 +49043,7 @@ function initDivergence(dir) {
48989
49043
  function startingPointMessage(template) {
48990
49044
  return template ? `Starting point: the ${template} template, before any of your changes.` : "Starting point: the starter app, before any of your changes.";
48991
49045
  }
48992
- function homeShotForPostcard(dir) {
49046
+ function homeShotForPostcard(dir, opts = {}) {
48993
49047
  const home = homeScreenIdOf(dir);
48994
49048
  if (!home) return { skipped: "no literal homeScreenId in dev/index.ts, so there is no home shot to attach" };
48995
49049
  const shot = homeShotPath(dir, home);
@@ -49003,6 +49057,7 @@ function homeShotForPostcard(dir) {
49003
49057
  changed = void 0;
49004
49058
  }
49005
49059
  if (!shotIsCurrent(shot, changed)) return { skipped: `the home shot is older than the last source change \u2014 ${render} makes a current one` };
49060
+ if (opts.repeatOk) return { image: shot };
49006
49061
  try {
49007
49062
  const sha = sha256Of(shot);
49008
49063
  const entries = readProgress(dir, { all: true }).entries;
@@ -49014,15 +49069,38 @@ function homeShotForPostcard(dir) {
49014
49069
  }
49015
49070
  return { image: shot };
49016
49071
  }
49017
- function liveWatchRunning(dir, isAlive = pidAlive) {
49072
+ function readInitWatch(dir) {
49073
+ const file = join46(dir, INIT_WATCH_FILE);
49074
+ try {
49075
+ if (!lstatSync11(file).isFile()) return null;
49076
+ const r = JSON.parse(readFileSync52(file, "utf8"));
49077
+ if (r.v !== 1 || typeof r.pid !== "number" || !Number.isInteger(r.pid) || r.pid < 1) return null;
49078
+ if (typeof r.url !== "string" || !/^http:\/\/127\.0\.0\.1:\d+\//.test(r.url) || typeof r.board !== "string" || typeof r.log !== "string" || typeof r.startedAt !== "string") return null;
49079
+ return r;
49080
+ } catch {
49081
+ return null;
49082
+ }
49083
+ }
49084
+ function liveWatch(dir, isAlive = pidAlive, now = /* @__PURE__ */ new Date()) {
49018
49085
  const r = readRunRecord(dir);
49019
- return !!r && r.watching === true && isAlive(r.pid);
49086
+ if (r) {
49087
+ if (r.watching === true && isAlive(r.pid)) return { url: r.url, board: `${new URL(r.url).origin}/__himi/studio/`, pid: r.pid, by: "run" };
49088
+ return null;
49089
+ }
49090
+ const w = readInitWatch(dir);
49091
+ const age = w ? now.getTime() - Date.parse(w.startedAt) : Number.NaN;
49092
+ if (w && age >= 0 && age < INIT_WATCH_STARTING_MS && isAlive(w.pid)) return { url: w.url, board: w.board, pid: w.pid, by: "init", log: w.log };
49093
+ return null;
49094
+ }
49095
+ function liveWatchRunning(dir, isAlive = pidAlive, now) {
49096
+ return liveWatch(dir, isAlive, now) !== null;
49020
49097
  }
49021
49098
  function watchReminder(dir, opts = {}) {
49022
49099
  try {
49023
49100
  if (!existsSync47(join46(dir, "himalaya.content.json"))) return void 0;
49024
- if (liveWatchRunning(dir, opts.isAlive)) return void 0;
49025
- const now = (opts.now ?? /* @__PURE__ */ new Date()).getTime();
49101
+ const nowDate = opts.now ?? /* @__PURE__ */ new Date();
49102
+ const live2 = liveWatch(dir, opts.isAlive, nowDate);
49103
+ const now = nowDate.getTime();
49026
49104
  const file = join46(dir, REMINDERS_FILE);
49027
49105
  let last = Number.NaN;
49028
49106
  try {
@@ -49033,21 +49111,96 @@ function watchReminder(dir, opts = {}) {
49033
49111
  if (!realDirIn(dir, ".himi")) return void 0;
49034
49112
  writeFileAtomic(file, `${JSON.stringify({ watch: new Date(now).toISOString() })}
49035
49113
  `);
49114
+ if (live2) {
49115
+ return `the live preview is running at ${live2.url} (pid ${live2.pid}${live2.by === "init" ? ", started by himi init" : ""}) \u2014 it repaints on every save, and the build board is ${live2.board}; give the owner that link to click around while you work, and do not start another`;
49116
+ }
49036
49117
  const content = opts.content ?? displayPath(dir);
49037
49118
  return `start the live preview and leave it running: \`himi run --watch --content ${shellQuote(content)} &\` (the consent preflight first, if a worker or config calls Wix at boot) \u2014 it repaints on every save; the build board is ${shellQuote(join46(content, ".himi", "studio", "index.html"))}, or /__himi/studio/ on the preview`;
49038
49119
  } catch {
49039
49120
  return void 0;
49040
49121
  }
49041
49122
  }
49123
+ function watchUrlFor(port, app) {
49124
+ const origin = `http://127.0.0.1:${port}`;
49125
+ return { url: `${origin}/preview/a/${encodeURIComponent(app)}`, board: `${origin}/__himi/studio/` };
49126
+ }
49127
+ function absoluteLoaderArgs(args, resolveSpec = (spec) => import.meta.resolve(spec)) {
49128
+ const bare = (v) => !/^(?:\.{1,2}[\\/]|\/|[A-Za-z]:[\\/]|file:|data:|node:)/.test(v);
49129
+ const fix = (v) => {
49130
+ if (!bare(v)) return v;
49131
+ try {
49132
+ return resolveSpec(v);
49133
+ } catch {
49134
+ return v;
49135
+ }
49136
+ };
49137
+ const out2 = [];
49138
+ for (let i = 0; i < args.length; i++) {
49139
+ const m = /^(--(?:import|loader|experimental-loader|require))=(.*)$/.exec(args[i]);
49140
+ if (m) out2.push(`${m[1]}=${fix(m[2])}`);
49141
+ else if (/^(--import|--loader|--experimental-loader|--require|-r)$/.test(args[i]) && i + 1 < args.length) out2.push(args[i], fix(args[++i]));
49142
+ else out2.push(args[i]);
49143
+ }
49144
+ return out2;
49145
+ }
49146
+ async function startWatchAtInit(dir, opts) {
49147
+ if (opts.source !== "bundled") return { skipped: REMOTE_WATCH_SKIP };
49148
+ if (opts.noWatch) return { skipped: "--no-watch" };
49149
+ const env = opts.env ?? process.env;
49150
+ if (ciSet(env)) return { skipped: "the CI environment variable is set, so nothing is left running after init" };
49151
+ if (env.NODE_TEST_CONTEXT) return { skipped: "running under `node --test`, so nothing is left running after init" };
49152
+ const deps = { ...defaultStartWatchDeps, ...opts.deps };
49153
+ const now = opts.now ?? /* @__PURE__ */ new Date();
49154
+ const live2 = liveWatch(dir, deps.isAlive, now);
49155
+ if (live2 && (live2.by === "init" || await deps.previewRunning(dir))) {
49156
+ return { skipped: `a live preview of this app is already running (pid ${live2.pid})`, url: live2.url, board: live2.board, pid: live2.pid };
49157
+ }
49158
+ try {
49159
+ const port = await deps.freePort(previewPortForDir(dir));
49160
+ const { url, board } = watchUrlFor(port, opts.app);
49161
+ const { fd } = openPreviewLog(dir, "watch.log");
49162
+ let child;
49163
+ try {
49164
+ child = deps.spawn(
49165
+ process.execPath,
49166
+ [...absoluteLoaderArgs(opts.loader ?? []), opts.entry, "run", "--watch", "--content", dir, "--port", String(port), "--no-open", "--idle-exit", String(INIT_WATCH_IDLE_MINUTES), "--skip-version-check"],
49167
+ { cwd: dir, detached: true, stdio: ["ignore", fd, fd], env: { ...env, HIMI_NO_UPDATE_CHECK: "1" } }
49168
+ );
49169
+ } finally {
49170
+ closeSync3(fd);
49171
+ }
49172
+ child.once("error", () => {
49173
+ });
49174
+ child.unref();
49175
+ if (!child.pid) return { skipped: "the live preview did not start (no process) \u2014 start it yourself: `himi run --watch --content <dir> &`" };
49176
+ const record2 = { v: 1, pid: child.pid, url, board, log: WATCH_LOG, startedAt: now.toISOString() };
49177
+ if (realDirIn(dir, ".himi")) writeFileAtomic(join46(dir, INIT_WATCH_FILE), `${JSON.stringify(record2, null, 2)}
49178
+ `);
49179
+ return { started: true, pid: child.pid, url, board, log: WATCH_LOG };
49180
+ } catch (e) {
49181
+ return { skipped: `the live preview did not start: ${e.message.split("\n")[0]}` };
49182
+ }
49183
+ }
49184
+ function watchRunning(w) {
49185
+ if (!w) return null;
49186
+ if ("started" in w) return { url: w.url, board: w.board, pid: w.pid, log: w.log, by: "init" };
49187
+ return w.url && w.board && w.pid ? { url: w.url, board: w.board, pid: w.pid, by: "run" } : null;
49188
+ }
49042
49189
  function doNowBlock(opts) {
49043
49190
  const content = displayPath(opts.dir, opts.cwd);
49044
49191
  const home = opts.home ?? "<homeScreenId>";
49045
49192
  const board = join46(content, ".himi", "studio", "index.html");
49046
- const watch2 = `\`himi run --watch${contentArg(content)} &\` and leave it running (the board: ${shellQuote(board)}, or /__himi/studio/ on the preview)`;
49193
+ const running = watchRunning(opts.watch);
49194
+ const who = running?.by === "init" ? "init started it" : "already running";
49195
+ const watch2 = running ? `${running.url} (${who}; the board: ${running.board}) \u2014 leave it running, and do not start another` : `\`himi run --watch${contentArg(content)} &\` and leave it running (the board: ${shellQuote(board)}, or /__himi/studio/ on the preview)`;
49047
49196
  const postcard = `after each milestone, \`himi progress "<what changed>" --step <n>${contentArg(content)}\` \u2014 the current home picture attaches itself`;
49048
- return opts.pictured ? [`1. Consent preflight: if a worker or config can call Wix or anything external at boot, authorize or mock it (none: go on).`, `2. ${watch2}.`, `3. ${postcard[0].toUpperCase()}${postcard.slice(1)}.`] : [
49197
+ return opts.pictured ? [
49198
+ `1. Consent preflight: if a worker or config can call Wix or anything external at boot, authorize or mock it (none: go on).`,
49199
+ running ? `2. The live preview is already running at ${watch2}; open it in a browser only after step 1.` : `2. ${watch2}.`,
49200
+ `3. ${postcard[0].toUpperCase()}${postcard.slice(1)}.`
49201
+ ] : [
49049
49202
  `1. \`himi render ${shellQuote(home)} --png --device iphone${contentArg(content)}\` \u2014 the starting picture, offline${opts.source === "remote" ? " (read the template's dev/, config.ts and tier5-src/ first)" : ""}.`,
49050
- `2. Consent preflight (authorize or mock any Wix or external startup request), then ${watch2}.`,
49203
+ running ? `2. Consent preflight (authorize or mock any Wix or external startup request), then open the live preview at ${watch2}.` : `2. Consent preflight (authorize or mock any Wix or external startup request), then ${watch2}.`,
49051
49204
  `3. ${postcard[0].toUpperCase()}${postcard.slice(1)}.`
49052
49205
  ];
49053
49206
  }
@@ -49062,7 +49215,20 @@ function writeHandoverReel(dir) {
49062
49215
  return { skipped: `the making-of was not written: ${e.message.split("\n")[0]} \u2014 \`himi studio reel\` retries it` };
49063
49216
  }
49064
49217
  }
49065
- var contentArg, CONSENT_PREFLIGHT, CONSENT_PREFLIGHT_STEP, REMOTE_FIRST_LOOK_SKIP, FIRST_LOOK_BUDGET_MS, plural, appName, INIT_SOURCE_FILE, STARTING_POINT_REF, TEMPLATE_NAME, sha256Of, WATCH_REMINDER_EVERY_MS, REMINDERS_FILE, pidAlive;
49218
+ function afterHandoverPostcard(dir, opts = {}) {
49219
+ const file = join46(dir, ".himi", "making-of.html");
49220
+ let fresh = false;
49221
+ try {
49222
+ const st = lstatSync11(file);
49223
+ fresh = st.isFile() && !st.isSymbolicLink() && st.mtimeMs >= statSync11(join46(dir, ".himi", "progress.jsonl")).mtimeMs;
49224
+ } catch {
49225
+ fresh = false;
49226
+ }
49227
+ const makingOf = fresh ? { file, hint: `hand ${basename14(file)} to the owner at the handover \u2014 one self-contained page of their app taking shape; send it beside the preview link, never instead of it` } : writeHandoverReel(dir);
49228
+ const next = "file" in makingOf ? [`hand the owner the making-of, ${shellQuote(displayPath(makingOf.file))}, beside the preview link \u2014 in your final message and report, never instead of the link`] : [`\`himi studio reel${contentArg(opts.content)}\` writes the making-of to hand the owner beside the preview link (${makingOf.skipped})`];
49229
+ return { makingOf, next };
49230
+ }
49231
+ var contentArg, CONSENT_PREFLIGHT, CONSENT_PREFLIGHT_STEP, REMOTE_FIRST_LOOK_SKIP, FIRST_LOOK_BUDGET_MS, plural, appName, STILL_BUILDING_AFTER_MS, STILL_BUILDING_MESSAGE, yourFirstLookMessage, INIT_SOURCE_FILE, STARTING_POINT_REF, TEMPLATE_NAME, sha256Of, WATCH_REMINDER_EVERY_MS, REMINDERS_FILE, pidAlive, INIT_WATCH_FILE, INIT_WATCH_STARTING_MS, INIT_WATCH_IDLE_MINUTES, WATCH_LOG, REMOTE_WATCH_SKIP, listenOn, defaultStartWatchDeps, ciSet;
49066
49232
  var init_milestones = __esm({
49067
49233
  "src/milestones.ts"() {
49068
49234
  init_app_source();
@@ -49077,6 +49243,9 @@ var init_milestones = __esm({
49077
49243
  FIRST_LOOK_BUDGET_MS = 9e4;
49078
49244
  plural = (n, one, many = `${one}s`) => `${n} ${n === 1 ? one : many}`;
49079
49245
  appName = (dir) => appLabel(dir) ?? basename14(dir);
49246
+ STILL_BUILDING_AFTER_MS = 10 * 60 * 1e3;
49247
+ STILL_BUILDING_MESSAGE = "Still building: here's the home screen now.";
49248
+ yourFirstLookMessage = (dir) => `First look at your app: ${appName(dir)}'s home screen, with your changes.`;
49080
49249
  INIT_SOURCE_FILE = join46(".himi", "init-source.json");
49081
49250
  STARTING_POINT_REF = "starting-point";
49082
49251
  TEMPLATE_NAME = /^[A-Za-z0-9._-]{1,64}$/;
@@ -49091,6 +49260,30 @@ var init_milestones = __esm({
49091
49260
  return e.code === "EPERM";
49092
49261
  }
49093
49262
  };
49263
+ INIT_WATCH_FILE = join46(".himi", "watch.json");
49264
+ INIT_WATCH_STARTING_MS = 5 * 60 * 1e3;
49265
+ INIT_WATCH_IDLE_MINUTES = 30;
49266
+ WATCH_LOG = ".himi/watch.log";
49267
+ REMOTE_WATCH_SKIP = "the template came off the serve plane: the live preview builds and serves its publisher's code on this machine, so init never starts it for one \u2014 read its dev/, config.ts and tier5-src/ first, then start it yourself";
49268
+ listenOn = (port) => new Promise((done) => {
49269
+ const srv = createServer4();
49270
+ srv.once("error", () => done(null));
49271
+ srv.listen(port, "127.0.0.1", () => {
49272
+ const addr = srv.address();
49273
+ const got = typeof addr === "object" && addr ? addr.port : null;
49274
+ srv.close(() => done(got));
49275
+ });
49276
+ });
49277
+ defaultStartWatchDeps = {
49278
+ spawn: (cmd, args, opts) => spawn5(cmd, args, opts),
49279
+ isAlive: (pid) => pidAlive(pid),
49280
+ freePort: async (preferred) => await listenOn(preferred) ?? await listenOn(0) ?? preferred,
49281
+ previewRunning: async (dir) => (await previewStatus(dir)).running
49282
+ };
49283
+ ciSet = (env) => {
49284
+ const v = env.CI?.trim().toLowerCase();
49285
+ return !!v && v !== "0" && v !== "false";
49286
+ };
49094
49287
  }
49095
49288
  });
49096
49289
 
@@ -49578,9 +49771,9 @@ function bundlesOf(block) {
49578
49771
  walk2(block);
49579
49772
  return [...out2];
49580
49773
  }
49581
- function parseJson2(text6, what) {
49774
+ function parseJson2(text8, what) {
49582
49775
  try {
49583
- const value = JSON.parse(text6);
49776
+ const value = JSON.parse(text8);
49584
49777
  if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("not an object");
49585
49778
  return value;
49586
49779
  } catch (e) {
@@ -49815,25 +50008,25 @@ function pruneChrome(items, removed, ledger) {
49815
50008
  }
49816
50009
  function rewriteNav(nav, removed, homeId) {
49817
50010
  const next = structuredClone(nav);
49818
- const report2 = { linkable: [], tabs: [], drawer: [], sidebar: [], deepLinkPatterns: [], topBarActions: [], headerActions: [], surfaceDestinations: [], appIconBadgeSources: [], reparented: [] };
50011
+ const report3 = { linkable: [], tabs: [], drawer: [], sidebar: [], deepLinkPatterns: [], topBarActions: [], headerActions: [], surfaceDestinations: [], appIconBadgeSources: [], reparented: [] };
49819
50012
  const tabItems = asItems(next.tabBar);
49820
50013
  const hadTabs = !!tabItems?.length;
49821
- if (tabItems) next.tabBar.items = pruneChrome(tabItems, removed, report2.tabs);
50014
+ if (tabItems) next.tabBar.items = pruneChrome(tabItems, removed, report3.tabs);
49822
50015
  const drawerItems = asItems(next.drawer);
49823
- if (drawerItems) next.drawer.items = pruneChrome(drawerItems, removed, report2.drawer);
50016
+ if (drawerItems) next.drawer.items = pruneChrome(drawerItems, removed, report3.drawer);
49824
50017
  const sidebarItems = asItems(next.sidebar);
49825
- if (sidebarItems) next.sidebar.items = pruneChrome(sidebarItems, removed, report2.sidebar);
50018
+ if (sidebarItems) next.sidebar.items = pruneChrome(sidebarItems, removed, report3.sidebar);
49826
50019
  if (hadTabs && !asItems(next.tabBar).length) {
49827
- throw new TrimRefusal(`no tab would remain \u2014 every tabBar item opens a screen this trim removes (${report2.tabs.join(", ")}); keep at least one tab's screen`, { removedTabs: report2.tabs });
50020
+ throw new TrimRefusal(`no tab would remain \u2014 every tabBar item opens a screen this trim removes (${report3.tabs.join(", ")}); keep at least one tab's screen`, { removedTabs: report3.tabs });
49828
50021
  }
49829
50022
  const remainingRoots = [...asItems(next.tabBar) ?? [], ...asItems(next.sidebar) ?? []].filter((i) => i.role !== "section");
49830
50023
  const remainingRootIds = new Set(remainingRoots.map((i) => i.id).filter((id) => typeof id === "string"));
49831
- const removedRootIds = /* @__PURE__ */ new Set([...report2.tabs, ...report2.sidebar]);
50024
+ const removedRootIds = /* @__PURE__ */ new Set([...report3.tabs, ...report3.sidebar]);
49832
50025
  if (Array.isArray(next.deepLinkPatterns)) {
49833
50026
  next.deepLinkPatterns = next.deepLinkPatterns.filter((p) => {
49834
50027
  const id = p?.params?.screenId ?? p?.screenId;
49835
50028
  if (typeof id === "string" && removed(id)) {
49836
- report2.deepLinkPatterns.push(String(p.pattern ?? id));
50029
+ report3.deepLinkPatterns.push(String(p.pattern ?? id));
49837
50030
  return false;
49838
50031
  }
49839
50032
  return true;
@@ -49844,7 +50037,7 @@ function rewriteNav(nav, removed, homeId) {
49844
50037
  if (!Array.isArray(actions)) continue;
49845
50038
  next[key2].header.actions = actions.filter((a) => {
49846
50039
  if (!(typeof a?.screenId === "string" && removed(a.screenId))) return true;
49847
- report2.headerActions.push(`${key2}:${String(a.icon ?? a.screenId)}`);
50040
+ report3.headerActions.push(`${key2}:${String(a.icon ?? a.screenId)}`);
49848
50041
  return false;
49849
50042
  });
49850
50043
  }
@@ -49853,7 +50046,7 @@ function rewriteNav(nav, removed, homeId) {
49853
50046
  next.topBarDefault.actions = topActions.filter((a) => {
49854
50047
  const to = openTarget(a?.onTap) ?? openTarget(a?.action);
49855
50048
  if (to && removed(to)) {
49856
- report2.topBarActions.push(entryLabel(a));
50049
+ report3.topBarActions.push(entryLabel(a));
49857
50050
  return false;
49858
50051
  }
49859
50052
  return true;
@@ -49868,7 +50061,7 @@ function rewriteNav(nav, removed, homeId) {
49868
50061
  for (const [dest, screenId] of Object.entries(cfg.screenIds)) {
49869
50062
  if (goneDest(dest) || typeof screenId === "string" && removed(screenId)) {
49870
50063
  delete cfg.screenIds[dest];
49871
- report2.surfaceDestinations.push(`${surface}:${dest}`);
50064
+ report3.surfaceDestinations.push(`${surface}:${dest}`);
49872
50065
  if (!remainingRootIds.has(dest)) unmapped.add(dest);
49873
50066
  }
49874
50067
  }
@@ -49878,24 +50071,24 @@ function rewriteNav(nav, removed, homeId) {
49878
50071
  const gone = goneDest(id) || typeof id === "string" && unmapped.has(id);
49879
50072
  if (!gone) return true;
49880
50073
  const entry = `${surface}:${String(id)}`;
49881
- if (!report2.surfaceDestinations.includes(entry)) report2.surfaceDestinations.push(entry);
50074
+ if (!report3.surfaceDestinations.includes(entry)) report3.surfaceDestinations.push(entry);
49882
50075
  return false;
49883
50076
  });
49884
50077
  }
49885
50078
  }
49886
50079
  }
49887
50080
  if (Array.isArray(next.appIconBadgeSources)) {
49888
- const goneActions = new Set(report2.topBarActions);
50081
+ const goneActions = new Set(report3.topBarActions);
49889
50082
  const idsOf = (items) => new Set((items ?? []).map((i) => i.id).filter((x) => typeof x === "string"));
49890
- const removedTabs = new Set(report2.tabs);
49891
- const removedDrawer = new Set(report2.drawer);
50083
+ const removedTabs = new Set(report3.tabs);
50084
+ const removedDrawer = new Set(report3.drawer);
49892
50085
  const remainingTabIds = idsOf(asItems(next.tabBar));
49893
50086
  const remainingDrawerIds = idsOf(asItems(next.drawer));
49894
50087
  next.appIconBadgeSources = next.appIconBadgeSources.filter((s) => {
49895
50088
  if (typeof s !== "string") return true;
49896
50089
  const [kind, id] = [s.slice(0, s.indexOf(":")), s.slice(s.indexOf(":") + 1)];
49897
50090
  const gone = kind === "topBarAction" ? goneActions.has(id) : kind === "tab" ? removedTabs.has(id) && !remainingTabIds.has(id) : kind === "drawer" ? removedDrawer.has(id) && !remainingDrawerIds.has(id) : false;
49898
- if (gone) report2.appIconBadgeSources.push(s);
50091
+ if (gone) report3.appIconBadgeSources.push(s);
49899
50092
  return !gone;
49900
50093
  });
49901
50094
  }
@@ -49905,7 +50098,7 @@ function rewriteNav(nav, removed, homeId) {
49905
50098
  for (const l of original) if (typeof l.screenId === "string") byId.set(l.screenId, l);
49906
50099
  const kept = original.filter((l) => {
49907
50100
  if (typeof l.screenId === "string" && removed(l.screenId)) {
49908
- report2.linkable.push(l.screenId);
50101
+ report3.linkable.push(l.screenId);
49909
50102
  return false;
49910
50103
  }
49911
50104
  return true;
@@ -49949,7 +50142,7 @@ function rewriteNav(nav, removed, homeId) {
49949
50142
  const entry = { screenId: String(l.screenId) };
49950
50143
  if (toParent !== fromParent) entry.parentScreenId = { from: fromParent, to: toParent };
49951
50144
  if (toTab !== fromTab) entry.tabId = { from: fromTab, to: toTab };
49952
- report2.reparented.push(entry);
50145
+ report3.reparented.push(entry);
49953
50146
  }
49954
50147
  for (const l of kept) {
49955
50148
  if (typeof l.parentScreenId === "string") l.parentScreenId = newParent(l);
@@ -49957,7 +50150,7 @@ function rewriteNav(nav, removed, homeId) {
49957
50150
  }
49958
50151
  next.linkableScreens = kept;
49959
50152
  }
49960
- return { nav: next, report: report2 };
50153
+ return { nav: next, report: report3 };
49961
50154
  }
49962
50155
  function leftoverNavRefs(nav, removedIds) {
49963
50156
  const gone = new Set(removedIds);
@@ -50116,9 +50309,9 @@ function planTrim(src, keepIds, links) {
50116
50309
  for (const r of keptRoutes) for (const b of r.bundles) keptWorkers.add(b);
50117
50310
  const importsOf = (dir) => {
50118
50311
  const out2 = [];
50119
- for (const [rel, text6] of Object.entries(src.workerFiles)) {
50312
+ for (const [rel, text8] of Object.entries(src.workerFiles)) {
50120
50313
  if (!rel.startsWith(`tier5-src/${dir}/`)) continue;
50121
- for (const m of text6.matchAll(/(?:\bfrom\s*|\bimport\s*\(?\s*|\brequire\s*\(\s*)["']\.\.\/([A-Za-z0-9_.-]+)(?:\/|["'])/g)) out2.push(m[1]);
50314
+ for (const m of text8.matchAll(/(?:\bfrom\s*|\bimport\s*\(?\s*|\brequire\s*\(\s*)["']\.\.\/([A-Za-z0-9_.-]+)(?:\/|["'])/g)) out2.push(m[1]);
50122
50315
  }
50123
50316
  return out2;
50124
50317
  };
@@ -50164,9 +50357,9 @@ function planTrim(src, keepIds, links) {
50164
50357
  continue;
50165
50358
  }
50166
50359
  const block = edited.get(r);
50167
- let text6 = block ? serializeAt(block, app.dev, r.open) : void 0;
50168
- if (r === app.home && homeModules?.text !== void 0) text6 = homeModules.text;
50169
- if (text6 !== void 0) cut2(r.open, r.close, text6);
50360
+ let text8 = block ? serializeAt(block, app.dev, r.open) : void 0;
50361
+ if (r === app.home && homeModules?.text !== void 0) text8 = homeModules.text;
50362
+ if (text8 !== void 0) cut2(r.open, r.close, text8);
50170
50363
  }
50171
50364
  if (templatedEmptied) cut2(templatedEmptied[0], templatedEmptied[1], "\n");
50172
50365
  dev += app.dev.slice(cursor);
@@ -50182,7 +50375,7 @@ function planTrim(src, keepIds, links) {
50182
50375
  for (const e of edits.sort((a, b) => b.start - a.start)) config = config.slice(0, e.start) + e.text + config.slice(e.end);
50183
50376
  const keptExact = keptRoutes.filter((r) => r.kind === "screen");
50184
50377
  const keptTemplated = keptRoutes.filter((r) => r.kind === "templated");
50185
- const report2 = {
50378
+ const report3 = {
50186
50379
  ...links ? { links } : {},
50187
50380
  kept: { screens: sorted(keptExact.map((r) => r.label)), templated: sorted(keptTemplated.map((r) => r.label)) },
50188
50381
  implicit: implicit.map((r) => r.label),
@@ -50212,16 +50405,16 @@ function planTrim(src, keepIds, links) {
50212
50405
  },
50213
50406
  bytes: { before: Buffer.byteLength(app.dev), after: Buffer.byteLength(dev) }
50214
50407
  };
50215
- return { dev, config, removedWorkers, removedIds, report: report2 };
50408
+ return { dev, config, removedWorkers, removedIds, report: report3 };
50216
50409
  }
50217
50410
  function homeModulesPass(app, kept, edited) {
50218
50411
  const verticals = Object.entries(EXPLORE_CARDS).filter(([, card2]) => app.exact.has(card2.screenId));
50219
50412
  const removedVertical = verticals.some(([, card2]) => !kept.has(app.exact.get(card2.screenId)));
50220
50413
  if (!removedVertical) return { dropped: [] };
50221
50414
  const block = edited.get(app.home);
50222
- const text6 = block ? serializeAt(block, app.dev, app.home.open) : app.dev.slice(app.home.open, app.home.close);
50415
+ const text8 = block ? serializeAt(block, app.dev, app.home.open) : app.dev.slice(app.home.open, app.home.close);
50223
50416
  const installed = verticals.filter(([, card2]) => kept.has(app.exact.get(card2.screenId))).map(([v]) => v);
50224
- const patched = patchHomeModules(text6, installed);
50417
+ const patched = patchHomeModules(text8, installed);
50225
50418
  if (!patched.droppedModules.length) return { dropped: [] };
50226
50419
  return { text: patched.src, dropped: patched.droppedModules };
50227
50420
  }
@@ -50935,8 +51128,8 @@ function isNoneEntry(entry) {
50935
51128
  const head = entryHead(entry);
50936
51129
  return NONE.test(entry) || head !== "" && NONE.test(head);
50937
51130
  }
50938
- function parseDataAccess(text6, from) {
50939
- const body = text6.replace(/<!--[\s\S]*?(?:-->|$)/g, "");
51131
+ function parseDataAccess(text8, from) {
51132
+ const body = text8.replace(/<!--[\s\S]*?(?:-->|$)/g, "");
50940
51133
  const heading = new RegExp(`(?:^|\\n)##[ \\t]+${DATA_ACCESS_HEADING}\\b[^\\n]*([\\s\\S]*?)(?=\\n##[ \\t]|$)`, "i").exec(body);
50941
51134
  if (!heading) return null;
50942
51135
  const lines = heading[1].split("\n").map((l) => l.trim()).filter((l) => l.startsWith("|"));
@@ -50963,13 +51156,13 @@ function readDataAccess(dir) {
50963
51156
  for (const name of ["SPEC.md", "MOBILE-UX.md"]) {
50964
51157
  const p = join49(dir, name);
50965
51158
  if (!existsSync50(p)) continue;
50966
- let text6;
51159
+ let text8;
50967
51160
  try {
50968
- text6 = readFileSync55(p, "utf8");
51161
+ text8 = readFileSync55(p, "utf8");
50969
51162
  } catch {
50970
51163
  continue;
50971
51164
  }
50972
- const plan = parseDataAccess(text6, name);
51165
+ const plan = parseDataAccess(text8, name);
50973
51166
  if (plan) return plan;
50974
51167
  }
50975
51168
  return null;
@@ -51122,7 +51315,7 @@ function readTestConfig(dir) {
51122
51315
  if (input !== void 0 && (!input || typeof input !== "object" || Array.isArray(input) || Object.values(input).some((v) => typeof v !== "string"))) {
51123
51316
  throw new Error(`${TEST_CONFIG_FILE}: journeys[${i}].input must be an object of string values \u2014 the screen input a nav edge would send, e.g. { "id": "svc-0001" }`);
51124
51317
  }
51125
- if (!Array.isArray(journey.expectText) || !journey.expectText.length || journey.expectText.some((text6) => typeof text6 !== "string" || !text6.trim())) {
51318
+ if (!Array.isArray(journey.expectText) || !journey.expectText.length || journey.expectText.some((text8) => typeof text8 !== "string" || !text8.trim())) {
51126
51319
  throw new Error(`${TEST_CONFIG_FILE}: journeys[${i}] needs non-empty expectText values that prove a visible result`);
51127
51320
  }
51128
51321
  });
@@ -51183,16 +51376,16 @@ function readCannedResponses() {
51183
51376
  }
51184
51377
  function makeRecordingWixCall(send2, token, app) {
51185
51378
  const events = [];
51186
- const refused = [];
51379
+ const refused2 = [];
51187
51380
  let id = 0;
51188
51381
  const call = async (method, path2, body) => {
51189
51382
  const verb = method.toUpperCase();
51190
51383
  if (/[#\x00-\x1F\x7F]/.test(path2)) {
51191
- refused.push(`${verb} ${path2}`);
51384
+ refused2.push(`${verb} ${path2}`);
51192
51385
  throw new Error(`--record refused ${verb} ${path2}: the path carries a fragment or control character \u2014 not a provable read`);
51193
51386
  }
51194
51387
  if (!isProvenRead(verb, path2)) {
51195
- refused.push(`${verb} ${path2}`);
51388
+ refused2.push(`${verb} ${path2}`);
51196
51389
  throw new Error(`--record refused ${verb} ${path2}: recording sends only proven reads (GET, or POST ending in /query, /search, /list*, /get*, /count, /aggregate, /facets, /connections) \u2014 if this IS a read, add its rule to dev/net-mocks.json by hand`);
51197
51390
  }
51198
51391
  const bodyStr = body === void 0 ? void 0 : typeof body === "string" ? body : JSON.stringify(body);
@@ -51219,7 +51412,7 @@ function makeRecordingWixCall(send2, token, app) {
51219
51412
  return r.body;
51220
51413
  }
51221
51414
  };
51222
- return { call, events, refused };
51415
+ return { call, events, refused: refused2 };
51223
51416
  }
51224
51417
  async function runRecordPass(opts) {
51225
51418
  const bootErrors = [];
@@ -51623,9 +51816,9 @@ function formatReport(r) {
51623
51816
  out2.push(`transport: ${r.transport}`);
51624
51817
  if (r.nativeDecode) {
51625
51818
  for (const platform of ["ios", "android"]) {
51626
- const summary = r.nativeDecode[platform];
51627
- if (!summary) continue;
51628
- out2.push("unavailable" in summary ? `native decode ${platform}: unavailable \u2014 ${summary.reason}` : `native decode ${platform}: ${summary.checked} checked, ${summary.failed} failed in ${r.nativeDecode.wallMs}ms`);
51819
+ const summary2 = r.nativeDecode[platform];
51820
+ if (!summary2) continue;
51821
+ out2.push("unavailable" in summary2 ? `native decode ${platform}: unavailable \u2014 ${summary2.reason}` : `native decode ${platform}: ${summary2.checked} checked, ${summary2.failed} failed in ${r.nativeDecode.wallMs}ms`);
51629
51822
  }
51630
51823
  }
51631
51824
  out2.push("");
@@ -51768,14 +51961,14 @@ import { join as join51 } from "node:path";
51768
51961
  function mobileUxIssues(dir, strict) {
51769
51962
  const file = join51(dir, "MOBILE-UX.md");
51770
51963
  if (!existsSync52(file)) return [];
51771
- let text6;
51964
+ let text8;
51772
51965
  try {
51773
- text6 = readFileSync57(file, "utf8");
51966
+ text8 = readFileSync57(file, "utf8");
51774
51967
  } catch {
51775
51968
  return [];
51776
51969
  }
51777
- if (!text6) return [];
51778
- return mobileUxMarkerFindings(text6, recordedRenderWidths(dir)).map((finding) => {
51970
+ if (!text8) return [];
51971
+ return mobileUxMarkerFindings(text8, recordedRenderWidths(dir)).map((finding) => {
51779
51972
  const scaffoldAdvisory = finding.code === "missing-width-evidence";
51780
51973
  return {
51781
51974
  screen: null,
@@ -52070,7 +52263,7 @@ async function pinnedFetch(url, init, addresses, maxResponseBytes) {
52070
52263
  const headers2 = Object.fromEntries(new Headers(init.headers).entries());
52071
52264
  const request = url.protocol === "https:" ? httpsRequest : httpRequest;
52072
52265
  const hostname2 = url.hostname.replace(/^\[|\]$/g, "");
52073
- return await new Promise((resolve52, reject) => {
52266
+ return await new Promise((resolve53, reject) => {
52074
52267
  let settled = false;
52075
52268
  const finish2 = (fn) => {
52076
52269
  if (settled) return;
@@ -52107,7 +52300,7 @@ async function pinnedFetch(url, init, addresses, maxResponseBytes) {
52107
52300
  }
52108
52301
  const responseHeaders = new Headers();
52109
52302
  for (const [key2, value] of Object.entries(response.headers)) if (value !== void 0) responseHeaders.set(key2, Array.isArray(value) ? value.join(", ") : value);
52110
- resolve52(new Response(bytes2, { status: response.statusCode, statusText: response.statusMessage, headers: responseHeaders }));
52303
+ resolve53(new Response(bytes2, { status: response.statusCode, statusText: response.statusMessage, headers: responseHeaders }));
52111
52304
  }));
52112
52305
  });
52113
52306
  req.once("error", (error) => finish2(() => reject(error)));
@@ -52407,7 +52600,7 @@ function createLocalTestSandbox() {
52407
52600
  }
52408
52601
  })();
52409
52602
  `;
52410
- return await new Promise((resolve52, reject) => {
52603
+ return await new Promise((resolve53, reject) => {
52411
52604
  const { port1, port2 } = new MessageChannel();
52412
52605
  const serializableContext = {
52413
52606
  auth: context.auth,
@@ -52483,7 +52676,7 @@ function createLocalTestSandbox() {
52483
52676
  timer = setTimeout(() => finish2(() => reject(new SandboxError("timeout", `execution exceeded ${limits.timeoutMs}ms`))), Math.max(1, limits.timeoutMs));
52484
52677
  return;
52485
52678
  }
52486
- if (message.ok) finish2(() => resolve52({ value: message.value, durationMs: Date.now() - started }));
52679
+ if (message.ok) finish2(() => resolve53({ value: message.value, durationMs: Date.now() - started }));
52487
52680
  else finish2(() => reject(new SandboxError("execution_failed", message.message ?? "handler failed")));
52488
52681
  });
52489
52682
  worker.once("error", (error) => finish2(() => reject(new SandboxError("execution_failed", error.message))));
@@ -53599,12 +53792,12 @@ async function runRemoteFunctionOperation(input) {
53599
53792
  if (input.boCookie) headers2.cookie = input.boCookie;
53600
53793
  if (input.previewToken) headers2["x-himalaya-preview-token"] = input.previewToken;
53601
53794
  const response = await (input.fetchImpl ?? fetch)(url, { method, headers: headers2 });
53602
- const text6 = await response.text();
53795
+ const text8 = await response.text();
53603
53796
  let body;
53604
53797
  try {
53605
- body = JSON.parse(text6);
53798
+ body = JSON.parse(text8);
53606
53799
  } catch {
53607
- body = text6;
53800
+ body = text8;
53608
53801
  }
53609
53802
  return { ok: response.ok, status: response.status, body };
53610
53803
  }
@@ -53692,7 +53885,7 @@ function flagShapedInputs(args) {
53692
53885
  function guarded(h) {
53693
53886
  return async (args, deps) => {
53694
53887
  const bad2 = flagShapedInputs(args);
53695
- if (bad2.length) return text5(`refused: ${bad2.join(", ")} may not start with "-" (it would be read as a himi flag)`, true);
53888
+ if (bad2.length) return text6(`refused: ${bad2.join(", ")} may not start with "-" (it would be read as a himi flag)`, true);
53696
53889
  return h(args, deps);
53697
53890
  };
53698
53891
  }
@@ -53845,7 +54038,7 @@ function rawTools() {
53845
54038
  return json3({ ...await d.preview.start(await appDir2(a, d)), hint: PREVIEW_HINT });
53846
54039
  } catch (e) {
53847
54040
  const log = e.log;
53848
- return text5(`${e.message}${typeof log === "string" && log ? `
54041
+ return text6(`${e.message}${typeof log === "string" && log ? `
53849
54042
 
53850
54043
  ${log}` : ""}`, true);
53851
54044
  }
@@ -53921,14 +54114,14 @@ ${log}` : ""}`, true);
53921
54114
  }
53922
54115
  ];
53923
54116
  }
53924
- var MAX_TEXT2, clip, text5, json3, opt, sw, DIR, LEARN, CHECK, LOCAL_WRITE, PUBLISH, PREVIEW_HINT, LOGIN_HINT;
54117
+ var MAX_TEXT2, clip, text6, json3, opt, sw, DIR, LEARN, CHECK, LOCAL_WRITE, PUBLISH, PREVIEW_HINT, LOGIN_HINT;
53925
54118
  var init_tools = __esm({
53926
54119
  "src/mcp/tools.ts"() {
53927
54120
  MAX_TEXT2 = 8e4;
53928
54121
  clip = (s) => s.length <= MAX_TEXT2 ? s : `${s.slice(0, MAX_TEXT2)}
53929
54122
  \u2026 [truncated ${s.length - MAX_TEXT2} chars \u2014 re-run the command in a terminal for the full output]`;
53930
- text5 = (t, isError = false) => ({ content: [{ type: "text", text: t }], ...isError ? { isError: true } : {} });
53931
- json3 = (o) => text5(JSON.stringify(o, null, 2));
54123
+ text6 = (t, isError = false) => ({ content: [{ type: "text", text: t }], ...isError ? { isError: true } : {} });
54124
+ json3 = (o) => text6(JSON.stringify(o, null, 2));
53932
54125
  opt = (flag, v) => typeof v === "string" && v ? [flag, v] : typeof v === "number" ? [flag, String(v)] : [];
53933
54126
  sw = (flag, v) => v === true ? [flag] : [];
53934
54127
  DIR = z.string().optional().describe("The app (content package) directory. Default: the workspace root the host opened, else the server's working directory.");
@@ -54004,9 +54197,9 @@ async function startMcpServer(opts) {
54004
54197
  },
54005
54198
  readFile: (p) => readFileSync59(p)
54006
54199
  };
54007
- const closed = new Promise((resolve52) => {
54008
- stdin.once("end", resolve52);
54009
- stdin.once("close", resolve52);
54200
+ const closed = new Promise((resolve53) => {
54201
+ stdin.once("end", resolve53);
54202
+ stdin.once("close", resolve53);
54010
54203
  });
54011
54204
  const handle = serveStdio(
54012
54205
  () => {
@@ -54248,7 +54441,7 @@ function fieldAt(path2) {
54248
54441
  if (path2 === null) return void 0;
54249
54442
  return path2.match(/\.([^.[\]]+)$/)?.[1];
54250
54443
  }
54251
- function mergeNativeDecode(report2, run2) {
54444
+ function mergeNativeDecode(report3, run2) {
54252
54445
  const platformLabel = run2.platform === "ios" ? "iOS" : "Android";
54253
54446
  const nativeFailures = run2.verdicts.flatMap((verdict) => verdict.errors.map((error) => ({
54254
54447
  screen: verdict.screenId,
@@ -54261,18 +54454,18 @@ function mergeNativeDecode(report2, run2) {
54261
54454
  ...error.actual ? { actual: error.actual } : {},
54262
54455
  hint: `This descriptor ships as a full-screen \u201CCouldn't load\u201D; fix the typed field and rerun \`himi test --native ${run2.platform}\`.`
54263
54456
  })));
54264
- const failures = [...report2.failures, ...nativeFailures];
54457
+ const failures = [...report3.failures, ...nativeFailures];
54265
54458
  const failedScreens = new Set(failures.map((finding) => finding.screen));
54266
54459
  return {
54267
- ...report2,
54268
- ok: report2.ok && nativeFailures.length === 0,
54460
+ ...report3,
54461
+ ok: report3.ok && nativeFailures.length === 0,
54269
54462
  failures,
54270
- counts: { ...report2.counts, errors: failures.length },
54271
- screens: { ...report2.screens, clean: report2.screens.total - failedScreens.size, failed: failedScreens.size },
54463
+ counts: { ...report3.counts, errors: failures.length },
54464
+ screens: { ...report3.screens, clean: report3.screens.total - failedScreens.size, failed: failedScreens.size },
54272
54465
  nativeDecode: {
54273
- ...report2.nativeDecode,
54466
+ ...report3.nativeDecode,
54274
54467
  [run2.platform]: { checked: run2.verdicts.length, failed: run2.verdicts.filter((verdict) => !verdict.ok).length },
54275
- wallMs: (report2.nativeDecode?.wallMs ?? 0) + run2.wallMs
54468
+ wallMs: (report3.nativeDecode?.wallMs ?? 0) + run2.wallMs
54276
54469
  }
54277
54470
  };
54278
54471
  }
@@ -54298,7 +54491,7 @@ __export(browser_authoring_exports, {
54298
54491
  runBrowserAuthoring: () => runBrowserAuthoring,
54299
54492
  sdkSubpathReader: () => sdkSubpathReader
54300
54493
  });
54301
- import { createServer as createServer4 } from "node:http";
54494
+ import { createServer as createServer5 } from "node:http";
54302
54495
  import { createHash as createHash29, randomBytes as randomBytes4 } from "node:crypto";
54303
54496
  import { existsSync as existsSync55, readdirSync as readdirSync27, readFileSync as readFileSync61, realpathSync as realpathSync7, statSync as statSync13 } from "node:fs";
54304
54497
  import { join as join54, posix as posix2, relative as relative11, sep as sep14 } from "node:path";
@@ -54385,10 +54578,10 @@ function collectSdkModules(files, resolveSubpath) {
54385
54578
  for (const source of Object.values(files)) visit(source);
54386
54579
  return { modules, unsupported };
54387
54580
  }
54388
- function sdkSubpathReader(resolve52) {
54581
+ function sdkSubpathReader(resolve53) {
54389
54582
  return (sub) => {
54390
54583
  try {
54391
- return readFileSync61(resolve52(`@wix/himalaya/${sub}`), "utf8");
54584
+ return readFileSync61(resolve53(`@wix/himalaya/${sub}`), "utf8");
54392
54585
  } catch {
54393
54586
  return null;
54394
54587
  }
@@ -54466,10 +54659,10 @@ async function runBrowserAuthoring(opts) {
54466
54659
  const token = mintToken2(payload);
54467
54660
  const unusable = await probeAuthoringPage(opts.authoringBase, opts.fetchImpl, opts.probeTimeoutMs);
54468
54661
  if (unusable) throw new Error(unusable);
54469
- return await new Promise((resolve52, reject) => {
54662
+ return await new Promise((resolve53, reject) => {
54470
54663
  let settled = false;
54471
54664
  let url = "";
54472
- const server = createServer4();
54665
+ const server = createServer5();
54473
54666
  const finish2 = (fn) => {
54474
54667
  if (settled) return;
54475
54668
  settled = true;
@@ -54517,8 +54710,8 @@ async function runBrowserAuthoring(opts) {
54517
54710
  return;
54518
54711
  }
54519
54712
  res.writeHead(200, { "content-type": "text/plain" }).end("ok");
54520
- const report2 = body.result;
54521
- finish2(() => resolve52({ report: report2, url }));
54713
+ const report3 = body.result;
54714
+ finish2(() => resolve53({ report: report3, url }));
54522
54715
  })();
54523
54716
  });
54524
54717
  server.on("error", (e) => finish2(() => reject(e)));
@@ -55195,10 +55388,10 @@ async function hostedAuthUrl(req, visitorToken, fetchImpl = fetch) {
55195
55388
  }
55196
55389
  })
55197
55390
  });
55198
- const text6 = await res.text();
55391
+ const text8 = await res.text();
55199
55392
  let parsed = {};
55200
55393
  try {
55201
- parsed = text6 ? JSON.parse(text6) : {};
55394
+ parsed = text8 ? JSON.parse(text8) : {};
55202
55395
  } catch {
55203
55396
  }
55204
55397
  const fullUrl = parsed.redirectSession?.fullUrl;
@@ -55218,10 +55411,10 @@ async function exchangeAuthCode(cfg, code, verifier, fetchImpl = fetch) {
55218
55411
  redirectUri: cfg.redirectUri
55219
55412
  })
55220
55413
  });
55221
- const text6 = await res.text();
55414
+ const text8 = await res.text();
55222
55415
  let parsed = {};
55223
55416
  try {
55224
- parsed = text6 ? JSON.parse(text6) : {};
55417
+ parsed = text8 ? JSON.parse(text8) : {};
55225
55418
  } catch {
55226
55419
  }
55227
55420
  if (res.status !== 200 || !parsed.access_token) throw new Error(`token exchange failed (${res.status})`);
@@ -56435,11 +56628,11 @@ async function localizeConfigResponse(res, kind, header) {
56435
56628
  header
56436
56629
  )
56437
56630
  };
56438
- const text6 = JSON.stringify(next);
56631
+ const text8 = JSON.stringify(next);
56439
56632
  const headers2 = new Headers(res.headers);
56440
56633
  headers2.delete("etag");
56441
- headers2.set("content-length", String(new TextEncoder().encode(text6).length));
56442
- return new Response(text6, { status: 200, headers: headers2 });
56634
+ headers2.set("content-length", String(new TextEncoder().encode(text8).length));
56635
+ return new Response(text8, { status: 200, headers: headers2 });
56443
56636
  }
56444
56637
  function makeRunHandler(opts) {
56445
56638
  const mount = opts.chrome === "app" ? "/app" : "/preview";
@@ -56562,7 +56755,7 @@ var init_run_handler = __esm({
56562
56755
  });
56563
56756
 
56564
56757
  // ../../core/serve-runtime/src/node-adapter.ts
56565
- import { createServer as createServer5 } from "node:http";
56758
+ import { createServer as createServer6 } from "node:http";
56566
56759
  async function toRequest(req) {
56567
56760
  const method = (req.method ?? "GET").toUpperCase();
56568
56761
  const host = typeof req.headers.host === "string" && req.headers.host || "serve.local";
@@ -56606,7 +56799,7 @@ async function writeResponse(res, response) {
56606
56799
  res.end(ab.byteLength ? Buffer.from(ab) : void 0);
56607
56800
  }
56608
56801
  function nodeAdapter(handler) {
56609
- return createServer5(async (req, res) => {
56802
+ return createServer6(async (req, res) => {
56610
56803
  try {
56611
56804
  const response = await handler(await toRequest(req));
56612
56805
  await writeResponse(res, response);
@@ -56627,8 +56820,8 @@ var init_node_adapter = __esm({
56627
56820
  function authConfigOf(appConfig) {
56628
56821
  if (!appConfig) return void 0;
56629
56822
  try {
56630
- const text6 = typeof appConfig === "string" ? appConfig : new TextDecoder().decode(appConfig);
56631
- return JSON.parse(text6)?.authConfig ?? void 0;
56823
+ const text8 = typeof appConfig === "string" ? appConfig : new TextDecoder().decode(appConfig);
56824
+ return JSON.parse(text8)?.authConfig ?? void 0;
56632
56825
  } catch {
56633
56826
  return void 0;
56634
56827
  }
@@ -56960,7 +57153,7 @@ __export(run_watch_exports, {
56960
57153
  watchedContentDir: () => watchedContentDir,
56961
57154
  writeRunRecord: () => writeRunRecord
56962
57155
  });
56963
- import { closeSync as closeSync3, constants, existsSync as existsSync59, fstatSync, lstatSync as lstatSync12, statSync as statSync15, mkdirSync as mkdirSync28, openSync as openSync3, readFileSync as readFileSync66, realpathSync as realpathSync8, renameSync as renameSync6, rmSync as rmSync14, writeFileSync as writeFileSync23, watch as fsWatch } from "node:fs";
57156
+ import { closeSync as closeSync4, constants, existsSync as existsSync59, fstatSync, lstatSync as lstatSync12, statSync as statSync15, mkdirSync as mkdirSync28, openSync as openSync3, readFileSync as readFileSync66, realpathSync as realpathSync8, renameSync as renameSync6, rmSync as rmSync14, writeFileSync as writeFileSync23, watch as fsWatch } from "node:fs";
56964
57157
  import { randomUUID as randomUUID4 } from "node:crypto";
56965
57158
  import { extname as extname4, join as join58, resolve as resolve46, sep as sep15 } from "node:path";
56966
57159
  function diffReleases(prev, next) {
@@ -57159,7 +57352,7 @@ function readConfined(root, rel) {
57159
57352
  if (!fstatSync(fd).isFile()) return null;
57160
57353
  return readFileSync66(fd);
57161
57354
  } finally {
57162
- closeSync3(fd);
57355
+ closeSync4(fd);
57163
57356
  }
57164
57357
  } catch {
57165
57358
  return null;
@@ -57209,7 +57402,7 @@ __export(run_browser_exports, {
57209
57402
  releasePayloadFromDir: () => releasePayloadFromDir,
57210
57403
  runInBrowser: () => runInBrowser
57211
57404
  });
57212
- import { createServer as createServer6 } from "node:http";
57405
+ import { createServer as createServer7 } from "node:http";
57213
57406
  import { existsSync as existsSync60, readFileSync as readFileSync67 } from "node:fs";
57214
57407
  import { spawnSync as spawnSync3 } from "node:child_process";
57215
57408
  import { dirname as dirname32, resolve as resolve47 } from "node:path";
@@ -57238,16 +57431,16 @@ async function releasePayloadFromDir(root, app, header) {
57238
57431
  const manifest = await store.getManifest(app);
57239
57432
  if (!manifest) throw new Error(`no release manifest for ${app} under ${root}`);
57240
57433
  const b64 = (b) => b ? Buffer.from(b).toString("base64") : void 0;
57241
- const text6 = (b) => b ? new TextDecoder().decode(b) : void 0;
57434
+ const text8 = (b) => b ? new TextDecoder().decode(b) : void 0;
57242
57435
  const screens = {};
57243
57436
  const docs = {};
57244
57437
  const addDoc = async (hash) => {
57245
57438
  if (!hash || docs[hash] !== void 0) return;
57246
- const d = text6(await store.getDoc?.(app, hash) ?? null);
57439
+ const d = text8(await store.getDoc?.(app, hash) ?? null);
57247
57440
  if (d !== void 0) docs[hash] = d;
57248
57441
  };
57249
57442
  for (const [id, meta] of Object.entries(manifest.screens ?? {})) {
57250
- const s = text6(await store.getScreen(app, id));
57443
+ const s = text8(await store.getScreen(app, id));
57251
57444
  if (s !== void 0) screens[id] = s;
57252
57445
  await addDoc(meta.descriptorHash);
57253
57446
  }
@@ -57268,10 +57461,10 @@ async function releasePayloadFromDir(root, app, header) {
57268
57461
  }
57269
57462
  const widgets = {};
57270
57463
  for (const id of Object.keys(manifest.widgets ?? {})) {
57271
- const w = text6(await store.getWidget?.(app, id) ?? null);
57464
+ const w = text8(await store.getWidget?.(app, id) ?? null);
57272
57465
  if (w !== void 0) widgets[id] = w;
57273
57466
  }
57274
- const appConfig = text6(await store.getAppConfig(app));
57467
+ const appConfig = text8(await store.getAppConfig(app));
57275
57468
  const wixClientId = appConfigWixClientId(appConfig);
57276
57469
  return {
57277
57470
  app,
@@ -57281,7 +57474,7 @@ async function releasePayloadFromDir(root, app, header) {
57281
57474
  docs,
57282
57475
  assets,
57283
57476
  widgets,
57284
- tokens: text6(await store.getTokens(app)),
57477
+ tokens: text8(await store.getTokens(app)),
57285
57478
  appConfig,
57286
57479
  icon: b64(await store.getIcon?.(app) ?? null),
57287
57480
  header,
@@ -57294,7 +57487,7 @@ async function runInBrowser(releaseRoot, app, header, opts = {}) {
57294
57487
  const payload = { ...await releasePayloadFromDir(releaseRoot, app, header), chrome: opts.chrome ?? "preview" };
57295
57488
  const body = JSON.stringify(payload);
57296
57489
  const missed = [];
57297
- const server = createServer6((req, res) => {
57490
+ const server = createServer7((req, res) => {
57298
57491
  const path2 = (req.url ?? "/").split("?")[0];
57299
57492
  if (path2 === RUN_IDENTITY_PATH && opts.identityDir) {
57300
57493
  res.writeHead(200, { "content-type": "application/json", "cache-control": "no-store" });
@@ -57811,10 +58004,10 @@ function scalar3(v) {
57811
58004
  }
57812
58005
  return t;
57813
58006
  }
57814
- function parseStoreConfig(text6) {
58007
+ function parseStoreConfig(text8) {
57815
58008
  const cfg = {};
57816
58009
  let section2 = null;
57817
- for (const raw of text6.split("\n")) {
58010
+ for (const raw of text8.split("\n")) {
57818
58011
  const line = strip2(raw);
57819
58012
  if (!line.trim()) continue;
57820
58013
  const indented = /^\s+/.test(raw);
@@ -57873,10 +58066,10 @@ function profilePath(name, home = homedir5()) {
57873
58066
  }
57874
58067
  return join59(credentialsDir(home), `${name}.json`);
57875
58068
  }
57876
- function parseProfile(text6, label2) {
58069
+ function parseProfile(text8, label2) {
57877
58070
  let doc;
57878
58071
  try {
57879
- doc = JSON.parse(text6);
58072
+ doc = JSON.parse(text8);
57880
58073
  } catch (e) {
57881
58074
  throw new Error(`${label2} is not valid JSON: ${e.message}`);
57882
58075
  }
@@ -58485,11 +58678,11 @@ async function registerBundleId(opts) {
58485
58678
  const j = await res.json();
58486
58679
  return { status: "created", id: j.data?.id };
58487
58680
  }
58488
- const text6 = await res.text();
58489
- if (res.status === 409 || /already (exists|in use)|duplicate|ENTITY_ERROR.*identifier/i.test(text6)) {
58681
+ const text8 = await res.text();
58682
+ if (res.status === 409 || /already (exists|in use)|duplicate|ENTITY_ERROR.*identifier/i.test(text8)) {
58490
58683
  return { status: "exists" };
58491
58684
  }
58492
- throw new Error(`POST /v1/bundleIds \u2192 ${res.status}: ${text6.slice(0, 300)}`);
58685
+ throw new Error(`POST /v1/bundleIds \u2192 ${res.status}: ${text8.slice(0, 300)}`);
58493
58686
  }
58494
58687
  async function main2() {
58495
58688
  const [app, ...rest] = process.argv.slice(2);
@@ -58767,12 +58960,12 @@ async function runRemoteSecretsCommand(operation, flags, io, options) {
58767
58960
  body = JSON.stringify({ value });
58768
58961
  }
58769
58962
  const response = await (options.fetchImpl ?? fetch)(url, { method: operation === "list" ? "GET" : operation === "set" ? "PUT" : "POST", headers: headers2, body });
58770
- const text6 = await response.text();
58963
+ const text8 = await response.text();
58771
58964
  let output;
58772
58965
  try {
58773
- output = JSON.parse(text6);
58966
+ output = JSON.parse(text8);
58774
58967
  } catch {
58775
- output = text6;
58968
+ output = text8;
58776
58969
  }
58777
58970
  if (!response.ok) throw new Error(typeof output === "string" ? output : JSON.stringify(output));
58778
58971
  io.log(JSON.stringify(output));
@@ -58788,6 +58981,217 @@ var init_secrets2 = __esm({
58788
58981
  }
58789
58982
  });
58790
58983
 
58984
+ // ../serve-cli/src/push-send.ts
58985
+ async function pushSendRequest(options) {
58986
+ const refusal = localRefusal(options);
58987
+ if (refusal) return { ok: false, status: 0, body: null, error: refusal };
58988
+ try {
58989
+ const response = await (options.fetchImpl ?? fetch)(`${options.target.control.replace(/\/+$/, "")}/v1/admin/push/send`, {
58990
+ method: "POST",
58991
+ headers: headersFor(options),
58992
+ redirect: "error",
58993
+ signal: AbortSignal.timeout(TIMEOUT_MS),
58994
+ body: JSON.stringify(options.body)
58995
+ });
58996
+ const retryAfterSec = response.status === 429 ? Number(response.headers.get("retry-after")) : NaN;
58997
+ return {
58998
+ ok: response.ok,
58999
+ status: response.status,
59000
+ body: objectFrom(await response.text()),
59001
+ ...Number.isFinite(retryAfterSec) && retryAfterSec > 0 ? { retryAfterSec } : {}
59002
+ };
59003
+ } catch {
59004
+ return { ok: false, status: 0, body: null, error: "push_send_unreachable" };
59005
+ }
59006
+ }
59007
+ function localRefusal(options) {
59008
+ if (!options.target.controlToken) return "control_token_required";
59009
+ if (!options.wixToken) return "wix_account_token_required";
59010
+ if (!options.siteToken) return "site_token_required";
59011
+ if (credentialHostRefusal(options.target.control)) return "insecure_target";
59012
+ if (options.previewTag !== void 0 && !PREVIEW_TAG2.test(options.previewTag)) return "invalid_preview_tag";
59013
+ return void 0;
59014
+ }
59015
+ function headersFor(options) {
59016
+ return {
59017
+ "x-control-token": options.target.controlToken,
59018
+ // RAW, never `Bearer ` — what normalizeWixAuthorization and token-info expect.
59019
+ Authorization: options.wixToken,
59020
+ [SITE_TOKEN_HEADER]: options.siteToken,
59021
+ accept: "application/json",
59022
+ "content-type": "application/json",
59023
+ ...options.previewTag ? { "x-wix-route-serve": options.previewTag } : {}
59024
+ };
59025
+ }
59026
+ function objectFrom(text8) {
59027
+ try {
59028
+ const value = text8 ? JSON.parse(text8) : null;
59029
+ return value && typeof value === "object" && !Array.isArray(value) ? value : null;
59030
+ } catch {
59031
+ return null;
59032
+ }
59033
+ }
59034
+ var SITE_TOKEN_HEADER, PREVIEW_TAG2, TIMEOUT_MS;
59035
+ var init_push_send = __esm({
59036
+ "../serve-cli/src/push-send.ts"() {
59037
+ init_credential_hosts();
59038
+ SITE_TOKEN_HEADER = "x-himi-site-token";
59039
+ PREVIEW_TAG2 = /^dp-[a-z0-9]+$/;
59040
+ TIMEOUT_MS = 6e4;
59041
+ }
59042
+ });
59043
+
59044
+ // ../../core/serve-control/src/push-send-service.ts
59045
+ var PUSH_SEND_ERROR_CODES;
59046
+ var init_push_send_service = __esm({
59047
+ "../../core/serve-control/src/push-send-service.ts"() {
59048
+ PUSH_SEND_ERROR_CODES = [
59049
+ "unauthorized",
59050
+ "token_revoked",
59051
+ "revocation_check_unavailable",
59052
+ "method_not_allowed",
59053
+ "rate_limited",
59054
+ "forbidden_cross_tenant",
59055
+ "forbidden_site",
59056
+ "site_check_unavailable",
59057
+ "push_send_unconfigured",
59058
+ "push_send_unavailable",
59059
+ "invalid_input",
59060
+ "payload_too_large",
59061
+ "unknown_field",
59062
+ "invalid_app_id",
59063
+ "invalid_site_id",
59064
+ "invalid_member_email",
59065
+ "invalid_title",
59066
+ "invalid_body",
59067
+ "invalid_link",
59068
+ "invalid_request_id",
59069
+ "invalid_dry_run",
59070
+ "link_not_routable",
59071
+ "app_config_unavailable",
59072
+ "push_not_ready",
59073
+ "site_token_required",
59074
+ "site_token_invalid",
59075
+ "site_token_unverifiable",
59076
+ "member_not_found",
59077
+ "member_ambiguous",
59078
+ "member_not_active",
59079
+ "member_read_forbidden",
59080
+ "member_lookup_unavailable",
59081
+ "ping_unconfigured",
59082
+ "ping_permission_denied",
59083
+ "ping_rejected",
59084
+ "ping_rate_limited",
59085
+ "send_outcome_unknown"
59086
+ ];
59087
+ }
59088
+ });
59089
+
59090
+ // src/notify-ping.ts
59091
+ var notify_ping_exports = {};
59092
+ __export(notify_ping_exports, {
59093
+ runNotifyPing: () => runNotifyPing
59094
+ });
59095
+ import { randomUUID as randomUUID5 } from "node:crypto";
59096
+ import { resolve as resolve49 } from "node:path";
59097
+ function refuse4(io, error, hint, exit = 2, extra = {}) {
59098
+ io.error(JSON.stringify({ ok: false, error, ...hint ? { hint } : {}, ...extra }));
59099
+ return exit;
59100
+ }
59101
+ async function runNotifyPing(flags, io, deps = {}) {
59102
+ const flagError = flagRefusal(flags);
59103
+ if (flagError) return refuse4(io, flagError);
59104
+ const input = mergePushInputs(pushInputsFromContent(resolve49(text7(flags.content) ?? process.cwd())), flags);
59105
+ const missing = [...input.app ? [] : ["app"], ...input.site ? [] : ["site"], ...REQUIRED_FLAGS.filter((f) => !text7(flags[f]))];
59106
+ if (missing.length) return refuse4(io, "needs_input", "Name the app and site (or --content), the member's login email, a title and a link.", 2, { missing });
59107
+ const requestId2 = text7(flags["request-id"]) ?? (deps.newRequestId ?? randomUUID5)();
59108
+ if (!UUID_RE.test(requestId2)) return refuse4(io, "invalid_request_id", "Reuse the requestId a previous send printed.");
59109
+ const owner = await hostedOwnerSession(flags, deps);
59110
+ if (!owner.ok) return refuse4(io, owner.error, owner.hint, owner.exit);
59111
+ const { session } = owner;
59112
+ const ownership = await verifyAppOwnership({ ownerIssue: session.ownerIssue, controlToken: session.controlToken, declaredName: input.app, fetchImpl: deps.fetchImpl });
59113
+ if (ownership.state !== "owned") return refuse4(io, `app_ownership_${ownership.state}`, "Sending requires the app owner.", 1);
59114
+ let siteToken2;
59115
+ try {
59116
+ siteToken2 = await (deps.siteToken ?? siteToken)(input.site);
59117
+ } catch {
59118
+ return refuse4(io, "wix_site_token_required", "Run wix login as the site owner; the site token reads this site's members.", 1);
59119
+ }
59120
+ const body = {
59121
+ app: ownership.slug,
59122
+ site: input.site,
59123
+ memberEmail: text7(flags["member-email"]),
59124
+ title: text7(flags.title),
59125
+ ...text7(flags.body) ? { body: text7(flags.body) } : {},
59126
+ link: text7(flags.link),
59127
+ requestId: requestId2,
59128
+ ...flags["dry-run"] === true ? { dryRun: true } : {}
59129
+ };
59130
+ const http = await pushSendRequest({ target: session.target, wixToken: session.wixToken, siteToken: siteToken2, body, previewTag: session.previewTag, fetchImpl: deps.fetchImpl });
59131
+ return report2(http, body, flags, io);
59132
+ }
59133
+ function flagRefusal(flags) {
59134
+ if (LOCAL_SENDER_FLAGS.some((f) => flags[f] !== void 0)) return "flag_not_supported_with_ping";
59135
+ if (SWITCHES.some((f) => flags[f] !== void 0 && typeof flags[f] !== "boolean")) return "invalid_boolean_flag";
59136
+ return void 0;
59137
+ }
59138
+ function report2(http, sent, flags, io) {
59139
+ const code = refusalCode(http);
59140
+ if (code === "send_outcome_unknown") {
59141
+ return refuse4(io, code, `Ping may have accepted it. Re-run the same command with --request-id ${sent.requestId}: Ping runs one request at most once per site for 24 hours.`, 1, { requestId: sent.requestId });
59142
+ }
59143
+ if (code === "rate_limited" && http.retryAfterSec) {
59144
+ return refuse4(io, code, `Test sends are limited per owner; retry in ${http.retryAfterSec} seconds.`, 1, { retryAfterSec: http.retryAfterSec });
59145
+ }
59146
+ if (code) return refuse4(io, code, HINTS[code], 1);
59147
+ if (flags.json === true) io.log(JSON.stringify(http.body, null, 2));
59148
+ else io.log(summary(http.body ?? {}, sent));
59149
+ return 0;
59150
+ }
59151
+ function refusalCode(http) {
59152
+ if (http.error) return http.error === "push_send_unreachable" ? "send_outcome_unknown" : http.error;
59153
+ if (http.ok && (http.body?.state === "accepted" || http.body?.state === "dry_run")) return void 0;
59154
+ const code = String(http.body?.error ?? "");
59155
+ if (KNOWN_CODES.has(code)) return code;
59156
+ return http.ok || http.status >= 500 ? "send_outcome_unknown" : "push_send_failed";
59157
+ }
59158
+ function summary(result, sent) {
59159
+ if (result.state === "dry_run") {
59160
+ return `Dry run passed: ${sent.memberEmail} is an approved member, the link routes, and push is ready for ${sent.app}. No notification was sent.`;
59161
+ }
59162
+ if (result.deduplicated === true) {
59163
+ return `Ping already ran request ${sent.requestId}; the earlier send stands and no second notification was sent.`;
59164
+ }
59165
+ return `Ping accepted request ${sent.requestId} for ${sent.memberEmail}. Ping does not confirm delivery: it reaches the member's signed-in devices with notifications allowed.`;
59166
+ }
59167
+ var LOCAL_SENDER_FLAGS, SWITCHES, REQUIRED_FLAGS, UUID_RE, KNOWN_CODES, HINTS, text7;
59168
+ var init_notify_ping = __esm({
59169
+ "src/notify-ping.ts"() {
59170
+ init_push_send();
59171
+ init_push_send_service();
59172
+ init_hosted_owner();
59173
+ init_owner_claim();
59174
+ init_push_onboarding();
59175
+ init_wix_owner();
59176
+ LOCAL_SENDER_FLAGS = ["token", "device", "all", "admin-url", "admin-session", "silent", "badge", "data", "category", "channel", "platform", "bundle-id"];
59177
+ SWITCHES = ["json", "dry-run"];
59178
+ REQUIRED_FLAGS = ["member-email", "title", "link"];
59179
+ UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
59180
+ KNOWN_CODES = new Set(PUSH_SEND_ERROR_CODES);
59181
+ HINTS = {
59182
+ push_not_ready: "Run himi notify setup for this app and site first; status must be ready.",
59183
+ member_not_found: "Use the login email of an approved member of this site.",
59184
+ member_ambiguous: "More than one member has this login email; resolve it in the site's Members.",
59185
+ member_not_active: "The member must be approved before they can be notified.",
59186
+ link_not_routable: "Link to a screen this app's published release ships, e.g. <scheme>://screen/<screenId>?id=\u2026.",
59187
+ forbidden_site: "The Wix account from wix login must own this site.",
59188
+ site_token_invalid: "Run wix login as the site owner; the site token must belong to the app owner's account.",
59189
+ ping_permission_denied: "Himi Serve is not permitted to send through Ping; this is a platform issue, not your setup."
59190
+ };
59191
+ text7 = (v) => typeof v === "string" && v.length > 0 ? v : void 0;
59192
+ }
59193
+ });
59194
+
58791
59195
  // src/notify.ts
58792
59196
  var notify_exports = {};
58793
59197
  __export(notify_exports, {
@@ -58890,7 +59294,7 @@ async function runNotify(pos, flags, io, deps = {}) {
58890
59294
  } catch {
58891
59295
  reachable = false;
58892
59296
  }
58893
- const report2 = {
59297
+ const report3 = {
58894
59298
  // BOTH platforms must be ready. Considering only iOS meant a server with an APNs
58895
59299
  // key but no Firebase credentials reported ok and exited 0, while every Android
58896
59300
  // send would fail — from the command whose whole job is to answer that question.
@@ -58903,15 +59307,15 @@ async function runNotify(pos, flags, io, deps = {}) {
58903
59307
  ...reachable && authed && !senders ? { hint: "this admin-server predates /v1/admin/push-doctor \u2014 upgrade it to see sender readiness" } : {}
58904
59308
  };
58905
59309
  if (json6) {
58906
- print(report2);
58907
- return report2.ok ? 0 : 1;
59310
+ print(report3);
59311
+ return report3.ok ? 0 : 1;
58908
59312
  }
58909
59313
  io.log(
58910
59314
  `admin ${adminUrl} ${reachable ? authed ? "reachable, authenticated" : "reachable, NOT authenticated" : "UNREACHABLE"}`
58911
59315
  );
58912
59316
  if (!senders) {
58913
59317
  io.log("senders unknown \u2014 credentials live on the admin-server, and it did not report them");
58914
- if (report2.hint) io.log(` ${report2.hint}`);
59318
+ if (report3.hint) io.log(` ${report3.hint}`);
58915
59319
  return 1;
58916
59320
  }
58917
59321
  for (const [platform, r] of [
@@ -58921,7 +59325,7 @@ async function runNotify(pos, flags, io, deps = {}) {
58921
59325
  io.log(`${platform.padEnd(11)} ${r.sender}${r.note ? ` \u2014 ${r.note}` : ""}`);
58922
59326
  if (r.missing.length) io.log(` missing on the server: ${r.missing.join(", ")}`);
58923
59327
  }
58924
- return report2.ok ? 0 : 1;
59328
+ return report3.ok ? 0 : 1;
58925
59329
  }
58926
59330
  const app = str9(flags.app);
58927
59331
  if (!app) {
@@ -59100,10 +59504,10 @@ function makeTransport(fetchImpl, auth, timeoutMs = DEFAULT_TIMEOUT_MS4) {
59100
59504
  body: body === void 0 ? void 0 : JSON.stringify(body),
59101
59505
  signal: AbortSignal.timeout(timeoutMs)
59102
59506
  });
59103
- const text6 = await res.text();
59104
- let parsed = text6;
59507
+ const text8 = await res.text();
59508
+ let parsed = text8;
59105
59509
  try {
59106
- parsed = text6 ? JSON.parse(text6) : null;
59510
+ parsed = text8 ? JSON.parse(text8) : null;
59107
59511
  } catch {
59108
59512
  }
59109
59513
  if (!res.ok) throw new WixApiError2(res.status, method, path2, parsed);
@@ -59200,10 +59604,10 @@ async function mintVisitorToken(fetchImpl, clientId) {
59200
59604
  body: JSON.stringify({ clientId, grantType: "anonymous" }),
59201
59605
  signal: AbortSignal.timeout(8e3)
59202
59606
  });
59203
- const text6 = await res.text();
59204
- let parsed = text6;
59607
+ const text8 = await res.text();
59608
+ let parsed = text8;
59205
59609
  try {
59206
- parsed = text6 ? JSON.parse(text6) : null;
59610
+ parsed = text8 ? JSON.parse(text8) : null;
59207
59611
  } catch {
59208
59612
  }
59209
59613
  if (!res.ok) throw new WixApiError2(res.status, "POST", "/oauth2/token", parsed);
@@ -59218,28 +59622,28 @@ var init_visitor_auth = __esm({
59218
59622
  });
59219
59623
 
59220
59624
  // src/site-analyze/copy-clean.ts
59221
- function isTemplatePlaceholder(text6) {
59222
- return PLACEHOLDER_RE.test(text6);
59625
+ function isTemplatePlaceholder(text8) {
59626
+ return PLACEHOLDER_RE.test(text8);
59223
59627
  }
59224
59628
  function cleanText(raw, cap) {
59225
59629
  const s = String(raw ?? "").replace(/[\u0000-\u001F\u007F]/g, " ").replace(/\s+/g, " ").trim();
59226
59630
  return s.length > cap ? `${s.slice(0, cap - 1).trimEnd()}\u2026` : s;
59227
59631
  }
59228
- function stripPii(text6) {
59229
- return text6.replace(EMAIL_RE2, "").replace(PHONE_SHAPE_RE, (m) => {
59632
+ function stripPii(text8) {
59633
+ return text8.replace(EMAIL_RE2, "").replace(PHONE_SHAPE_RE, (m) => {
59230
59634
  const digits2 = (m.match(ANY_DIGIT_RE) ?? []).length;
59231
59635
  return digits2 >= PHONE_MIN_DIGITS && PHONE_DIGIT_RUN_RE.test(m) ? "" : m;
59232
59636
  }).replace(/\s+/g, " ").trim();
59233
59637
  }
59234
- function isBoilerplate(text6) {
59235
- return BOILERPLATE_RE.test(text6) || PLACEHOLDER_RE.test(text6);
59638
+ function isBoilerplate(text8) {
59639
+ return BOILERPLATE_RE.test(text8) || PLACEHOLDER_RE.test(text8);
59236
59640
  }
59237
59641
  function copyOrDrop(raw, cap) {
59238
- const text6 = stripPii(cleanText(raw, cap));
59239
- if (text6.length < 2) return "";
59240
- if (isBoilerplate(text6)) return "";
59241
- if (!/[A-Za-zͰ-῿぀-힯]/.test(text6)) return "";
59242
- return text6;
59642
+ const text8 = stripPii(cleanText(raw, cap));
59643
+ if (text8.length < 2) return "";
59644
+ if (isBoilerplate(text8)) return "";
59645
+ if (!/[A-Za-zͰ-῿぀-힯]/.test(text8)) return "";
59646
+ return text8;
59243
59647
  }
59244
59648
  function dedupe2(values, cap) {
59245
59649
  const out2 = [];
@@ -59273,9 +59677,9 @@ var init_copy_clean = __esm({
59273
59677
  function genericDescription(it) {
59274
59678
  for (const field of DESCRIPTION_FIELDS) {
59275
59679
  const raw = it[field];
59276
- const text6 = typeof raw === "string" ? raw : str6(raw?.text);
59277
- if (!text6) continue;
59278
- const plain2 = text6.replace(/<[^>]*>/g, " ").replace(/&nbsp;/g, " ");
59680
+ const text8 = typeof raw === "string" ? raw : str6(raw?.text);
59681
+ if (!text8) continue;
59682
+ const plain2 = text8.replace(/<[^>]*>/g, " ").replace(/&nbsp;/g, " ");
59279
59683
  if (plain2.trim()) return plain2;
59280
59684
  }
59281
59685
  return void 0;
@@ -59388,7 +59792,7 @@ function formLabels(x, depth = 0, out2 = []) {
59388
59792
  if (typeof x !== "object") return out2;
59389
59793
  const o = x;
59390
59794
  const label2 = str6(o.label ?? o.displayName ?? o.fieldTitle ?? o.title);
59391
- if (label2 && !UUID_RE.test(label2) && label2.length < 80 && !out2.includes(label2)) out2.push(label2);
59795
+ if (label2 && !UUID_RE2.test(label2) && label2.length < 80 && !out2.includes(label2)) out2.push(label2);
59392
59796
  for (const k of ["fields", "formFields", "form", "schema", "properties"]) {
59393
59797
  if (o[k] != null) formLabels(o[k], depth + 1, out2);
59394
59798
  }
@@ -59506,7 +59910,7 @@ function sampleTitle(data) {
59506
59910
  for (const [k, v] of Object.entries(data)) {
59507
59911
  if (k.startsWith("_") || typeof v !== "string") continue;
59508
59912
  const s = v.trim();
59509
- if (s.length > 2 && s.length < 80 && !s.includes("@") && !/^https?:/i.test(s) && !UUID_RE.test(s)) return s;
59913
+ if (s.length > 2 && s.length < 80 && !s.includes("@") && !/^https?:/i.test(s) && !UUID_RE2.test(s)) return s;
59510
59914
  }
59511
59915
  return void 0;
59512
59916
  }
@@ -59605,7 +60009,7 @@ async function readMemberRoles(call) {
59605
60009
  if (sawAuthError) return { names: [], status: "error" };
59606
60010
  return { names: [], status: "empty" };
59607
60011
  }
59608
- var asArr, str6, DESCRIPTION_FIELDS, UUID_RE, CORE_PROBES, OPTIONAL_PROBES, ALL, CMS_LIST_LIMIT;
60012
+ var asArr, str6, DESCRIPTION_FIELDS, UUID_RE2, CORE_PROBES, OPTIONAL_PROBES, ALL, CMS_LIST_LIMIT;
59609
60013
  var init_probes = __esm({
59610
60014
  "src/site-analyze/probes.ts"() {
59611
60015
  init_rest();
@@ -59613,7 +60017,7 @@ var init_probes = __esm({
59613
60017
  asArr = (x) => Array.isArray(x) ? x : [];
59614
60018
  str6 = (x) => typeof x === "string" ? x : "";
59615
60019
  DESCRIPTION_FIELDS = ["plainDescription", "description", "shortDescription", "excerpt", "tagLine", "subtitle"];
59616
- UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
60020
+ UUID_RE2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
59617
60021
  CORE_PROBES = [
59618
60022
  {
59619
60023
  vertical: "store",
@@ -60320,12 +60724,12 @@ function mergeWst(pages) {
60320
60724
  return acc;
60321
60725
  }
60322
60726
  function fontSlug(value) {
60323
- let text6 = value;
60727
+ let text8 = value;
60324
60728
  try {
60325
- text6 = decodeURIComponent(value);
60729
+ text8 = decodeURIComponent(value);
60326
60730
  } catch {
60327
60731
  }
60328
- return text6.toLowerCase().replace(/[^a-z0-9]+/g, "");
60732
+ return text8.toLowerCase().replace(/[^a-z0-9]+/g, "");
60329
60733
  }
60330
60734
  function fontUrlSlug(url) {
60331
60735
  const last = url.split("?")[0].split("#")[0].split("/").pop() ?? "";
@@ -60499,8 +60903,8 @@ function parsePublishedHtml(html) {
60499
60903
  const rawHeadings = [];
60500
60904
  HEADING_RE.lastIndex = 0;
60501
60905
  while (m = HEADING_RE.exec(html)) {
60502
- const text6 = copyOrDrop(textFromHtml(m[1] ?? ""), 120);
60503
- if (text6) rawHeadings.push(text6);
60906
+ const text8 = copyOrDrop(textFromHtml(m[1] ?? ""), 120);
60907
+ if (text8) rawHeadings.push(text8);
60504
60908
  if (rawHeadings.length >= 40) break;
60505
60909
  }
60506
60910
  const headings = dedupe2(rawHeadings, 20);
@@ -61327,16 +61731,16 @@ function isPrivateV4([a, b, c]) {
61327
61731
  return a >= 224;
61328
61732
  }
61329
61733
  function v6Groups(host) {
61330
- let text6 = host;
61331
- const dotted = /:((?:\d{1,3}\.){3}\d{1,3})$/.exec(text6);
61734
+ let text8 = host;
61735
+ const dotted = /:((?:\d{1,3}\.){3}\d{1,3})$/.exec(text8);
61332
61736
  if (dotted) {
61333
61737
  const octets = ipv4Octets(dotted[1]);
61334
61738
  if (!octets) return null;
61335
61739
  const hi = (octets[0] << 8 | octets[1]).toString(16);
61336
61740
  const lo = (octets[2] << 8 | octets[3]).toString(16);
61337
- text6 = `${text6.slice(0, dotted.index)}:${hi}:${lo}`;
61741
+ text8 = `${text8.slice(0, dotted.index)}:${hi}:${lo}`;
61338
61742
  }
61339
- const halves = text6.split("::");
61743
+ const halves = text8.split("::");
61340
61744
  if (halves.length > 2) return null;
61341
61745
  const parts = (part) => part ? part.split(":") : [];
61342
61746
  const head = parts(halves[0]);
@@ -61418,10 +61822,10 @@ function assertPublicSiteUrl(raw) {
61418
61822
  if (refusal) throw new Error(refusal);
61419
61823
  }
61420
61824
  function resolveAll(hostname2) {
61421
- return new Promise((resolve52, reject) => {
61825
+ return new Promise((resolve53, reject) => {
61422
61826
  dnsLookup(hostname2, { all: true, verbatim: true }, (err, addresses) => {
61423
61827
  if (err) reject(err);
61424
- else resolve52(addresses);
61828
+ else resolve53(addresses);
61425
61829
  });
61426
61830
  });
61427
61831
  }
@@ -61429,13 +61833,13 @@ function withAbort(start, signal, what) {
61429
61833
  if (signal?.aborted) return Promise.reject(new Error(`${what} aborted`));
61430
61834
  const work = start();
61431
61835
  if (!signal) return work;
61432
- return new Promise((resolve52, reject) => {
61836
+ return new Promise((resolve53, reject) => {
61433
61837
  const onAbort = () => reject(new Error(`${what} aborted`));
61434
61838
  signal.addEventListener("abort", onAbort, { once: true });
61435
- work.then(resolve52, reject).finally(() => signal.removeEventListener("abort", onAbort));
61839
+ work.then(resolve53, reject).finally(() => signal.removeEventListener("abort", onAbort));
61436
61840
  });
61437
61841
  }
61438
- async function pinAddress(url, resolve52 = resolveAll, signal) {
61842
+ async function pinAddress(url, resolve53 = resolveAll, signal) {
61439
61843
  const host = url.hostname.replace(/^\[|\]$/g, "");
61440
61844
  if (ipv4Octets(host)) {
61441
61845
  if (isBlockedAddress(host)) throw new Error(`${host} is a blocked address`);
@@ -61445,7 +61849,7 @@ async function pinAddress(url, resolve52 = resolveAll, signal) {
61445
61849
  if (isBlockedAddress(host)) throw new Error(`${host} is a blocked address`);
61446
61850
  return { address: host, family: 6 };
61447
61851
  }
61448
- const addresses = await withAbort(() => resolve52(host), signal, `${host} lookup`);
61852
+ const addresses = await withAbort(() => resolve53(host), signal, `${host} lookup`);
61449
61853
  if (!addresses.length) throw new Error(`${host} resolved to no address`);
61450
61854
  const blocked = addresses.filter((a) => isBlockedAddress(a.address)).map((a) => a.address);
61451
61855
  if (blocked.length) throw new Error(`${host} resolves to a blocked address (${blocked.join(", ")})`);
@@ -61485,7 +61889,7 @@ function copyIncomingHeaders(headers2) {
61485
61889
  return out2;
61486
61890
  }
61487
61891
  function requestOnce(url, pinned, init) {
61488
- return new Promise((resolve52, reject) => {
61892
+ return new Promise((resolve53, reject) => {
61489
61893
  const headers2 = { host: url.host, "accept-encoding": "identity" };
61490
61894
  new Headers(init.headers ?? {}).forEach((value, key2) => {
61491
61895
  if (key2.toLowerCase() !== "host") headers2[key2] = value;
@@ -61515,7 +61919,7 @@ function requestOnce(url, pinned, init) {
61515
61919
  reject(err);
61516
61920
  return;
61517
61921
  }
61518
- resolve52(new Response(body, { status, statusText: res.statusMessage ?? "", headers: out2 }));
61922
+ resolve53(new Response(body, { status, statusText: res.statusMessage ?? "", headers: out2 }));
61519
61923
  }
61520
61924
  );
61521
61925
  req.on("error", reject);
@@ -61591,8 +61995,8 @@ var init_net_guard = __esm({
61591
61995
  function textFields(look) {
61592
61996
  if (look.unavailable) return [];
61593
61997
  const out2 = [];
61594
- const add = (text6, where, narrative = false) => {
61595
- if (text6 && text6.trim()) out2.push({ text: text6, where, narrative });
61998
+ const add = (text8, where, narrative = false) => {
61999
+ if (text8 && text8.trim()) out2.push({ text: text8, where, narrative });
61596
62000
  };
61597
62001
  for (const label2 of look.navLabels) add(label2, "nav");
61598
62002
  for (const p of look.pages) {
@@ -61983,8 +62387,8 @@ async function analyzeSite(input, ports) {
61983
62387
  if (locale) plan.provisioning.locale = locale;
61984
62388
  const velo = buildVeloChapter({ look, owner: ownerVelo });
61985
62389
  const veloFiles = ownerVelo.files.flatMap((f) => {
61986
- const text6 = f.content ?? f.excerpt;
61987
- return text6 ? [{ path: f.path, text: text6 }] : [];
62390
+ const text8 = f.content ?? f.excerpt;
62391
+ return text8 ? [{ path: f.path, text: text8 }] : [];
61988
62392
  }).slice(0, 80);
61989
62393
  const permissions = buildPermissionsChapter({
61990
62394
  cms,
@@ -62206,8 +62610,8 @@ function cmsStatusNote(taxonomy) {
62206
62610
  function yn(v) {
62207
62611
  return v == null || v === "" ? "\u2014" : String(v);
62208
62612
  }
62209
- function isAuthPhrase(text6) {
62210
- const t = text6.trim();
62613
+ function isAuthPhrase(text8) {
62614
+ const t = text8.trim();
62211
62615
  return AUTH_ONLY_ASCII.test(t) || LOGIN_HE.includes(t);
62212
62616
  }
62213
62617
  function looksGated(page) {
@@ -62584,9 +62988,9 @@ function attachVeloSources(book, sources, put) {
62584
62988
  if (isSecretVeloPath(f.path)) continue;
62585
62989
  const rel = veloArtifactRelPath(f.path);
62586
62990
  if (!rel) continue;
62587
- const text6 = byPath.get(f.path) ?? f.excerpt;
62588
- if (!text6) continue;
62589
- put(rel, text6);
62991
+ const text8 = byPath.get(f.path) ?? f.excerpt;
62992
+ if (!text8) continue;
62993
+ put(rel, text8);
62590
62994
  f.localPath = rel;
62591
62995
  }
62592
62996
  }
@@ -62831,14 +63235,14 @@ var init_customer_build = __esm({
62831
63235
 
62832
63236
  // src/site-analyze/apply.ts
62833
63237
  import { existsSync as existsSync64, readFileSync as readFileSync76, writeFileSync as writeFileSync27, copyFileSync as copyFileSync3, mkdirSync as mkdirSync31, realpathSync as realpathSync9, statSync as statSync17 } from "node:fs";
62834
- import { dirname as dirname35, isAbsolute as isAbsolute6, join as join63, resolve as resolve49, sep as sep16 } from "node:path";
63238
+ import { dirname as dirname35, isAbsolute as isAbsolute6, join as join63, resolve as resolve50, sep as sep16 } from "node:path";
62835
63239
  import { spawnSync as spawnSync4 } from "node:child_process";
62836
63240
  function unpairedRedirectNote(overlay) {
62837
63241
  if (!overlay.wix?.clientId || overlay.wix.redirectUri) return void 0;
62838
63242
  return `no OAuth redirect paired with client ${overlay.wix.clientId} \u2014 the app (and prepare:customer) will refuse to start until one is set. Register it with \`himi site client ensure --site ${overlay.siteId?.trim() || "<msid>"} --redirect-uri <uri> --yes\`, then re-run analyze with \`--redirect-uri <uri>\` (or export HIMI_BRANDED_LITE_WIX_REDIRECT_URI)`;
62839
63243
  }
62840
63244
  function posix3(p) {
62841
- return resolve49(p).replace(/\\/g, "/");
63245
+ return resolve50(p).replace(/\\/g, "/");
62842
63246
  }
62843
63247
  function committedShellProvisioning(path2) {
62844
63248
  return committedShellFor(posix3(path2));
@@ -62872,7 +63276,7 @@ function assertNotCommitted(path2, forbidden) {
62872
63276
  `refusing to overwrite committed ${committedProvisioningPath(committed)} \u2014 use prepare:customer --contract`
62873
63277
  );
62874
63278
  }
62875
- if (forbidden && resolve49(path2) === resolve49(forbidden)) {
63279
+ if (forbidden && resolve50(path2) === resolve50(forbidden)) {
62876
63280
  const named = committedShellProvisioning(forbidden);
62877
63281
  throw new Error(
62878
63282
  `refusing to overwrite committed ${named ? committedProvisioningPath(named) : forbidden} \u2014 pass a content-package provisioning.json or use prepare:customer --contract`
@@ -62928,8 +63332,8 @@ function buildMergedContract(options) {
62928
63332
  const staged = [];
62929
63333
  const contained = (root, rel) => {
62930
63334
  if (isAbsolute6(rel)) return void 0;
62931
- const full = resolve49(root, rel);
62932
- return full.startsWith(resolve49(root) + sep16) ? full : void 0;
63335
+ const full = resolve50(root, rel);
63336
+ return full.startsWith(resolve50(root) + sep16) ? full : void 0;
62933
63337
  };
62934
63338
  const stage = (fromRel, toRel) => {
62935
63339
  const from = contained(packageDir, fromRel);
@@ -63018,7 +63422,7 @@ function catalogTypography(overlay, template) {
63018
63422
  function staticFaqFor(overlay, overlayPath) {
63019
63423
  const own3 = overlay.faq;
63020
63424
  if (own3 !== void 0) return deriveStaticFaq(own3);
63021
- const bookPath = join63(dirname35(resolve49(overlayPath)), "book.json");
63425
+ const bookPath = join63(dirname35(resolve50(overlayPath)), "book.json");
63022
63426
  if (!existsSync64(bookPath)) return void 0;
63023
63427
  try {
63024
63428
  const book = JSON.parse(readFileSync76(bookPath, "utf8"));
@@ -63032,7 +63436,7 @@ function staticFaqFor(overlay, overlayPath) {
63032
63436
  }
63033
63437
  }
63034
63438
  async function applyOverlays(input) {
63035
- const cwd = resolve49(input.cwd);
63439
+ const cwd = resolve50(input.cwd);
63036
63440
  const shell = input.shell ?? DEFAULT_BRANDABLE_SHELL;
63037
63441
  assertKnownShell(shell);
63038
63442
  const overlay = JSON.parse(readFileSync76(input.overlayPath, "utf8"));
@@ -63056,7 +63460,7 @@ async function applyOverlays(input) {
63056
63460
  `shell "${shell}" is a site shell: its contract names the customer's website (a \`site\` block), and this overlay has none. Re-run \`himi site analyze --url <site> --shell ${shell}\` to derive the site's page map (edit \`site.pages\` in provisioning.overlay.json if needed), then --apply.`
63057
63461
  );
63058
63462
  }
63059
- const packageDir = dirname35(resolve49(input.overlayPath));
63463
+ const packageDir = dirname35(resolve50(input.overlayPath));
63060
63464
  const { path: contract, staged } = buildMergedContract({ appDir: appDir3, packageDir, overlay });
63061
63465
  wrote.push(...staged);
63062
63466
  const r = spawnSync4("npm", ["run", "prepare:customer", "--", "--contract", contract], {
@@ -63151,7 +63555,7 @@ async function applyOverlays(input) {
63151
63555
  }
63152
63556
  }
63153
63557
  if (!input.iconPath || !existsSync64(input.iconPath)) {
63154
- const book = input.iconPath ? dirname35(dirname35(resolve49(input.iconPath))) : void 0;
63558
+ const book = input.iconPath ? dirname35(dirname35(resolve50(input.iconPath))) : void 0;
63155
63559
  notes.push(
63156
63560
  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"
63157
63561
  );
@@ -64270,10 +64674,10 @@ function permuteDomain(domain, allowSpecialUseDomain) {
64270
64674
  }
64271
64675
  function createPromiseCallback(cb) {
64272
64676
  let callback;
64273
- let resolve52;
64677
+ let resolve53;
64274
64678
  let reject;
64275
64679
  const promise = new Promise((_resolve, _reject) => {
64276
- resolve52 = _resolve;
64680
+ resolve53 = _resolve;
64277
64681
  reject = _reject;
64278
64682
  });
64279
64683
  if (typeof cb === "function") {
@@ -64289,7 +64693,7 @@ function createPromiseCallback(cb) {
64289
64693
  callback = (err, result) => {
64290
64694
  try {
64291
64695
  if (err) reject(err);
64292
- else resolve52(result);
64696
+ else resolve53(result);
64293
64697
  } catch (e) {
64294
64698
  reject(e instanceof Error ? e : new Error());
64295
64699
  }
@@ -66356,8 +66760,8 @@ function copyFromEval(evaled) {
66356
66760
  evaled.meta?.ogTitle,
66357
66761
  evaled.meta?.ogDescription
66358
66762
  ].map((raw) => {
66359
- const text6 = cleanText(raw, 400);
66360
- return text6 && isTemplatePlaceholder(text6) ? stripPii(cleanText(text6, 90)) : "";
66763
+ const text8 = cleanText(raw, 400);
66764
+ return text8 && isTemplatePlaceholder(text8) ? stripPii(cleanText(text8, 90)) : "";
66361
66765
  }),
66362
66766
  8
66363
66767
  );
@@ -66889,7 +67293,7 @@ var init_crawl_look = __esm({
66889
67293
  });
66890
67294
 
66891
67295
  // src/site-analyze/site-function.ts
66892
- import { randomUUID as randomUUID5 } from "node:crypto";
67296
+ import { randomUUID as randomUUID6 } from "node:crypto";
66893
67297
  function createRevisionUrl(msid, esi) {
66894
67298
  const q = new URLSearchParams({ editorSessionId: esi, esi, metaSiteId: msid });
66895
67299
  return `https://editor.wix.com/html/v1/create_revision?${q}`;
@@ -67256,13 +67660,13 @@ async function veloCall(fetchImpl, method, url, opts = {}) {
67256
67660
  body: opts.body === void 0 ? void 0 : JSON.stringify(opts.body),
67257
67661
  signal: AbortSignal.timeout(opts.timeoutMs ?? 3e4)
67258
67662
  });
67259
- const text6 = await res.text();
67260
- let json6 = text6;
67663
+ const text8 = await res.text();
67664
+ let json6 = text8;
67261
67665
  try {
67262
- json6 = text6 ? JSON.parse(text6) : null;
67666
+ json6 = text8 ? JSON.parse(text8) : null;
67263
67667
  } catch {
67264
67668
  }
67265
- return { status: res.status, json: json6, text: text6 };
67669
+ return { status: res.status, json: json6, text: text8 };
67266
67670
  }
67267
67671
  function snippet2(x, cap = 180) {
67268
67672
  const s = typeof x === "string" ? x : JSON.stringify(x) ?? "";
@@ -67334,7 +67738,7 @@ function codeAppIdFromRevision(json6) {
67334
67738
  async function snapshotAndPublish(fetchImpl, token, msid, opts = {}) {
67335
67739
  const production = opts.production !== false;
67336
67740
  const initiator = opts.initiator ?? "himi";
67337
- const esi = randomUUID5();
67741
+ const esi = randomUUID6();
67338
67742
  const rev = await veloCall(fetchImpl, "POST", createRevisionUrl(msid, esi), {
67339
67743
  token,
67340
67744
  msid,
@@ -67431,7 +67835,7 @@ async function runSiteFunction(opts) {
67431
67835
  if (!httpOk(patched)) {
67432
67836
  return { ...base, error: `UpdateFiles ${patched.status}: ${snippet2(patched.json ?? patched.text)}` };
67433
67837
  }
67434
- let report2 = {
67838
+ let report3 = {
67435
67839
  ...base,
67436
67840
  ok: true,
67437
67841
  source: extractHttpFunction(next, method, name),
@@ -67440,21 +67844,21 @@ async function runSiteFunction(opts) {
67440
67844
  if (opts.publish) {
67441
67845
  const snap = await snapshotAndPublish(fetchImpl, opts.token, opts.msid, { production: true, initiator: "himi" });
67442
67846
  const pubOk = httpOk(snap.rev) && httpOk(snap.live);
67443
- report2 = {
67444
- ...report2,
67445
- ok: report2.ok && pubOk,
67847
+ report3 = {
67848
+ ...report3,
67849
+ ok: report3.ok && pubOk,
67446
67850
  revision: snap.revision,
67447
67851
  codeAppId: snap.codeAppId,
67448
67852
  publish: { revision: httpOk(snap.rev), rc: httpOk(snap.rc), live: httpOk(snap.live), status: snap.live?.status },
67449
- error: pubOk ? report2.error : snippet2(snap.live?.json ?? snap.rev.json)
67853
+ error: pubOk ? report3.error : snippet2(snap.live?.json ?? snap.rev.json)
67450
67854
  };
67451
67855
  }
67452
67856
  if (opts.invoke) {
67453
- if (!opts.siteUrl) return { ...report2, ok: false, error: "put --invoke needs --url" };
67454
- const invoked = await invokeAndReport(fetchImpl, opts, report2, name, method);
67455
- return { ...report2, ...invoked, op: "put", ok: report2.ok && invoked.ok };
67857
+ if (!opts.siteUrl) return { ...report3, ok: false, error: "put --invoke needs --url" };
67858
+ const invoked = await invokeAndReport(fetchImpl, opts, report3, name, method);
67859
+ return { ...report3, ...invoked, op: "put", ok: report3.ok && invoked.ok };
67456
67860
  }
67457
- return report2;
67861
+ return report3;
67458
67862
  }
67459
67863
  async function invokeAndReport(fetchImpl, opts, base, name, method) {
67460
67864
  const siteUrl = opts.siteUrl;
@@ -67519,7 +67923,7 @@ __export(cli_exports, {
67519
67923
  runSiteFunctionCli: () => runSiteFunctionCli
67520
67924
  });
67521
67925
  import { readFileSync as readFileSync77 } from "node:fs";
67522
- import { resolve as resolve50 } from "node:path";
67926
+ import { resolve as resolve51 } from "node:path";
67523
67927
  function assetFetchFor(_opts) {
67524
67928
  return pinnedFetch2;
67525
67929
  }
@@ -67539,7 +67943,7 @@ async function runSiteAnalyze(opts) {
67539
67943
  const assetFetch = assetFetchFor(opts);
67540
67944
  const siteFetch = guardedSiteFetch;
67541
67945
  const cwd = opts.cwd ?? process.cwd();
67542
- const outDir = resolve50(cwd, opts.outDir ?? ".himi/business-book");
67946
+ const outDir = resolve51(cwd, opts.outDir ?? ".himi/business-book");
67543
67947
  let ownerAuth;
67544
67948
  let msid;
67545
67949
  const ownerSite = ownerSiteFor(opts);
@@ -67591,9 +67995,9 @@ async function runSiteAnalyze(opts) {
67591
67995
  if (opts.apply) {
67592
67996
  applied = await applyOverlays({
67593
67997
  cwd,
67594
- overlayPath: resolve50(outDir, "provisioning.overlay.json"),
67595
- tokensPath: resolve50(outDir, "tokens.overlay.json"),
67596
- iconPath: resolve50(outDir, "media/icon.png"),
67998
+ overlayPath: resolve51(outDir, "provisioning.overlay.json"),
67999
+ tokensPath: resolve51(outDir, "tokens.overlay.json"),
68000
+ iconPath: resolve51(outDir, "media/icon.png"),
67597
68001
  shell: opts.shell
67598
68002
  });
67599
68003
  const note = noteWithWarnings(applied.note, warnings);
@@ -67629,7 +68033,7 @@ async function runSiteFunctionCli(opts) {
67629
68033
  const token = opts.token ?? await siteToken(msid);
67630
68034
  let source;
67631
68035
  if (opts.sourcePath) {
67632
- const p = resolve50(opts.cwd ?? process.cwd(), opts.sourcePath);
68036
+ const p = resolve51(opts.cwd ?? process.cwd(), opts.sourcePath);
67633
68037
  source = readFileSync77(p, "utf8");
67634
68038
  }
67635
68039
  let body;
@@ -67685,7 +68089,7 @@ async function accountToken2(spawnImpl = spawn6) {
67685
68089
  cached2 = fromEnv;
67686
68090
  return fromEnv;
67687
68091
  }
67688
- const token = await new Promise((resolve52, reject) => {
68092
+ const token = await new Promise((resolve53, reject) => {
67689
68093
  let child;
67690
68094
  try {
67691
68095
  child = spawnImpl("wix", ["token"]);
@@ -67714,7 +68118,7 @@ async function accountToken2(spawnImpl = spawn6) {
67714
68118
  const scan = (d) => {
67715
68119
  out2 += String(d);
67716
68120
  const m = out2.match(TOKEN_RE2);
67717
- if (m) done(() => resolve52(m[0]));
68121
+ if (m) done(() => resolve53(m[0]));
67718
68122
  };
67719
68123
  child.stdout?.on("data", scan);
67720
68124
  child.stderr?.on("data", scan);
@@ -67757,10 +68161,10 @@ function accountTransport(fetchImpl = fetch, timeoutMs = DEFAULT_TIMEOUT_MS5, sp
67757
68161
  }
67758
68162
  throw new Error(`${method} ${path2} failed: ${err?.message ?? String(e)}`);
67759
68163
  }
67760
- const text6 = await res.text();
67761
- let parsed = text6;
68164
+ const text8 = await res.text();
68165
+ let parsed = text8;
67762
68166
  try {
67763
- parsed = text6 ? JSON.parse(text6) : null;
68167
+ parsed = text8 ? JSON.parse(text8) : null;
67764
68168
  } catch {
67765
68169
  }
67766
68170
  if (!res.ok) throw new WixApiError(res.status, method, path2, parsed);
@@ -67821,16 +68225,16 @@ async function installVerticals(msid, wanted, token, fetchImpl = fetch, timeoutM
67821
68225
  body: JSON.stringify({ apps: wanted.map((w) => ({ id: w.appDefId })) }),
67822
68226
  signal: AbortSignal.timeout(timeoutMs)
67823
68227
  });
67824
- const text6 = await res.text();
68228
+ const text8 = await res.text();
67825
68229
  if (!res.ok) {
67826
68230
  return {
67827
68231
  ...empty,
67828
- errors: wanted.map((w) => ({ ...w, message: `bulk-install -> ${res.status}: ${text6.slice(0, 200)}` }))
68232
+ errors: wanted.map((w) => ({ ...w, message: `bulk-install -> ${res.status}: ${text8.slice(0, 200)}` }))
67829
68233
  };
67830
68234
  }
67831
68235
  let body = {};
67832
68236
  try {
67833
- body = text6 ? JSON.parse(text6) : {};
68237
+ body = text8 ? JSON.parse(text8) : {};
67834
68238
  } catch {
67835
68239
  }
67836
68240
  const nameOf = (appDefId) => wanted.find((w) => w.appDefId === appDefId)?.vertical ?? appDefId;
@@ -68433,9 +68837,9 @@ function redactVeloSource(source, knownSecrets = []) {
68433
68837
  const visitLiterals = (node) => {
68434
68838
  if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node) || ts.isTemplateExpression(node)) {
68435
68839
  const start = node.getStart(clean2);
68436
- const text6 = source.slice(start, node.end);
68437
- const redacted = redactLiteral(text6, start);
68438
- if (redacted !== text6) {
68840
+ const text8 = source.slice(start, node.end);
68841
+ const redacted = redactLiteral(text8, start);
68842
+ if (redacted !== text8) {
68439
68843
  edits.push({ start, end: node.end, text: redacted });
68440
68844
  return;
68441
68845
  }
@@ -68716,9 +69120,9 @@ var init_context = __esm({
68716
69120
  import { mkdtempSync as mkdtempSync4, readFileSync as readFileSync79, mkdirSync as mkdirSync32, writeFileSync as writeFileSync29, existsSync as existsSync66, rmSync as rmSync15, readdirSync as readdirSync30, statSync as statSync18, lstatSync as lstatSync13, accessSync, constants as constants2, renameSync as renameSync7, cpSync as cpSync3 } from "node:fs";
68717
69121
  import { execFile, execFileSync as execFileSync7, spawnSync as spawnSync5, spawn as spawn7 } from "node:child_process";
68718
69122
  import { AsyncLocalStorage } from "node:async_hooks";
68719
- import { createHash as createHash31, randomUUID as randomUUID6 } from "node:crypto";
69123
+ import { createHash as createHash31, randomUUID as randomUUID7 } from "node:crypto";
68720
69124
  import { tmpdir as tmpdir5, homedir as homedir7, hostname } from "node:os";
68721
- import { join as join64, resolve as resolve51, relative as relative13, basename as basename17, dirname as dirname36 } from "node:path";
69125
+ import { join as join64, resolve as resolve52, relative as relative13, basename as basename17, dirname as dirname36 } from "node:path";
68722
69126
  import { fileURLToPath as fileURLToPath26 } from "node:url";
68723
69127
 
68724
69128
  // ../serve-cli/src/client.ts
@@ -68759,10 +69163,10 @@ var ServeCliClient = class {
68759
69163
  ...carriesCredential ? { redirect: "error" } : {},
68760
69164
  body: opts.body !== void 0 ? JSON.stringify(opts.body) : void 0
68761
69165
  });
68762
- const text6 = await res.text();
69166
+ const text8 = await res.text();
68763
69167
  let json6 = null;
68764
69168
  try {
68765
- json6 = text6 ? JSON.parse(text6) : null;
69169
+ json6 = text8 ? JSON.parse(text8) : null;
68766
69170
  } catch {
68767
69171
  json6 = null;
68768
69172
  }
@@ -68770,7 +69174,7 @@ var ServeCliClient = class {
68770
69174
  res.headers.forEach((v, k) => {
68771
69175
  h[k] = v;
68772
69176
  });
68773
- return { status: res.status, ok: res.ok, json: json6, text: text6, headers: h };
69177
+ return { status: res.status, ok: res.ok, json: json6, text: text8, headers: h };
68774
69178
  }
68775
69179
  // ---- serve (device-facing reads) ----
68776
69180
  health(plane) {
@@ -69335,8 +69739,8 @@ init_attestation();
69335
69739
 
69336
69740
  // ../../core/admin-web/src/utils/qr-terminal.ts
69337
69741
  init_qr();
69338
- function qrTerminal(text6, opts = {}) {
69339
- const { modules, size } = encodeQr(text6);
69742
+ function qrTerminal(text8, opts = {}) {
69743
+ const { modules, size } = encodeQr(text8);
69340
69744
  const quietZone = opts.quietZone ?? 4;
69341
69745
  const dim = size + quietZone * 2;
69342
69746
  const dark = (x, y) => x >= quietZone && x < quietZone + size && y >= quietZone && y < quietZone + size ? modules[y - quietZone][x - quietZone] : false;
@@ -69392,9 +69796,9 @@ function dpxSessionPresent(path2 = dpxSessionTokenPath()) {
69392
69796
  return false;
69393
69797
  }
69394
69798
  }
69395
- function isPortInUseError(text6) {
69396
- if (!text6) return false;
69397
- return /EADDRINUSE/i.test(text6) || new RegExp(`(:::|:)${DPX_SESSION_PORT}\\b`).test(text6);
69799
+ function isPortInUseError(text8) {
69800
+ if (!text8) return false;
69801
+ return /EADDRINUSE/i.test(text8) || new RegExp(`(:::|:)${DPX_SESSION_PORT}\\b`).test(text8);
69398
69802
  }
69399
69803
  function parseDpxAuthStatusIdentity(stdout) {
69400
69804
  if (!stdout) return void 0;
@@ -69459,7 +69863,7 @@ async function browserLogin(opts) {
69459
69863
  const log = opts.log ?? (() => {
69460
69864
  });
69461
69865
  const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
69462
- return await new Promise((resolve52, reject) => {
69866
+ return await new Promise((resolve53, reject) => {
69463
69867
  let settled = false;
69464
69868
  let loginUrl = "";
69465
69869
  const server = createServer();
@@ -69523,7 +69927,7 @@ async function browserLogin(opts) {
69523
69927
  return;
69524
69928
  }
69525
69929
  res.writeHead(200, { "content-type": "text/html; charset=utf-8", "cache-control": "no-store", connection: "close" }).end(CLOSE_PAGE);
69526
- finish2(() => resolve52({ token, loginUrl }));
69930
+ finish2(() => resolve53({ token, loginUrl }));
69527
69931
  });
69528
69932
  });
69529
69933
  server.on("error", (err) => finish2(() => reject(err)));
@@ -69787,7 +70191,7 @@ async function queryCms(opts) {
69787
70191
  async function createCms(opts) {
69788
70192
  const { app, cms, call, dryRun = false, allowDestructive = false, applyPermissions = false } = opts;
69789
70193
  const prefix = namespacePrefix(app);
69790
- const report2 = {
70194
+ const report3 = {
69791
70195
  ok: true,
69792
70196
  app,
69793
70197
  namespace: prefix,
@@ -69809,19 +70213,19 @@ async function createCms(opts) {
69809
70213
  for (const spec of cms?.collections ?? []) {
69810
70214
  const id = physicalId(spec, app);
69811
70215
  if (spec.wixId) {
69812
- report2.skipped.push({
70216
+ report3.skipped.push({
69813
70217
  collection: id,
69814
70218
  reason: remoteById.has(id) ? "adopted (read-only) \u2014 himi never changes the schema of a collection it does not own" : `adopted but absent from this site \u2014 create it in the Wix dashboard, or declare it as owned ("${prefix}\u2026")`
69815
70219
  });
69816
- if (!remoteById.has(id)) report2.ok = false;
70220
+ if (!remoteById.has(id)) report3.ok = false;
69817
70221
  continue;
69818
70222
  }
69819
70223
  if (!spec.id?.startsWith(prefix)) {
69820
- report2.refused.push({
70224
+ report3.refused.push({
69821
70225
  collection: id || "<unnamed>",
69822
70226
  reason: `"${id}" is outside this app's namespace "${prefix}" \u2014 himi only writes collections it owns. Rename it in config.ts, or bind an existing collection read-only with \`wixId\`.`
69823
70227
  });
69824
- report2.ok = false;
70228
+ report3.ok = false;
69825
70229
  continue;
69826
70230
  }
69827
70231
  const existing = remoteById.get(id);
@@ -69829,35 +70233,35 @@ async function createCms(opts) {
69829
70233
  if (!dryRun) {
69830
70234
  await call("POST", "/data/v2/collections", { collection: toWixCollection(spec, app) });
69831
70235
  }
69832
- report2.created.push(id);
70236
+ report3.created.push(id);
69833
70237
  continue;
69834
70238
  }
69835
70239
  const permDrift = diffPermissions(spec, existing);
69836
- for (const d of permDrift) report2.permissionDrift.push({ collection: id, ...d });
70240
+ for (const d of permDrift) report3.permissionDrift.push({ collection: id, ...d });
69837
70241
  const applyingPermissions = applyPermissions && permDrift.length > 0;
69838
70242
  const drift = diffFields(spec, existing);
69839
70243
  const extras = drift.filter((d) => d.kind === "extra-field");
69840
70244
  const mismatches = drift.filter((d) => d.kind === "type-mismatch");
69841
70245
  const missing = drift.filter((d) => d.kind === "missing-field");
69842
70246
  for (const m of mismatches) {
69843
- report2.skipped.push({ collection: id, reason: m.detail });
69844
- report2.ok = false;
70247
+ report3.skipped.push({ collection: id, reason: m.detail });
70248
+ report3.ok = false;
69845
70249
  }
69846
70250
  for (const e of extras) {
69847
70251
  if (allowDestructive) continue;
69848
- report2.skipped.push({ collection: id, reason: e.detail });
70252
+ report3.skipped.push({ collection: id, reason: e.detail });
69849
70253
  }
69850
70254
  if (mismatches.length > 0) continue;
69851
70255
  if (missing.length === 0 && (extras.length === 0 || !allowDestructive) && !applyingPermissions) {
69852
- report2.unchanged.push(id);
70256
+ report3.unchanged.push(id);
69853
70257
  continue;
69854
70258
  }
69855
70259
  if (!allowDestructive && !Array.isArray(existing.fields)) {
69856
- report2.skipped.push({
70260
+ report3.skipped.push({
69857
70261
  collection: id,
69858
70262
  reason: "the site returned this collection without a `fields` array, so an update would drop every field (and its data) by omission. Re-run to retry the read; pass --allow-destructive only if narrowing to the declaration is genuinely what you want."
69859
70263
  });
69860
- report2.ok = false;
70264
+ report3.ok = false;
69861
70265
  continue;
69862
70266
  }
69863
70267
  if (applyingPermissions) {
@@ -69894,14 +70298,14 @@ async function createCms(opts) {
69894
70298
  }
69895
70299
  }
69896
70300
  }
69897
- report2.updated.push(id);
70301
+ report3.updated.push(id);
69898
70302
  }
69899
- return report2;
70303
+ return report3;
69900
70304
  }
69901
70305
  async function seedCms(opts) {
69902
70306
  const { app, cms, call, dryRun = false, force = false, only, dir } = opts;
69903
70307
  const prefix = namespacePrefix(app);
69904
- const report2 = { ok: true, app, inserted: {}, sources: {}, skipped: [], refused: [], dryRun };
70308
+ const report3 = { ok: true, app, inserted: {}, sources: {}, skipped: [], refused: [], dryRun };
69905
70309
  const fileRows = dir ? readSeedFile(dir) : {};
69906
70310
  const rowsFor = (id, spec) => fileRows[id]?.length ? { rows: fileRows[id], source: "cms-seed.json" } : { rows: spec.seed ?? [], source: "config.ts" };
69907
70311
  const remoteIds = /* @__PURE__ */ new Set();
@@ -69918,30 +70322,30 @@ async function seedCms(opts) {
69918
70322
  const { rows, source } = rowsFor(id, spec);
69919
70323
  considered++;
69920
70324
  if (!rows.length) {
69921
- report2.skipped.push({
70325
+ report3.skipped.push({
69922
70326
  collection: id,
69923
70327
  reason: `no seed rows \u2014 add a \`seed\` array to this collection in config.ts, or run \`himi cms seed --ai\` and refine ${dir ? "cms-seed.json" : "the generated cms-seed.json"}`
69924
70328
  });
69925
70329
  continue;
69926
70330
  }
69927
70331
  if (!spec.id?.startsWith(prefix)) {
69928
- report2.refused.push({ collection: id, reason: `"${id}" is outside this app's namespace "${prefix}" \u2014 himi only seeds collections it owns.` });
69929
- report2.ok = false;
70332
+ report3.refused.push({ collection: id, reason: `"${id}" is outside this app's namespace "${prefix}" \u2014 himi only seeds collections it owns.` });
70333
+ report3.ok = false;
69930
70334
  continue;
69931
70335
  }
69932
70336
  if (!remoteIds.has(id)) {
69933
- report2.skipped.push({ collection: id, reason: `not on the site yet \u2014 run \`himi cms create\` first` });
69934
- report2.ok = false;
70337
+ report3.skipped.push({ collection: id, reason: `not on the site yet \u2014 run \`himi cms create\` first` });
70338
+ report3.ok = false;
69935
70339
  continue;
69936
70340
  }
69937
70341
  const count2 = await countItems(call, id);
69938
70342
  if (count2 === null && !force) {
69939
- 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." });
69940
- report2.ok = false;
70343
+ report3.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." });
70344
+ report3.ok = false;
69941
70345
  continue;
69942
70346
  }
69943
70347
  if (count2 !== null && count2 > 0 && !force) {
69944
- 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` });
70348
+ report3.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` });
69945
70349
  continue;
69946
70350
  }
69947
70351
  if (!dryRun) {
@@ -69953,7 +70357,7 @@ async function seedCms(opts) {
69953
70357
  });
69954
70358
  const droppedKeys = [...new Set(rows.flatMap((r) => Object.keys(r).filter((k) => !declaredKeys.has(k))))];
69955
70359
  if (droppedKeys.length > 0) {
69956
- report2.skipped.push({
70360
+ report3.skipped.push({
69957
70361
  collection: id,
69958
70362
  reason: `ignored undeclared key(s) in seed rows: ${droppedKeys.join(", ")} \u2014 only declared fields are written (a row cannot set system keys such as _id or _owner)`
69959
70363
  });
@@ -69969,18 +70373,18 @@ async function seedCms(opts) {
69969
70373
  )
69970
70374
  );
69971
70375
  }
69972
- report2.inserted[id] = rows.length;
69973
- report2.sources[id] = source;
70376
+ report3.inserted[id] = rows.length;
70377
+ report3.sources[id] = source;
69974
70378
  }
69975
70379
  if (considered === 0) {
69976
70380
  const adopted = (cms?.collections ?? []).map((spec) => ({ spec, id: physicalId(spec, app) })).filter(({ spec, id }) => !!spec.wixId && (!only || id === only));
69977
- report2.skipped.push({
70381
+ report3.skipped.push({
69978
70382
  collection: only ?? "<none>",
69979
70383
  reason: adopted.length ? `the selected collection${adopted.length === 1 ? " is" : "s are"} adopted and read-only \u2014 himi never seeds ${adopted.map(({ id }) => `"${id}"`).join(", ")}. Add rows in Wix CMS, or declare an owned collection with an id beginning "${prefix}".` : only ? `--only "${only}" matched no owned collection declared by this app` : "this app declares no owned CMS collections to seed"
69980
70384
  });
69981
- report2.ok = false;
70385
+ report3.ok = false;
69982
70386
  }
69983
- return report2;
70387
+ return report3;
69984
70388
  }
69985
70389
  function readSeedFile(dir) {
69986
70390
  const file = join8(dir, "cms-seed.json");
@@ -70281,57 +70685,57 @@ function summarizeLint(app, bres, outcome, strict, extraIssues = []) {
70281
70685
  }
70282
70686
  };
70283
70687
  const record2 = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : {};
70284
- const text6 = (value) => typeof value === "string" && value ? value : void 0;
70688
+ const text8 = (value) => typeof value === "string" && value ? value : void 0;
70285
70689
  const message = (value) => {
70286
70690
  const item = record2(value);
70287
- if (text6(item.message)) return text6(item.message);
70288
- if (text6(item.error)) return text6(item.error);
70691
+ if (text8(item.message)) return text8(item.message);
70692
+ if (text8(item.error)) return text8(item.error);
70289
70693
  if (Array.isArray(item.errors)) {
70290
70694
  const diagnostics = item.errors.map((entry) => {
70291
70695
  const diagnostic = record2(entry);
70292
- return text6(diagnostic.text) ?? text6(diagnostic.message);
70696
+ return text8(diagnostic.text) ?? text8(diagnostic.message);
70293
70697
  }).filter((entry) => !!entry);
70294
70698
  if (diagnostics.length) return diagnostics.join("; ");
70295
70699
  }
70296
70700
  return typeof value === "string" ? value : JSON.stringify(value);
70297
70701
  };
70298
- const report2 = foldLegacyReport(legacy, {
70702
+ const report3 = foldLegacyReport(legacy, {
70299
70703
  source: "validate",
70300
70704
  evidence: { runId: `validate:${outcome.manifest.releaseId}`, ts: (/* @__PURE__ */ new Date()).toISOString() },
70301
70705
  findings: [
70302
70706
  ...issues,
70303
70707
  ...bres.compileErrors.map((value) => ({
70304
70708
  pass: "compile",
70305
- path: text6(record2(value).bundle),
70709
+ path: text8(record2(value).bundle),
70306
70710
  ruleCode: "compile-error",
70307
70711
  severity: "error",
70308
70712
  message: message(value)
70309
70713
  })),
70310
70714
  ...outcome.failedToBuild.map((value) => ({
70311
70715
  pass: "build",
70312
- screen: text6(record2(value).screenId),
70716
+ screen: text8(record2(value).screenId),
70313
70717
  ruleCode: "failed-to-build",
70314
70718
  severity: "error",
70315
70719
  message: message(value)
70316
70720
  })),
70317
70721
  ...outcome.missingBundles.map((value) => ({
70318
70722
  pass: "build",
70319
- screen: text6(record2(value).screen),
70320
- path: text6(record2(value).bundle),
70723
+ screen: text8(record2(value).screen),
70724
+ path: text8(record2(value).bundle),
70321
70725
  ruleCode: "missing-bundle",
70322
70726
  severity: "error",
70323
70727
  message: message(value)
70324
70728
  })),
70325
70729
  ...workerThrows.map((value) => ({
70326
70730
  pass: "worker",
70327
- path: text6(record2(value).bundle),
70731
+ path: text8(record2(value).bundle),
70328
70732
  ruleCode: "worker-threw",
70329
70733
  severity: "error",
70330
70734
  message: message(value.threw)
70331
70735
  }))
70332
70736
  ]
70333
70737
  });
70334
- return { report: report2, hasErrors };
70738
+ return { report: report3, hasErrors };
70335
70739
  }
70336
70740
 
70337
70741
  // src/cms-lint.ts
@@ -70455,8 +70859,8 @@ function surfaceIssues(dir, surfaceNav, strict) {
70455
70859
  if (requested.length) {
70456
70860
  const { rejected } = resolveSurfaceEnvelope(platforms, requested);
70457
70861
  for (const r of rejected) {
70458
- const report2 = surfaceById(r.id) ? warn : error;
70459
- report2(`navigation.surfaces: ${r.reason} \u2014 the server will drop this key (narrow-only rule, RFC 0051 \xA73.6)`);
70862
+ const report3 = surfaceById(r.id) ? warn : error;
70863
+ report3(`navigation.surfaces: ${r.reason} \u2014 the server will drop this key (narrow-only rule, RFC 0051 \xA73.6)`);
70460
70864
  }
70461
70865
  }
70462
70866
  return issues;
@@ -70603,18 +71007,18 @@ function walkSource(dir, root = dir) {
70603
71007
  const ext = entry.name.slice(entry.name.lastIndexOf("."));
70604
71008
  if (!SOURCE_EXTENSIONS.has(ext)) continue;
70605
71009
  const absolute = join25(dir, entry.name);
70606
- const text6 = readText2(absolute);
70607
- if (text6 !== null) found.push({ path: relative4(root, absolute), text: text6 });
71010
+ const text8 = readText2(absolute);
71011
+ if (text8 !== null) found.push({ path: relative4(root, absolute), text: text8 });
70608
71012
  }
70609
71013
  return found;
70610
71014
  }
70611
- function blankMatrixRows(text6) {
70612
- const acceptance = mobileUxSection(mobileUxDocumentBody(text6), "Implementation acceptance") ?? "";
71015
+ function blankMatrixRows(text8) {
71016
+ const acceptance = mobileUxSection(mobileUxDocumentBody(text8), "Implementation acceptance") ?? "";
70613
71017
  return /\|\s*(?:Ready|Empty|Add\s*\/\s*edit|Saved\s*\/\s*completed|Error\s*\/\s*recovery)\s*\|\s*\|\s*\|\s*\|\s*\|/i.test(acceptance);
70614
71018
  }
70615
71019
  function usesAppNetwork(source) {
70616
- const code = (text6) => text6.replace(/\/\*[\s\S]*?\*\//g, "").replace(/\/\/[^\n]*/g, "");
70617
- return source.some(({ text: text6 }) => /\bwixApi\b|(?:from|import)\s*["']@(?:himalaya|wix\/himalaya)\/wix-[^"']+["']/.test(code(text6)));
71020
+ const code = (text8) => text8.replace(/\/\*[\s\S]*?\*\//g, "").replace(/\/\/[^\n]*/g, "");
71021
+ return source.some(({ text: text8 }) => /\bwixApi\b|(?:from|import)\s*["']@(?:himalaya|wix\/himalaya)\/wix-[^"']+["']/.test(code(text8)));
70618
71022
  }
70619
71023
  function auditUx(content, strict = false) {
70620
71024
  const dir = resolve16(content);
@@ -70782,7 +71186,8 @@ var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
70782
71186
  "brief",
70783
71187
  "no-agent",
70784
71188
  "no-first-look",
70785
- "no-image"
71189
+ "no-image",
71190
+ "no-watch"
70786
71191
  ]);
70787
71192
  var SHELL_ENUM = SHELL_NAMES2.join("|");
70788
71193
  var SWITCH_FLAGS = new Set([...BOOLEAN_FLAGS].filter((name) => name !== "deep"));
@@ -70879,7 +71284,7 @@ var USAGE = `himi \u2014 the Himalaya authoring CLI (remote dev loop)
70879
71284
  create/store or inspect CAPTCHA keys (not login tokens).
70880
71285
  Setup needs final --ios-bundle-id / --android-package-name.
70881
71286
 
70882
- himi init <name> [--dir <parent>] [--template <t>] [--remote] [--push-setup | --no-push-setup] [--no-agent] [--no-first-look]
71287
+ himi init <name> [--dir <parent>] [--template <t>] [--remote] [--push-setup | --no-push-setup] [--no-agent] [--no-first-look] [--no-watch]
70883
71288
  scaffold a starter content package (--template <t> starts
70884
71289
  from a reference app; --remote fetches it off the serve plane),
70885
71290
  with the agent files inside it (AGENTS.md, CLAUDE.md, the himalaya
@@ -70893,7 +71298,11 @@ var USAGE = `himi \u2014 the Himalaya authoring CLI (remote dev loop)
70893
71298
  first; doNowAgain is the last key, for a reader of the tail) and
70894
71299
  next[] with THIS session's steps: the consent preflight, then
70895
71300
  the live preview (and the picture's steps when init could not
70896
- make it)
71301
+ make it). For a bundled template it also STARTS the live
71302
+ preview itself (\`himi run --watch\`, detached, --no-open, log
71303
+ .himi/watch.log, stops after 30 idle minutes) and reports
71304
+ watch: {started, pid, url, board, log} \u2014 or {skipped} for a
71305
+ --remote template, --no-watch, or a CI environment
70897
71306
  himi template list [--remote] list starter templates + metadata (title/description/tags) + each one's start prompt
70898
71307
  himi template prompt <name> print the prompt that starts an app from that template
70899
71308
  himi template publish [--dir <app>] [--source-dir <dir>] [--name <n>] [--icon <png>] [--visibility public|unlisted]
@@ -70938,7 +71347,9 @@ var USAGE = `himi \u2014 the Himalaya authoring CLI (remote dev loop)
70938
71347
  changed, which journey step, and the image that shows it (copied
70939
71348
  into .himi/history/ so later renders cannot replace it). With no
70940
71349
  --image the current home shot is attached (never a stale one;
70941
- --no-image opts out; imageAttached says which). Bare
71350
+ --no-image opts out; imageAttached says which; on the
71351
+ --step review handover card it attaches even as a repeat,
71352
+ and the output names the making-of to hand over). Bare
70942
71353
  prints the journal, the current step and the elapsed time.
70943
71354
  himi ux audit [--content <dir>] [--strict] [--json]
70944
71355
  inspect the mobile UX contract, template placeholders, app fixtures,
@@ -71159,14 +71570,16 @@ var USAGE = `himi \u2014 the Himalaya authoring CLI (remote dev loop)
71159
71570
  himi mcp [plugin --out <dir>] the himi tools as a local MCP server over stdio, for coding
71160
71571
  agents (Claude Code, Cursor, Codex, VS Code). \`himi init\`
71161
71572
  configures each; see \`himi docs himi-mcp\`.
71162
- himi run [<package.himi|dir|url>] [--browser] [--watch] [--lan] [--port <n>] [--chrome preview|app] [--screen <id>] [--surface <id>] [--no-open]
71573
+ himi run [<package.himi|dir|url>] [--browser] [--watch [--idle-exit <min>]] [--lan] [--port <n>] [--chrome preview|app] [--screen <id>] [--surface <id>] [--no-open]
71163
71574
  serve a package on localhost + open it in a browser. No serve
71164
71575
  plane, no dev-server. With no argument it packs the current
71165
71576
  content package first \u2014 edit, run, look at it.
71166
71577
  --watch rebuilds on every save and repaints the open tab
71167
71578
  (a failed build keeps the last good preview). The build board
71168
71579
  is served beside it at /__himi/studio/ with the preview framed
71169
- in it. Blocks until Ctrl-C.
71580
+ in it. Blocks until Ctrl-C (or, with --idle-exit <min>, until
71581
+ that long passes with no source save \u2014 the watch \`himi init\`
71582
+ starts passes 30).
71170
71583
  --lan also serves it to the local network and prints a QR:
71171
71584
  a phone on the same Wi-Fi opens it as the real app, full
71172
71585
  screen. Read-only, but anyone on that network can open it.
@@ -71319,6 +71732,13 @@ var USAGE = `himi \u2014 the Himalaya authoring CLI (remote dev loop)
71319
71732
  Requires Himi owner login plus Wix CLI login for site authorization.
71320
71733
  himi notify status --content <dir> [--site <msid>] [--refresh] [--json]
71321
71734
  Reads server setup status. --refresh checks Branded/Ping live; it does not send push.
71735
+ himi notify send --provider ping --target wix --app <a> --site <msid> --member-email <email>
71736
+ --title <s> [--body <s>] --link <scheme>://screen/<id>[?\u2026] [--request-id <uuid>] [--dry-run] [--json]
71737
+ Hosted test notification to ONE site member's devices: Himi Serve sends it through Ping
71738
+ as Himalaya, so no Apple/Firebase keys or Fire Console. Needs himi login --browser and
71739
+ wix login as the site owner, and a ready notify setup. Acceptance is not delivery.
71740
+ --dry-run checks ownership, member, link and readiness without sending. After an unknown
71741
+ outcome, re-run the same command with the printed --request-id; Ping never runs it twice.
71322
71742
  himi notify send --app <a> (--token <t> | --device <id> | --all) --title <s> [--body <s>] [--link <url>]
71323
71743
  deliver a REAL push notification to registered devices \u2014
71324
71744
  APNs (.p8) for iOS, FCM v1 for Android. --all fans out to
@@ -71493,7 +71913,7 @@ WHAT A PATCH DOES NOT CARRY \u2014 all reported in the output, never silent
71493
71913
  himi docs releasing-with-himi-serve`;
71494
71914
  var COMMANDS = [
71495
71915
  { name: "captcha", summary: "create and store native CAPTCHA keys in Serve, or inspect setup status; customer setup verifies Wix site ownership; does not enable member CAPTCHA SDKs", required: "setup|status --app <app> [--content <dir>] [--site <msid>] [--ios-bundle-id <id>] [--android-package-name <id>] [--target <t>]" },
71496
- { name: "init", summary: "scaffold a starter content package (OTA-only; no native shells). --template <t> starts from a full reference app (branded-lite = consumer storefront over a Wix site; owner-lite = owner CRUD/management) instead of the minimal counter; --templates lists bundled ones; --remote fetches the template off the serve plane (falls back to the bundled copy offline). The new app also gets its agent files (the same set `himi agent-init` writes: AGENTS.md, CLAUDE.md, the himalaya skill, HIMALAYA.md, a SessionStart hook) so the next session started inside it picks the work up; --no-agent skips them. It also makes the build board (.himi/studio/, exactly as `himi studio --no-open` does \u2014 it never opens a browser), so every later render, test, validate, push and progress entry refreshes it; and, for a template bundled with this CLI, it makes the FIRST PICTURE itself (never for a --remote one: that is its publisher's code, and a render runs it on this machine, so next[] leaves the render to the agent after it has read the code): exactly `himi render <homeScreenId> --png --device iphone` (offline \u2014 no request; never --live), run as a child it kills after a time budget, writing .himi/shots/<home>.png and journaling it (by: himi) as the STARTING POINT (\"Starting point: the <template> template, before any of your changes\") so the board and the journal hold a picture when init returns. init also records the source it wrote (.himi/init-source.json: a content fingerprint, Markdown left out), and the first home PNG render after the source diverges from it is journaled once as \"First look at your app\" \u2014 reported as `firstLook: {image}` or `{skipped: <reason>}`, never a failed init; `--no-first-look` skips it (for scripts and CI that render themselves). The envelope LEADS with `doNow`: three lines to run now (the consent preflight, `himi run --watch --content <dir> &` with the board's path, and the postcard line), and ENDS with the same lines as `doNowAgain`, so an agent that reads only the head or only the tail (`| tail`) gets them; at a terminal they are also printed as plain text on stderr, before the JSON and again after it. Its `next[]` LEADS with the steps for THIS session, in this order for every app \u2014 the offline first render and that picture's `himi progress` postcard (only when init could not make the picture), the skill's consent preflight (authorize or mock any Wix or external startup request; with none, go straight on), and only then `himi run --watch &` \u2014 ahead of the next-session guidance", required: "<name> [--dir] [--template <t>] [--remote] [--no-agent] [--no-first-look] | --templates" },
71916
+ { name: "init", summary: "scaffold a starter content package (OTA-only; no native shells). --template <t> starts from a full reference app (branded-lite = consumer storefront over a Wix site; owner-lite = owner CRUD/management) instead of the minimal counter; --templates lists bundled ones; --remote fetches the template off the serve plane (falls back to the bundled copy offline). The new app also gets its agent files (the same set `himi agent-init` writes: AGENTS.md, CLAUDE.md, the himalaya skill, HIMALAYA.md, a SessionStart hook) so the next session started inside it picks the work up; --no-agent skips them. It also makes the build board (.himi/studio/, exactly as `himi studio --no-open` does \u2014 it never opens a browser), so every later render, test, validate, push and progress entry refreshes it; and, for a template bundled with this CLI, it makes the FIRST PICTURE itself (never for a --remote one: that is its publisher's code, and a render runs it on this machine, so next[] leaves the render to the agent after it has read the code): exactly `himi render <homeScreenId> --png --device iphone` (offline \u2014 no request; never --live), run as a child it kills after a time budget, writing .himi/shots/<home>.png and journaling it (by: himi) as the STARTING POINT (\"Starting point: the <template> template, before any of your changes\") so the board and the journal hold a picture when init returns. init also records the source it wrote (.himi/init-source.json: a content fingerprint, Markdown left out), and the first home PNG render after the source diverges from it is journaled once as \"First look at your app\" \u2014 reported as `firstLook: {image}` or `{skipped: <reason>}`, never a failed init; `--no-first-look` skips it (for scripts and CI that render themselves). For a bundled template (never a --remote one \u2014 the preview would build and serve its publisher's code) init also STARTS the live preview itself: `himi run --watch --content <dir> --port <p> --no-open --idle-exit 30`, detached in its own process group with its output in .himi/watch.log, never waited for (the url is chosen before it builds), recorded in .himi/watch.json, and reported as `watch: {started, pid, url, board, log}` \u2014 or `{skipped: <reason>}` for `--no-watch`, a `CI` environment, a test runner, a watch already running, or a start that failed (never a failed init). The envelope LEADS with `doNow`: three lines to run now (the consent preflight, the live preview \u2014 its url and board when init started it, else `himi run --watch --content <dir> &` with the board's path \u2014 and the postcard line), and ENDS with the same lines as `doNowAgain`, so an agent that reads only the head or only the tail (`| tail`) gets them; at a terminal they are also printed as plain text on stderr, before the JSON and again after it. Its `next[]` LEADS with the steps for THIS session, in this order for every app \u2014 the offline first render and that picture's `himi progress` postcard (only when init could not make the picture), the skill's consent preflight (authorize or mock any Wix or external startup request; with none, go straight on), and only then the live preview (the running one's url, or `himi run --watch &` when init did not start it) \u2014 ahead of the next-session guidance", required: "<name> [--dir] [--template <t>] [--remote] [--no-agent] [--no-first-look] [--no-watch] | --templates" },
71497
71917
  { name: "template", summary: "starter-template catalog + publishing. Every template is a SHOWCASE APP \u2014 a real published release you can open running before you start from it. `list` prints available templates with metadata (title/description/tags) plus each one's start prompt \u2014 pass --remote to include the serve-plane catalog so an agent can pick one; `prompt <name>` prints just that sentence for one template (the skill URL is the public door, or the plane named by --target/--serve-url); `publish` publishes an app AS a `himi init --template` (needs a himalaya.template.json; builds a previewable release + attaches the scaffold source; --source-dir overrides which tree becomes the source, --name sets the template name; bakes a launcher icon \u2014 --icon <png>, or the committed template-icons/<name>.png \u2014 and extracts accent/palette gallery meta from the scaffold's tokens.json; --visibility unlisted keeps it out of the /v1/templates catalog \u2014 templates default to public); `trim --keep <ids>` cuts an app scaffolded from a template down to those screens (the home, and any screen the navigation pins such as a drawer body, always stays) \u2014 it deletes the other SCREENS/TEMPLATED entries whole, prunes navigation (linkableScreens, tabBar, drawer, deep links, surfaces) and drops tier5-src workers no kept screen uses, then runs validate --strict and rolls everything back if it fails (--force keeps it, --dry-run writes nothing). A kept screen that links to a removed one is refused unless --links follow (keep those too) or --links drop (remove the taps)", required: "list [--remote] | prompt <name> | publish [--dir <app>] [--source-dir <dir>] [--name <n>] [--icon <png>] [--visibility public|unlisted] [--target <t>] | trim --keep <id,id,\u2026> [--content <dir>] [--links follow|drop] [--dry-run] [--force]" },
71498
71918
  { name: "build", summary: "compile tier5-src/<name>/index.ts -> dist/bundles/<name>.bundle.js with esbuild (--binary = resolve published @himalaya/*); semantic TypeScript checking belongs to himi validate", required: "[--content <dir>] [--binary]" },
71499
71919
  { name: "functions", summary: "inspect/invoke server functions and operate durable jobs or schedules", required: "list|describe|openapi|invoke | jobs list|get|cancel|retry|dlq | schedule describe|invoke [--app <a>] [--environment preview|production] [--release <id>]" },
@@ -71501,7 +71921,7 @@ var COMMANDS = [
71501
71921
  { name: "validate", summary: "lint the content package (no push): fixed-contract TypeScript checking for tier5-src workers + schema/proto + token refs + component allowlist + $service bindings + strict-enum foot-guns + capability decls + worker boot dry-run + build/compile failures. Type issues are advisory warnings normally; --strict promotes them and hygiene warnings to errors. Exit 1 on any error. Off-repo, the CLI supplies its compiler and SDK declarations.", required: "[--content <dir>] [--strict]" },
71502
71922
  { name: "test", summary: "DERIVED behavioral crawl \u2014 no test files to write. Boots each screen's real worker, replays its onAppear, then dispatches every action site the descriptor already declares (each from a fresh boot) and checks a narrow set of unambiguous invariants: no uncaught throw, never stuck on `loading`, no `$service:` token or NaN/undefined/[object Object] reaching visible text, no binding aimed at state the worker never defines, no nav param silently dropped, no dead nav target. Catches what validate/render cannot \u2014 a WRONG REDUCER passes both with ok:true. Runs the worker in a DEVICE-PARITY sandbox (no setTimeout/setInterval, as on JSC/WebView) so a bundle that would die on device fails here. Crawls on HERMETIC DATA by default \u2014 zero network: the app's own dev/net-mocks.json first (--net <file> overrides), then the config's CMS `seed:` rows, then a canned fixture of frozen real vertical responses shipped with the CLI (exercises code paths, not API-contract truth) \u2014 so the success/mapping path runs, not just the failure UI. Two boot-only HOSTILE passes (every list empty; every request 500) then check the failure/empty day \u2014 corpus-proven, so their findings are errors. NAV EDGES are followed too: every himi.open site ($item params resolved against row 0 of its list, $service against worker state) and worker _nav directive is harvested, and each baked target is booted WITH the params the source sends \u2014 detail screens finally run their content arm; edge findings are errors tagged edge:<source>. --native runs both real native decoders over the exact baked JSON bytes; --native ios or --native android selects one. It is explicit, runs only from a Himalaya SOURCE CHECKOUT (the decoder oracles are that tree's own HimalayaCore Swift package and Gradle harness, which a published install does not ship), and needs the selected local toolchain. From a source checkout, default runs name missing native verdicts in the coverage ledger; elsewhere the ledger stays silent about them rather than offering a remedy you cannot run. --offline strips all data layers (the old reject-all axis); --no-hostile skips the hostile passes. --record (EXPLICIT, never automatic) refreshes dev/net-mocks.json from live reads first: it boots each screen (load + root onAppear ONLY \u2014 nothing the app's own launch would not fire; the tap/action crawl never sees the live transport, and only proven reads \u2014 GET, or read-shaped POST (/query, /search, /list*, \u2026) \u2014 ever reach the wire), freezes the round-trips, then crawls hermetically on the fresh fixture; record against a QA site, not production. Nodes it cannot judge headless are named, never silently passed. Exit 1 on any error. The CLI carries the authoring runtime off-repo; no per-app SDK install is required. --browser runs the whole loop (build \u2192 extract \u2192 validate \u2192 render \u2192 test) in YOUR browser instead of here, so the source never reaches a pod: it opens one screen's worker + descriptor in a tab, blocks on a loopback callback, and prints the report it posts back. One screen per tab \u2014 name it. --no-open prints the URL rather than launching a browser (SSH); --timeout <ms> bounds the wait, which matters because the page has no execution deadline of its own and an infinite loop in the worker hangs the tab.", required: "[<screenId>\u2026] [--content <dir>] [--net <file>] [--budget <ms>] [--deep [n]] [--record] [--native [ios|android]] [--no-shots] [--offline] [--no-hostile] [--all-screens] [--browser] [--no-open] [--timeout <ms>] [--json]" },
71503
71923
  { name: "ux", summary: "audit or record mobile UX evidence. `audit` checks the public-safe contract, seeded blank acceptance rows, placeholder source copy, app-specific mocks, and iOS/Android audit records. It warns while existing templates migrate; `--strict` promotes missing evidence. `record` stores checks performed on an installed iOS/Android build: `--checks` is required for passed|failed and empty for unavailable, which drove nothing and needs a `--reason` instead. `record` also STAMPS the release it audited (it resolves the id from a build, the same one a push would publish), because an unforced `himi push` of a release with screens refuses when no drive is recorded for THAT release, or when the one that is recorded says `failed` \u2014 the artifact stopped being advice nobody reads (#2955). `unavailable` still satisfies it; only `failed` refuses.", required: "audit [--content <dir>] [--strict] [--json] | record --content <dir> --platform ios|android --status passed|failed|unavailable [--checks back,keyboard,...] [--reason <text>]" },
71504
- { name: "progress", summary: 'the build JOURNAL an agent keeps for the owner while it works. `himi progress "<what changed>" --step <1-8|name> --image <png>` appends one postcard to .himi/progress.jsonl \u2014 the step is one of the customer journey\'s eight (discover, classify, plan, implement, brand, validate, review, download) \u2014 and COPIES each image, content-addressed, into .himi/history/, because .himi/shots/ is overwritten by every render and cleared by every full `himi test`, so a path into it would later show a different picture than the one the owner was sent. --image repeats. With NO --image, the home screen\'s current shot is attached automatically, only when it is no older than the last source change and not the picture the latest journaled postcard already shows; `imageAttached` in the output says whether the postcard carries a picture (with `imageNote` saying why not), and `--no-image` sends a text-only postcard. --notify also raises a best-effort desktop notification for an owner who stepped away. A bare `himi progress` prints the journal with the current step and elapsed time \u2014 also how an agent recovers its place after its context was compacted. APPROVALS: `--approve <gate> "<what>"` records one the customer just gave (gates: site-create, connect, private-data, draft, promote, push-signing, qa-build) in .himi/approvals.jsonl, and `--done <gate>` / `--drop <gate>` closes it \u2014 an approval is owed until then, and the chat that held it does not survive a new session. `--brief` prints plain text for a session start: the current step, the last postcard, every open approval, and whether this app\'s agent files are stale \u2014 the SessionStart hook `himi agent-init` installs runs exactly this. The CLI also journals milestones by itself \u2014 the first PNG render of the home screen (for an app a current `himi init` started: once as the template\'s "Starting point" while the source is what init wrote, then once as "First look at your app" after it diverges, Markdown edits not counting; once per app otherwise), a `himi test` crawl whose outcome changed since the last one (its counts or its home picture; an identical re-run writes none, and a repeat names what changed: "The home screen changed since the last crawl." or the changed counts, was \u2192 now), every draft push (with the home picture only when that shot is newer than the last source edit \u2014 otherwise the postcard says why and names `himi render <home> --png`) \u2014 marked `"by": "himi"`; they never move the journey step, carry no link or credential, and step aside (folded out of every reader) when the agent journals the same step or picture within five minutes. Local only: nothing is sent anywhere.', required: '["<message>" [--step <1-8|name>] [--image <png>]\u2026 [--no-image] [--notify]] [--content <dir>] | --approve|--done|--drop <gate> ["<note>"] | --brief' },
71924
+ { name: "progress", summary: 'the build JOURNAL an agent keeps for the owner while it works. `himi progress "<what changed>" --step <1-8|name> --image <png>` appends one postcard to .himi/progress.jsonl \u2014 the step is one of the customer journey\'s eight (discover, classify, plan, implement, brand, validate, review, download) \u2014 and COPIES each image, content-addressed, into .himi/history/, because .himi/shots/ is overwritten by every render and cleared by every full `himi test`, so a path into it would later show a different picture than the one the owner was sent. --image repeats. With NO --image, the home screen\'s current shot is attached automatically, only when it is no older than the last source change and not the picture the latest journaled postcard already shows \u2014 except on the HANDOVER card (`--step review`), where a repeat is the point and it attaches anyway; that card\'s output also names the making-of (`makingOf`, .himi/making-of.html, rebuilt when missing or older than the journal so it ends on this card) and its `next` says to hand it to the owner beside the preview link; `imageAttached` in the output says whether the postcard carries a picture (with `imageNote` saying why not), and `--no-image` sends a text-only postcard. --notify also raises a best-effort desktop notification for an owner who stepped away. A bare `himi progress` prints the journal with the current step and elapsed time \u2014 also how an agent recovers its place after its context was compacted. APPROVALS: `--approve <gate> "<what>"` records one the customer just gave (gates: site-create, connect, private-data, draft, promote, push-signing, qa-build) in .himi/approvals.jsonl, and `--done <gate>` / `--drop <gate>` closes it \u2014 an approval is owed until then, and the chat that held it does not survive a new session. `--brief` prints plain text for a session start: the current step, the last postcard, every open approval, and whether this app\'s agent files are stale \u2014 the SessionStart hook `himi agent-init` installs runs exactly this. The CLI also journals milestones by itself \u2014 the first PNG render of the home screen (for an app a current `himi init` started: once as the template\'s "Starting point" while the source is what init wrote, then once as "First look at your app" after it diverges, Markdown edits not counting; once per app otherwise), a `himi test` crawl whose outcome changed since the last one (its counts or its home picture; an identical re-run writes none, and a repeat names what changed: "The home screen changed since the last crawl." or the changed counts, was \u2192 now), every draft push (with the home picture only when that shot is newer than the last source edit \u2014 otherwise the postcard says why and names `himi render <home> --png`), and ONE rule for a long silence: a successful home `render --png` more than ten minutes after the newest postcard of any kind, with a picture the latest postcard does not show, journals "Still building: here\'s the home screen now." with it (never twice within the window). When a crawl\'s or a draft\'s home picture is the first of an app whose source diverged, "First look at your app" is journaled first, from that shot, once. All are marked `"by": "himi"`; they never move the journey step, carry no link or credential, and step aside (folded out of every reader) when the agent journals the same step or picture within five minutes. Local only: nothing is sent anywhere.', required: '["<message>" [--step <1-8|name>] [--image <png>]\u2026 [--no-image] [--notify]] [--content <dir>] | --approve|--done|--drop <gate> ["<note>"] | --brief' },
71505
71925
  { name: "studio", summary: "open the BUILD BOARD \u2014 one local page the owner can leave open while an agent builds their app, which fills itself in: every screen as it renders (NEW/CHANGED badges, and each earlier version kept for a filmstrip), the journey stepper and the latest postcard from `himi progress`, the validate/test/UX/draft checks as they run, the site next to the app's home screen with its palette, and the navigation the test crawl followed. Writes .himi/studio/index.html and opens it; the page re-reads its state every two seconds, so nothing needs to be served. OPT-IN BY EXISTENCE: until this command (or `himi init`, which makes the board for every app it creates, without opening a browser) has run, no other command touches .himi/studio/ \u2014 after it, render/test/validate/push/icon/site/ux/progress each refresh the board as they finish (best-effort; never fails a command). Every image is a content-addressed copy in .himi/history/, never a live path into .himi/shots/. Local only, no secrets: it never reads a preview token, handover code or control token. --no-open prints the path instead (SSH, CI). `himi studio reel` writes the MAKING-OF: one self-contained HTML file (every image inlined, nothing fetched) with a flipbook of the home screen version by version, captioned by the postcard in effect at the time, the final screens, and every postcard \u2014 sized to stay attachable, thinning frames first. Defaults to .himi/making-of.html (never archived by push), which every successful `himi push --draft` also rebuilds and names as `reel` in its output; --out to put it elsewhere. The output names that file as the one to hand the owner (`handover: {file, hint}` in JSON; two plain lines at a terminal) \u2014 beside the preview link, never instead of it.", required: "[--content <dir>] [--no-open] | reel [--out <file>] [--no-open]" },
71506
71926
  { name: "render", summary: "headless-render a screen with the web renderer: render-tree + diagnostics, optional --png [<file>] (bare: .himi/shots/<id>.png, what the build board shows), --scheme dark, --tree. Local by default (content dir). Pass --remote --app --release to hit GET /v1/preview/\u2026/render on the serve plane after a push. Boots the worker so $service bindings resolve; --static raw bindings; --live real Wix fetches; --state injects worker state; --input supplies pre-boot navigation params; --width/--device for PNG viewport; --rtl for dir=rtl + $env:isRTL.", required: "<screenId> [--content <dir>] [--scheme dark] [--png [<file>]] [--tree] [--static] [--live] [--state <json|file>] [--input <json|file>] [--width <px> | --device <name>] [--rtl] [--remote --app --release]" },
71507
71927
  { name: "cms", summary: "inspect, provision, and seed the app's CMS (Wix Data) collections, declared as a `cms` block in config.ts. `query` is READ-ONLY \u2014 by default it fetches ONLY the physical ids `config.ts` declares (item counts + schema drift) and reports `siteScanned: false`; it never lists the site, so an UNDECLARED `himi_<app>_` collection this app owns is not reached without `--all`. `--all` SCANS the site \u2014 every collection it lists is labeled `owned`/`adopted`/`foreign`, and it describes every foreign one \u2014 id, non-system field keys + types, and the site's read role \u2014 so an app with no `cms` block at all can DISCOVER a collection to adopt (`--all` is the one case where a missing `cms` block is not 'nothing to do'). Foreign collections are described up to a cap and are never item-counted (`itemCount: null`), because a row count is one query each; `foreign.count` is how many the scan LISTED \u2014 it follows the list endpoint's pages, and `listingIncomplete: true` says when the walk stopped short of the end, with `unlistedCollections` counting what it left out when Wix gave a paging total \u2014 and `foreign.truncated` says how many of those went undescribed, each still NAMED in `foreign.others` (id + displayName), so a collection the site already has is never hidden behind the cap. The scan is OWNER-scoped \u2014 it needs --site <msid> (or WIX_SITE_ID) plus owner auth via `wix token`; a visitor token can read a public collection's ITEMS but can never enumerate collections, so there is no tokenless discovery path. `create` reconciles declared collections additively and idempotently, and REFUSES any id outside the app's `himi_<app>_` namespace, so it is safe against a production site. It reports declared-vs-site `permissionDrift` but never writes permissions unless asked with --apply-permissions, so a field reconcile can't silently revert access control changed in the dashboard. `seed` inserts the git-tracked `seed:` rows into EMPTY owned collections (--force to add to a non-empty one; --ai writes placeholder rows plus a refinement prompt for you to review). Adopted collections (declared by `wixId`) are read-only and never created, updated, or seeded. Site from --site <msid> or WIX_SITE_ID; owner auth via `wix token`.", required: "query|create|seed [--site <msid>] [--content <dir>] [--all] [--dry-run] [--force] [--allow-destructive] [--apply-permissions] [--only <id>] [--ai] [--count <n>]" },
@@ -71510,7 +71930,7 @@ var COMMANDS = [
71510
71930
  { name: "icon", summary: "the app's LAUNCHER icon \u2014 the home-screen / store mark, which is BINARY-floor and never over-the-air (distinct from `himi icons`, the in-screen glyph vocabulary). `status`/`check` report what this app would actually ship and validate it against both stores: exact 1024x1024, no tRNS chunk, non-interlaced, and \u2014 when the optional image toolchain is installed \u2014 no transparent regions (Apple permits an alpha channel but not transparent pixels: ITMS-90717), no pre-masked corners, no letterboxed wordmark; they also print the store-policy checklist no checker can decide. `set <file.png>` installs supplied art, `from-site` installs the logo `himi site analyze` downloaded, and `generate` makes a gradient tile + glyph \u2014 all three trim, flatten onto the app's accent, write the 1024 opaque master plus the 512 Play listing icon (Play wants 32-bit WITH alpha, so it is a separate file), and DECLARE it in himalaya.content.json, without which the release bakes no icon at all", required: "[status|check] | set <file.png> | from-site [--book <dir>] | generate [--glyph <g>] [--accent <hex>] | glyphs [--content <dir>] [--json]" },
71511
71931
  { name: "login", summary: "obtain a scoped serve-control token from Wix identity and store it in ~/.himalaya/serve-targets.json (so push/promote need no hand-pasted token). Three modes: --browser (your own Wix account, no VPN/BO), --wix (HEADLESS \u2014 the machine's own Wix CLI credential; the mode a CI pod or agent container can satisfy), --dpx (Wix employee, reads ~/.dpx/session/token). RFC 0026 WS3.", required: "[--browser|--wix|--dpx] [--target <t>] [--issue-url <url>]" },
71512
71932
  { name: "logout", summary: "remove the stored control token for a target", required: "[--target <t>]" },
71513
- { name: "notify", summary: "`setup` optionally configures Branded/Ping from local customer key files; `status --refresh` reads live backend readiness. Setup may be deferred while building. These use serve-control and require owner plus Wix account authentication. `send`, `tokens`, `doctor`: send a REAL push notification to registered devices (RFC 0036 WS5) \u2014 APNs .p8 over HTTP/2 for iOS, FCM v1 for Android. `send` targets one device (--token / --device <registry id>) or fans out to the whole app (--all), reporting a per-device result; a token the provider says is uninstalled (APNs 410 Unregistered / FCM UNREGISTERED) is auto-disabled, while a merely wrong one (BadDeviceToken \u2014 usually a sandbox/production mismatch) is recorded and left enabled. `tokens` lists the registry. `doctor` reports which credentials resolve and which sender each platform picks. Talks to the admin-server via --admin-url/HIMALAYA_ADMIN_URL, authenticated with --admin-session/HIMALAYA_ADMIN_SESSION. NOT `himi push`, which publishes a release.", required: "setup|status [--content <dir>] [--target <t>] [--refresh] [--yes] [--no-push-setup] | send|tokens|doctor [--app <a>] [--token <t> | --device <id> | --all] [--title <s>] [--body <s>] [--link <url>] [--platform ios|android] [--badge <n>] [--channel <id>] [--silent] [--dry-run] [--json]" },
71933
+ { name: "notify", summary: "`setup` optionally configures Branded/Ping from local customer key files; `status --refresh` reads live backend readiness. Setup may be deferred while building. These use serve-control and require owner plus Wix account authentication. `send`, `tokens`, `doctor`: send a REAL push notification to registered devices (RFC 0036 WS5) \u2014 APNs .p8 over HTTP/2 for iOS, FCM v1 for Android. `send` targets one device (--token / --device <registry id>) or fans out to the whole app (--all), reporting a per-device result; a token the provider says is uninstalled (APNs 410 Unregistered / FCM UNREGISTERED) is auto-disabled, while a merely wrong one (BadDeviceToken \u2014 usually a sandbox/production mismatch) is recorded and left enabled. `tokens` lists the registry. `doctor` reports which credentials resolve and which sender each platform picks. Talks to the admin-server via --admin-url/HIMALAYA_ADMIN_URL, authenticated with --admin-session/HIMALAYA_ADMIN_SESSION. `send --provider ping` is the hosted alternative: the serve plane sends one test notification to one site member through Ping as Himalaya, with owner login instead of provider keys, and reports acceptance, never delivery. NOT `himi push`, which publishes a release.", required: "setup|status [--content <dir>] [--target <t>] [--refresh] [--yes] [--no-push-setup] | send --provider ping [--content <dir>] [--app <a>] [--site <msid>] --member-email <email> --title <s> [--body <s>] --link <url> [--request-id <uuid>] [--dry-run] [--json] | send|tokens|doctor [--app <a>] [--token <t> | --device <id> | --all] [--title <s>] [--body <s>] [--link <url>] [--platform ios|android] [--badge <n>] [--channel <id>] [--silent] [--dry-run] [--json]" },
71514
71934
  { name: "site", summary: "PROVISION a Wix project, then analyze or extend it. `create` is the one door that makes a site instead of adopting one: POST /funnel/projects/v1/create (a WIX site, a HEADLESS backend, a BRANDED_APP, or a VIBE site), and for headless it also mints the OAuth app whose id IS the `WIX_CLIENT_ID` \u2014 with --redirect-uri already registered, because an unregistered redirect makes native signInDirect fail at redirect-session. `--verticals` REQUIRES `--template` and reports only what Wix confirmed: measured, installing onto a project created from the BLANK template returns 200 while the vertical's API answers 428 forever, because there is no site for it to attach to (the install asks for CREATE_SITE). A real template ships its verticals installed and populated, and installing further ones onto it then works. It MUTATES your Wix account and needs --yes; without it you get the payload and nothing is sent. `list` is the read-only site inventory (with each site's premium flag, which is what explains an installed-but-428 vertical). Auth is the account-scoped `wix token` (no --site), falling back to WIX_API_KEY + WIX_ACCOUNT_ID. `client` is the same OAuth step for a site that ALREADY exists \u2014 the one himi adopts rather than makes \u2014 so a clientId no longer means a manual trip to the dashboard: `client ensure` reconciles rather than mints, because a site can already carry a client with NO registered redirect and shipping that one fails signInDirect at runtime; it reuses a client that carries --redirect-uri, PATCHes one that matches --name but lacks it (unioning, so another app's redirect survives), and creates only when there is nothing to reuse (--new forces a fresh one). --name IS the idempotency key. `client` auth is SITE-scoped (`wix token --site <msid>`) because minting is owner-of-that-site, and --yes gates every mutation: a site that already has a usable client resolves WITHOUT it, but --write-provisioning needs --yes even then because it rewrites a manifest on disk (the dry run's planned.provisioning names the file). That query endpoint IGNORES paging (measured: query.paging, cursorPaging and a top-level paging all return the full set at offset 0), so when pagingMetadata reports more clients than came back, `ensure` REFUSES EVERY outcome \u2014 create, patch AND reuse. Reuse is refused too because pickClient breaks ties on the OLDEST createdDate, so an omitted older same-named MOBILE client with the same redirect would win a later complete read and the resolved clientId would MOVE between runs \u2014 and it gets written into provisioning.json; convergence is what makes a stray duplicate untidy rather than harmful. --name cannot narrow the query (no filter is sent; the name is applied to the rows after they arrive), so the recovery is the dashboard; --new skips the query entirely. A `patched` result is verified against what Wix STORED \u2014 the redirect list is read off the PATCH response, and re-read via GET when the response omits it, because falling back to the list himi just sent would make the guard pass vacuously on exactly the silent no-op it exists to catch. `client list` is the read-only inventory of a site's clients: act on its `shippable` column rather than `hasRedirect`, because shippable ALSO requires a MOBILE applicationType and `ensure` refuses a WEB client \u2014 a WEB one carrying your redirect looks right and is not a candidate \u2014 and it sets truncated:true rather than presenting a partial inventory as the whole one. `analyze --read-only` returns API-only generation context as JSON (identity, access probes, CMS permissions and selected redacted Velo source); it needs no URL/browser and never uploads or applies anything. `analyze` reads a site into a business-book folder (REST + Playwright, GET-only); `--url` alone is the visitor-only pass over the published pages, and `--client-id` adds the vertical probes and the app binding `--apply` requires; `site function` list/get/put/publish/invokes that site's Velo HTTP functions and is not `himi functions` (serve-plane jobs). `--apply` bakes into a brandable shell; `--shell` takes the same names `himi dist --shell` does and defaults to branded-lite, and a shell that ships no prepare:customer is refused by name rather than half-built. `--shell brandy` (the site shell, RFC 0062) also derives the site's page map from its published pages and header menu \u2014 Home first, up to four tabs for the pages a visitor returns to, the rest in the drawer \u2014 into the overlay's `site` block, so it can be edited before `--apply`; only a site shell ever receives that block. It never overwrites any shell's committed apps/<shell>/provisioning.json.", required: "create <name> [--type wix|headless|branded-app|vibe] [--template <guid>] [--verticals stores,bookings,\u2026] [--folder <guid>] [--redirect-uri <uri>] [--no-oauth-app] [--write-provisioning [path]] [--site-url <url>] [--json] --yes | list | client ensure|list --site <msid> [--name <name>] [--redirect-uri <uri>] [--new] [--write-provisioning [path]] [--site-url <url>] [--json] --yes | analyze --read-only [--site <msid>] [--client-id <id>] [--json] | analyze --url <https> [--client-id <id>] [--site <msid>] [--out <dir>] [--redirect-uri <uri>] [--apply] [--shell " + SHELL_ENUM + "] | function list|get|put|publish|invoke [--site <msid>] [--url <https>] [--method GET|POST|PUT|DELETE] [--source <file>] [--body <json>] [--publish] [--invoke]" },
71515
71935
  { name: "doctor", summary: 'off-VPN preflight in one shot: probe the PUBLIC data plane (www.wixapis.com \u2014 reachable off-VPN), the INTERNAL serve/control plane for the selected target (VPN-gated), and report auth state (dpx session on disk + stored control token). --dpx additionally runs a live, EADDRINUSE-safe `dpx whoami`. Machine-readable with --json (an unreachable internal plane carries a stable code:"needs_vpn" so an agent branches on the code, not the prose); human-readable otherwise. Never loads the authoring runtime \u2014 works off-repo.', required: "[--target <t>] [--dpx] [--json] [--data-url <url>] [--timeout <ms>]" },
71516
71936
  { name: "upgrade", summary: "install the newest published CLI (`npm i -g @wix/himalaya-cli@latest`) and report what moved. This is the explicit door: himi also updates itself when the running build is more than a week old \u2014 unattended it just does it and re-runs your command, at a terminal it asks first \u2014 and `HIMI_NO_UPDATE_CHECK=1` turns that off, leaving this command as the way to catch up. Reports `unchanged` rather than claiming success when npm exits 0 without moving the version, which is what a registry mirror serving its own pinned copy looks like.", required: "[--json]" },
@@ -71519,7 +71939,7 @@ var COMMANDS = [
71519
71939
  { name: "import", summary: "turn a `himi pack --dev` package back into an EDITABLE content package \u2014 the file counterpart of `himi pull`, needing no serve plane, no account and no network. Refuses an invalid package before trusting its source (the content hash is recomputed over every entry on open, so the archive is provably the one that was packed), and refuses a RELEASE package by naming the absence \u2014 it carries built output and no source \u2014 rather than reporting a missing file. `--name` re-substitutes the app identity through the same machinery `himi init --template` and `himi pull --name` use, so the restored tree is a NEW app rather than a second copy of the same one, and a name that also appears inside a screen id (`feed` in `feed_home`) is left alone. A non-empty destination needs `--force` and a merge NAMES what it kept (`left: [...]`), the same contract `himi pull --force` has; a shape clash is refused whole before anything is written. There is no `--replace`: a file you were handed is not the plane's per-release archive.", required: "<package.himi> [--out <dir>] [--name <new>] [--force]" },
71520
71940
  { name: "pull", summary: "restore an app's SOURCE from the serve plane into an editable content package \u2014 the other half of `himi push`, which archives the content package on every publish. Owner or editor only: a viewer may download the RUNNING app (`/v1/owner/package`), not the codebase it was built from. A PAST release is restorable, not only the live one \u2014 the archive is content-addressed per release, so it has none of the live-single-slot problem that makes `himi pack` current-release-only. Two limits: pruning keeps the current release plus the newest ~20 ledger entries, and a release id identifies OTA content rather than source, so a source-only change republishes under the same id and replaces that id's archive. A release published before source archiving shipped, or pushed with `--no-source`, answers `app_source_not_found` and says to push again. `--name` re-substitutes the app identity through the same machinery `himi init --template` uses, so the restored tree is a NEW app rather than a second copy of the same one, and a name that also appears inside a screen id (`feed` in `feed_home`) is left alone. A NON-EMPTY destination is refused unless you say which you mean: `--force` merges and reports \u2014 it writes the archive over what is there, keeps files the release does not carry, and NAMES every one of them as `left: [...]` so a tree that matches no release is never silent (#2437); `--replace` restores exactly what was archived, removing those files first (`removed: [...]`) and implying `--force`. `--replace` refuses a directory holding no `himalaya.content.json`, so a mistyped `--out` cannot clear an unrelated tree, and the only paths it can remove are the ones a push would have archived \u2014 `.git/`, `node_modules/`, `dist/`, `.env*` and key material are out of its reach by construction.", required: "<app> [--release <id>] [--out <dir>] [--name <new>] [--force|--replace] [--target <t>]" },
71521
71941
  { name: "share", summary: "publish or revoke a content-addressed `.himi` share link. The package must be a validated archive for an already-published release; the resulting https URL is safe to QR, copy, or open in Himi Player.", required: "--file <package.himi> [--expires-at <ISO>] | revoke --app <app> <shareId>" },
71522
- { name: "run", summary: "serve a .himi package on localhost and open it in a browser \u2014 no serve plane, no dev-server, no setup. Given no argument it builds the current content package first, so `himi run` is the inner loop: edit, run, look at it. The SPA and /v1 are served from ONE origin because Tier-5 bundle fetches on web are same-origin-only. --chrome preview frames the app in a device chassis; --chrome app serves it as a plain web app. Blocks until Ctrl-C, like any dev server. --browser runs the whole plane INSIDE the tab (service worker + in-memory store) so no app code executes in this process or on any pod. --lan also serves it to the local network and prints a QR, so a phone on the same Wi-Fi opens it as the real app, full screen. --watch keeps the preview LIVE: every save in the content package rebuilds it and repaints the open tab with only what changed (the dev-server's hot-reload events); a build that fails keeps the last good preview on screen and says why. Any `himi run` of a content package (plain, --watch or --browser) frames its preview on the build board (`himi studio`) while it serves, and all but --browser also serve that board at /__himi/studio/ on the preview's own origin. Its .himi/run.json (pid, watching) is also how `himi render` and `himi test` tell a live --watch is running: while none is, they add a `reminder` naming `himi run --watch --content <dir> &` (at most once every ten minutes per app) \u2014 first in `next[]` and again as the envelope's LAST key (the last line of `himi test`'s plain report), so an output read with `tail` still carries it.", required: "[<package.himi|dir|url>] [--browser] [--watch] [--lan] [--content <dir>] [--port <n>] [--chrome preview|app] [--screen <id>] [--surface <id>] [--no-open] [--spa <url>]" },
71942
+ { name: "run", summary: "serve a .himi package on localhost and open it in a browser \u2014 no serve plane, no dev-server, no setup. Given no argument it builds the current content package first, so `himi run` is the inner loop: edit, run, look at it. The SPA and /v1 are served from ONE origin because Tier-5 bundle fetches on web are same-origin-only. --chrome preview frames the app in a device chassis; --chrome app serves it as a plain web app. Blocks until Ctrl-C, like any dev server. --browser runs the whole plane INSIDE the tab (service worker + in-memory store) so no app code executes in this process or on any pod. --lan also serves it to the local network and prints a QR, so a phone on the same Wi-Fi opens it as the real app, full screen. --watch keeps the preview LIVE: every save in the content package rebuilds it and repaints the open tab with only what changed (the dev-server's hot-reload events); a build that fails keeps the last good preview on screen and says why. --idle-exit <min> (with --watch only) stops the watch once that many minutes pass with no source save, printing `{\"watch\":\"idle_exit\"}` and removing its .himi/run.json on the way out \u2014 the watch `himi init` starts passes 30; one started by hand runs until Ctrl-C. Stopping (Ctrl-C, SIGTERM, SIGHUP or the idle exit) removes the run's own .himi/run.json. Any `himi run` of a content package (plain, --watch or --browser) frames its preview on the build board (`himi studio`) while it serves, and all but --browser also serve that board at /__himi/studio/ on the preview's own origin. Its .himi/run.json (pid, watching) is also how `himi render` and `himi test` tell a live --watch is running: while none is, they add a `reminder` naming `himi run --watch --content <dir> &`, and while one is (the one `himi init` started, usually, whose .himi/watch.json vouches for it until it serves) the reminder names its url and board instead (either at most once every ten minutes per app) \u2014 first in `next[]` and again as the envelope's LAST key (the last line of `himi test`'s plain report), so an output read with `tail` still carries it.", required: "[<package.himi|dir|url>] [--browser] [--watch] [--idle-exit <min>] [--lan] [--content <dir>] [--port <n>] [--chrome preview|app] [--screen <id>] [--surface <id>] [--no-open] [--spa <url>]" },
71523
71943
  { name: "mcp", summary: "run the himi tools as a local Model Context Protocol server over stdio, for coding agents \u2014 Claude Code, Cursor, Codex, VS Code. Tools: docs/components/sdk/icons, init, build, validate, test (hermetic), render (returns the PNG), progress, a live preview (start/status/stop a `himi run --watch`), whoami, and DRAFT-only push/share/preview links \u2014 never promote, login, dist or secrets. Each tool runs the same command in-process on the content package on disk (default: the host's workspace root). `himi init` and `himi agent-init` write the config for every host (.mcp.json, .cursor/mcp.json, .vscode/mcp.json, .codex/config.toml when absent) plus a `himi-preview` launch configuration (.claude/launch.json) that opens the live preview in Claude Code desktop's browser pane. Never self-updates (npx already runs the current CLI). Blocks until the host closes stdin. `himi mcp plugin --out <dir>` writes the Claude Code plugin (the customer skill + this server) as a folder \u2014 the same plugin the serve plane's marketplace hands out at /.claude-plugin/marketplace.json.", required: "[plugin --out <dir>]" },
71524
71944
  { name: "preview", summary: "print the shareable web URL + himi:// deep link + agent /v1/preview JSON URLs for an app+release; --surface frames the web preview as any catalog surface (?surface=)", required: "--app --release [--screen] [--surface]" },
71525
71945
  { name: "surfaces", summary: "the surface catalog vs this app's envelope (platforms.json): targeted surfaces, the opt-in key for the rest, per-surface preview links with --app --release, and which widget families the package produces with --measure-widgets (which runs its built bundles)", required: "[--content <dir>] [--app --release] [--measure-widgets]" },
@@ -71603,16 +72023,16 @@ function createScopedRegistryResolver(exec = execNpmConfig) {
71603
72023
  }
71604
72024
  var SCOPED_REGISTRY_TIMEOUT_MS = 5e3;
71605
72025
  function execNpmConfig(file, args, options) {
71606
- return new Promise((resolve52, reject) => {
71607
- execFile(file, args, { ...options, windowsHide: true }, (error, stdout) => error ? reject(error) : resolve52(String(stdout)));
72026
+ return new Promise((resolve53, reject) => {
72027
+ execFile(file, args, { ...options, windowsHide: true }, (error, stdout) => error ? reject(error) : resolve53(String(stdout)));
71608
72028
  });
71609
72029
  }
71610
72030
  var npmScopedWixRegistry = createScopedRegistryResolver();
71611
72031
  async function scopedRegistryBefore(lookup2, deadline) {
71612
72032
  let cancel = () => {
71613
72033
  };
71614
- const expired = new Promise((resolve52) => {
71615
- const timer = setTimeout(() => resolve52(void 0), Math.max(0, deadline - Date.now()));
72034
+ const expired = new Promise((resolve53) => {
72035
+ const timer = setTimeout(() => resolve53(void 0), Math.max(0, deadline - Date.now()));
71616
72036
  cancel = () => clearTimeout(timer);
71617
72037
  });
71618
72038
  try {
@@ -71677,7 +72097,7 @@ function packageVersion(path2) {
71677
72097
  }
71678
72098
  }
71679
72099
  function resolvedSdkVersion(fromDir = process.cwd(), bundledManifest = new URL("./authoring-runtime/node_modules/@wix/himalaya/package.json", import.meta.url)) {
71680
- let dir = resolve51(fromDir);
72100
+ let dir = resolve52(fromDir);
71681
72101
  for (; ; ) {
71682
72102
  const version2 = packageVersion(join64(dir, "node_modules", "@wix", "himalaya", "package.json"));
71683
72103
  if (version2) return { version: version2, source: "local" };
@@ -71842,7 +72262,7 @@ function thisHost() {
71842
72262
  return hostname().replace(/\s+/g, "_");
71843
72263
  }
71844
72264
  function lockStamp(now) {
71845
- return `${now} ${process.pid} ${thisHost()} ${randomUUID6()}`;
72265
+ return `${now} ${process.pid} ${thisHost()} ${randomUUID7()}`;
71846
72266
  }
71847
72267
  function processAlive(pid) {
71848
72268
  try {
@@ -71996,21 +72416,21 @@ function loaderArgs(execArgv) {
71996
72416
  }
71997
72417
  var spawnUpdateRefresh = createUpdateRefreshSpawner();
71998
72418
  function createTrimValidator(deps = {}, spawnFn = spawn7) {
71999
- const outcome = (code, report2, said) => {
72419
+ const outcome = (code, report3, said) => {
72000
72420
  let counts;
72001
72421
  let errors;
72002
72422
  try {
72003
- const parsed = JSON.parse(report2);
72423
+ const parsed = JSON.parse(report3);
72004
72424
  counts = parsed.counts;
72005
72425
  if (Array.isArray(parsed.findings)) {
72006
- const text6 = (v) => typeof v === "string" && v ? v : void 0;
72426
+ const text8 = (v) => typeof v === "string" && v ? v : void 0;
72007
72427
  errors = parsed.findings.filter((f) => f && f.severity === "error" && typeof f.id === "string").map((f) => ({
72008
72428
  id: f.id,
72009
- message: text6(f.message) ?? "",
72010
- ...text6(f.pass) ? { pass: text6(f.pass) } : {},
72011
- ...text6(f.screen) ? { screen: text6(f.screen) } : {},
72012
- ...text6(f.path) ? { path: text6(f.path) } : {},
72013
- ...text6(f.ruleCode) ? { ruleCode: text6(f.ruleCode) } : {}
72429
+ message: text8(f.message) ?? "",
72430
+ ...text8(f.pass) ? { pass: text8(f.pass) } : {},
72431
+ ...text8(f.screen) ? { screen: text8(f.screen) } : {},
72432
+ ...text8(f.path) ? { path: text8(f.path) } : {},
72433
+ ...text8(f.ruleCode) ? { ruleCode: text8(f.ruleCode) } : {}
72014
72434
  }));
72015
72435
  }
72016
72436
  } catch {
@@ -72018,7 +72438,7 @@ function createTrimValidator(deps = {}, spawnFn = spawn7) {
72018
72438
  return { code, counts, ...errors ? { errors } : {}, ...code !== 0 ? { tail: said.slice(-4e3) } : {} };
72019
72439
  };
72020
72440
  return async (dir, opts = {}) => {
72021
- const argv = ["validate", "--content", resolve51(dir), "--strict", "--skip-version-check"];
72441
+ const argv = ["validate", "--content", resolve52(dir), "--strict", "--skip-version-check"];
72022
72442
  if (!opts.fresh) {
72023
72443
  const out2 = [];
72024
72444
  const err = [];
@@ -72634,7 +73054,7 @@ function keptPlan(kept) {
72634
73054
  return note ? { kept, note } : {};
72635
73055
  }
72636
73056
  async function initFromRemoteTemplate(name, parentDir, template, c) {
72637
- const dir = resolve51(parentDir, name);
73057
+ const dir = resolve52(parentDir, name);
72638
73058
  if (existsSync66(join64(dir, "himalaya.content.json"))) {
72639
73059
  throw new Error(`content package already exists at ${dir}`);
72640
73060
  }
@@ -72647,17 +73067,17 @@ async function initFromRemoteTemplate(name, parentDir, template, c) {
72647
73067
  return finishScaffold(materializeSource(archive.files, name, dir), template);
72648
73068
  }
72649
73069
  function contentDir(flags) {
72650
- return resolve51(str8(flags.content) ?? process.cwd());
73070
+ return resolve52(str8(flags.content) ?? process.cwd());
72651
73071
  }
72652
73072
  function renameRestoredSource(restored, declaredApp, renamed) {
72653
- const text6 = {};
73073
+ const text8 = {};
72654
73074
  for (const entry of restored) {
72655
73075
  const decoded = entry.bytes.toString("utf8");
72656
- if (Buffer.from(decoded, "utf8").equals(entry.bytes)) text6[entry.path] = decoded;
73076
+ if (Buffer.from(decoded, "utf8").equals(entry.bytes)) text8[entry.path] = decoded;
72657
73077
  }
72658
- const sentineled = sentinelizeArchive(text6, declaredApp);
73078
+ const sentineled = sentinelizeArchive(text8, declaredApp);
72659
73079
  for (const entry of restored) {
72660
- if (!(entry.path in text6)) continue;
73080
+ if (!(entry.path in text8)) continue;
72661
73081
  const body = sentineled[entry.path];
72662
73082
  if (body !== void 0) entry.bytes = Buffer.from(substituteIdentity(body, renamed), "utf8");
72663
73083
  }
@@ -72691,7 +73111,7 @@ function restoreDestinationIssue(dest, force) {
72691
73111
  return null;
72692
73112
  }
72693
73113
  function isHimalayaMonorepoCheckout(start) {
72694
- let dir = resolve51(start);
73114
+ let dir = resolve52(start);
72695
73115
  for (; ; ) {
72696
73116
  if (existsSync66(join64(dir, "tools", "himi-cli", "src", "cli.ts")) && existsSync66(join64(dir, "core", "serve", "src", "server.ts"))) return true;
72697
73117
  const parent = dirname36(dir);
@@ -72753,7 +73173,7 @@ function materializeForPreview(appDir3, slug, displayName) {
72753
73173
  const forBuild = Object.fromEntries(
72754
73174
  Object.entries(files).map(([rel, body]) => [rel, rel === "himalaya.content.json" ? substituteIdentity(body, slug) : body])
72755
73175
  );
72756
- const repoRoot2 = resolve51(fileURLToPath26(new URL("../../..", import.meta.url)));
73176
+ const repoRoot2 = resolve52(fileURLToPath26(new URL("../../..", import.meta.url)));
72757
73177
  const root = join64(repoRoot2, "node_modules", ".cache", "himi-template-preview");
72758
73178
  const stillRunning = (pid) => {
72759
73179
  try {
@@ -72999,7 +73419,7 @@ Seed your coding agent with \`himi agent-init\`. Guides: ${idx.portalUrl}`);
72999
73419
  return 0;
73000
73420
  }
73001
73421
  case "agent-init": {
73002
- const dir = resolve51(str8(flags.dir) ?? process.cwd());
73422
+ const dir = resolve52(str8(flags.dir) ?? process.cwd());
73003
73423
  const { seedAgentFiles: seedAgentFiles2, NotAContentPackage: NotAContentPackage2 } = await Promise.resolve().then(() => (init_agent_context(), agent_context_exports));
73004
73424
  let res;
73005
73425
  try {
@@ -73556,7 +73976,7 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
73556
73976
  return 2;
73557
73977
  }
73558
73978
  const template = str8(flags.template);
73559
- const parentDir = resolve51(str8(flags.dir) ?? process.cwd());
73979
+ const parentDir = resolve52(str8(flags.dir) ?? process.cwd());
73560
73980
  const withAgentFiles = async (dir) => {
73561
73981
  const seeded = await seedAgentFilesFor(dir, flags);
73562
73982
  if (!seeded.agent || "error" in seeded.agent) return seeded;
@@ -73576,16 +73996,23 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
73576
73996
  return { ...seeded, next };
73577
73997
  };
73578
73998
  const startSession = async (dir, agent2, source) => {
73579
- const { openBoardForSession: openBoardForSession2, sessionSteps: sessionSteps2, homeScreenIdOf: homeScreenIdOf2, firstLookAtInit: firstLookAtInit2, recordInitSource: recordInitSource2, doNowBlock: doNowBlock2 } = await Promise.resolve().then(() => (init_milestones(), milestones_exports));
73999
+ const { openBoardForSession: openBoardForSession2, sessionSteps: sessionSteps2, homeScreenIdOf: homeScreenIdOf2, firstLookAtInit: firstLookAtInit2, recordInitSource: recordInitSource2, doNowBlock: doNowBlock2, startWatchAtInit: startWatchAtInit2 } = await Promise.resolve().then(() => (init_milestones(), milestones_exports));
73580
74000
  const board = await openBoardForSession2(dir);
73581
74001
  const home = homeScreenIdOf2(dir);
73582
74002
  recordInitSource2(dir, { template: template ?? null });
73583
74003
  const firstLook = flags["no-first-look"] === true ? { skipped: "--no-first-look" } : await firstLookAtInit2(dir, { source, home, entry: fileURLToPath26(import.meta.url), loader: loaderArgs(process.execArgv) });
73584
74004
  const pictured = "image" in firstLook;
74005
+ const watch2 = await startWatchAtInit2(dir, {
74006
+ source,
74007
+ app: await appId(dir),
74008
+ entry: fileURLToPath26(import.meta.url),
74009
+ loader: loaderArgs(process.execArgv),
74010
+ noWatch: flags["no-watch"] === true
74011
+ });
73585
74012
  const { next: later2, ...rest } = agent2;
73586
- const doNow = doNowBlock2({ dir, home, pictured, source });
74013
+ const doNow = doNowBlock2({ dir, home, pictured, source, watch: watch2 });
73587
74014
  sayDoNow(doNow);
73588
- return { doNow, ...rest, board, firstLook, next: [...sessionSteps2({ dir, home, pictured, source }), ...later2 ?? []] };
74015
+ return { doNow, ...rest, board, firstLook, watch: watch2, next: [...sessionSteps2({ dir, home, pictured, source, watch: watch2 }), ...later2 ?? []] };
73589
74016
  };
73590
74017
  const sayDoNow = (doNow, again = false) => {
73591
74018
  if (io.isTTY === true && flags.json !== true) io.error(`Do these now${again ? " (again)" : ""}:
@@ -73710,8 +74137,8 @@ ${doNow.map((l) => ` ${l}`).join("\n")}`);
73710
74137
  return 2;
73711
74138
  }
73712
74139
  }
73713
- const appDir3 = resolve51(str8(flags.dir) ?? process.cwd());
73714
- const sourceDir = resolve51(str8(flags["source-dir"]) ?? appDir3);
74140
+ const appDir3 = resolve52(str8(flags.dir) ?? process.cwd());
74141
+ const sourceDir = resolve52(str8(flags["source-dir"]) ?? appDir3);
73715
74142
  let meta;
73716
74143
  try {
73717
74144
  meta = JSON.parse(readFileSync79(join64(sourceDir, TEMPLATE_META_FILE), "utf8"));
@@ -73753,7 +74180,7 @@ ${doNow.map((l) => ` ${l}`).join("\n")}`);
73753
74180
  ...hasWorkers ? { tier5Dir: bundleOutDir2(buildDir) } : {},
73754
74181
  collect: { lint: true, strict }
73755
74182
  });
73756
- const scaffoldSourceFindings = resolve51(sourceDir) === resolve51(appDir3) ? [] : [
74183
+ const scaffoldSourceFindings = resolve52(sourceDir) === resolve52(appDir3) ? [] : [
73757
74184
  ...tokenColorIssues(sourceDir),
73758
74185
  ...surfaceIssues(sourceDir, void 0, strict),
73759
74186
  ...await fontIssues(sourceDir, strict),
@@ -73765,7 +74192,7 @@ ${doNow.map((l) => ` ${l}`).join("\n")}`);
73765
74192
  message: `scaffold source (--source-dir ${sourceDir}): ${issue2.message}`
73766
74193
  }));
73767
74194
  const { navIssues: navIssues2 } = await Promise.resolve().then(() => (init_nav_lint(), nav_lint_exports));
73768
- const { report: report2, hasErrors } = summarizeLint(app, bres, outcome, strict, [
74195
+ const { report: report3, hasErrors } = summarizeLint(app, bres, outcome, strict, [
73769
74196
  ...await cmsIssues(buildDir, app, strict, { local: true }),
73770
74197
  ...surfaceIssues(buildDir, outcome.surfaceNav, strict),
73771
74198
  ...tokenColorIssues(buildDir),
@@ -73775,7 +74202,7 @@ ${doNow.map((l) => ` ${l}`).join("\n")}`);
73775
74202
  ...scaffoldSourceFindings
73776
74203
  ]);
73777
74204
  if (hasErrors && !force) {
73778
- print({ ...report2, published: false, hint: "lint found errors \u2014 fix them, or re-run with --force to publish anyway" });
74205
+ print({ ...report3, published: false, hint: "lint found errors \u2014 fix them, or re-run with --force to publish anyway" });
73779
74206
  return 1;
73780
74207
  }
73781
74208
  const payload = await loadReleaseFromDir(app, out2);
@@ -73796,7 +74223,7 @@ ${doNow.map((l) => ` ${l}`).join("\n")}`);
73796
74223
  }));
73797
74224
  return 1;
73798
74225
  }
73799
- const iconPath = str8(flags.icon) ? resolve51(str8(flags.icon)) : templateIconPath(name);
74226
+ const iconPath = str8(flags.icon) ? resolve52(str8(flags.icon)) : templateIconPath(name);
73800
74227
  let iconHash;
73801
74228
  if (iconPath) {
73802
74229
  try {
@@ -74050,7 +74477,7 @@ ${doNow.map((l) => ` ${l}`).join("\n")}`);
74050
74477
  ...hasWorkers ? { tier5Dir: bundleOutDir2(dir) } : {},
74051
74478
  collect: { lint: true, strict }
74052
74479
  });
74053
- const { report: report2, hasErrors } = summarizeLint(app, bres, outcome, strict, [
74480
+ const { report: report3, hasErrors } = summarizeLint(app, bres, outcome, strict, [
74054
74481
  ...await cmsIssues(dir, app, strict, { local: true }),
74055
74482
  ...surfaceIssues(dir, outcome.surfaceNav, strict),
74056
74483
  ...tokenColorIssues(dir),
@@ -74067,10 +74494,10 @@ ${doNow.map((l) => ` ${l}`).join("\n")}`);
74067
74494
  if (authWarning) advisories.push(authWarning);
74068
74495
  advisories.push(...await nativeConfigAdvisories(app, configSource));
74069
74496
  const finalReport = advisories.length ? {
74070
- ...report2,
74071
- warnings: [...report2.warnings ?? [], ...advisories],
74072
- counts: { ...report2.counts, warnings: report2.counts.warnings + advisories.length }
74073
- } : report2;
74497
+ ...report3,
74498
+ warnings: [...report3.warnings ?? [], ...advisories],
74499
+ counts: { ...report3.counts, warnings: report3.counts.warnings + advisories.length }
74500
+ } : report3;
74074
74501
  await leaveForBoard(dir, "validate", {
74075
74502
  ok: !hasErrors,
74076
74503
  errors: finalReport.counts.errors,
@@ -74183,23 +74610,23 @@ ${doNow.map((l) => ` ${l}`).join("\n")}`);
74183
74610
  const app = str8(flags.app) ?? loaded.app;
74184
74611
  const invoked2 = await invokeRemoteFunction2({ baseUrl: target.baseUrl, app, definition, body: input, params, releaseId: remoteRelease, previewToken: str8(flags["preview-token"]), boCookie: boCookie() });
74185
74612
  const response = invoked2.response;
74186
- const text7 = await response.text();
74613
+ const text9 = await response.text();
74187
74614
  let body2;
74188
74615
  try {
74189
- body2 = JSON.parse(text7);
74616
+ body2 = JSON.parse(text9);
74190
74617
  } catch {
74191
- body2 = text7;
74618
+ body2 = text9;
74192
74619
  }
74193
74620
  print({ ok: response.ok, app, releaseId: remoteRelease ?? null, url: invoked2.url, status: response.status, body: body2 });
74194
74621
  return response.ok ? 0 : 1;
74195
74622
  }
74196
74623
  const invoked = await invokeLocalFunction2(loaded, name, input, params);
74197
- const text6 = await invoked.response.text();
74624
+ const text8 = await invoked.response.text();
74198
74625
  let body;
74199
74626
  try {
74200
- body = JSON.parse(text6);
74627
+ body = JSON.parse(text8);
74201
74628
  } catch {
74202
- body = text6;
74629
+ body = text8;
74203
74630
  }
74204
74631
  print({ ok: invoked.response.ok, app: loaded.app, releaseId: invoked.releaseId, status: invoked.response.status, body });
74205
74632
  return invoked.response.ok ? 0 : 1;
@@ -74218,7 +74645,7 @@ ${doNow.map((l) => ` ${l}`).join("\n")}`);
74218
74645
  return 1;
74219
74646
  }
74220
74647
  const { himiPluginFiles: himiPluginFiles2 } = await Promise.resolve().then(() => (init_claude_plugin(), claude_plugin_exports));
74221
- const dir = resolve51(out2);
74648
+ const dir = resolve52(out2);
74222
74649
  const files = himiPluginFiles2(skills.skillMd);
74223
74650
  for (const f of files) {
74224
74651
  mkdirSync32(dirname36(join64(dir, f.path)), { recursive: true });
@@ -74240,7 +74667,7 @@ ${doNow.map((l) => ` ${l}`).join("\n")}`);
74240
74667
  }
74241
74668
  if (pos[1] === "reel") {
74242
74669
  const { buildReel: buildReel2 } = await Promise.resolve().then(() => (init_studio_reel(), studio_reel_exports));
74243
- const out2 = resolve51(str8(flags.out) ?? join64(dir, ".himi", "making-of.html"));
74670
+ const out2 = resolve52(str8(flags.out) ?? join64(dir, ".himi", "making-of.html"));
74244
74671
  const { html, plan } = buildReel2(dir);
74245
74672
  mkdirSync32(dirname36(out2), { recursive: true });
74246
74673
  writeFileSync29(out2, html);
@@ -74252,7 +74679,7 @@ ${doNow.map((l) => ` ${l}`).join("\n")}`);
74252
74679
  openUrl2(url2);
74253
74680
  }
74254
74681
  const handover = { file: out2, hint: `hand ${basename17(out2)} to the owner at the handover \u2014 one self-contained page of their app taking shape; send it beside the preview link, never instead of it` };
74255
- const report2 = {
74682
+ const report3 = {
74256
74683
  ok: true,
74257
74684
  file: out2,
74258
74685
  url: url2,
@@ -74265,7 +74692,7 @@ ${doNow.map((l) => ` ${l}`).join("\n")}`);
74265
74692
  ...plan.dropped ? { dropped: plan.dropped, note: "pictures were left out to keep the file attachable" } : {},
74266
74693
  ...plan.frames.length === 0 ? { hint: "no screen versions were recorded \u2014 run `himi studio` before building so the board keeps them" } : {}
74267
74694
  };
74268
- if (flags.json === true || io.isTTY !== true) print(report2);
74695
+ if (flags.json === true || io.isTTY !== true) print(report3);
74269
74696
  else io.log(`The making-of: ${out2}
74270
74697
  ${handover.hint[0].toUpperCase()}${handover.hint.slice(1)}.`);
74271
74698
  return 0;
@@ -74351,10 +74778,10 @@ ${handover.hint[0].toUpperCase()}${handover.hint.slice(1)}.`);
74351
74778
  }
74352
74779
  if (flags.brief === true) {
74353
74780
  const { agentContextNote: agentContextNote2 } = await Promise.resolve().then(() => (init_agent_context(), agent_context_exports));
74354
- const summary = summarizeProgress2(readProgress2(dir).entries);
74781
+ const summary2 = summarizeProgress2(readProgress2(dir).entries);
74355
74782
  const open = openApprovals2(readApprovals2(dir).events);
74356
74783
  const note = agentContextNote2(dir, cliVersion());
74357
- io.log(formatBrief2({ appName: appLabel2(dir), isContentPackage, summary, open, ...note ? { agentContextNote: note } : {} }));
74784
+ io.log(formatBrief2({ appName: appLabel2(dir), isContentPackage, summary: summary2, open, ...note ? { agentContextNote: note } : {} }));
74358
74785
  return 0;
74359
74786
  }
74360
74787
  if (!message) {
@@ -74386,8 +74813,9 @@ ${handover.hint[0].toUpperCase()}${handover.hint.slice(1)}.`);
74386
74813
  return 2;
74387
74814
  }
74388
74815
  let imageNote;
74816
+ const handover = step === "review";
74389
74817
  if (!images.length && flags["no-image"] !== true && isContentPackage) {
74390
- const auto = (await Promise.resolve().then(() => (init_milestones(), milestones_exports))).homeShotForPostcard(dir);
74818
+ const auto = (await Promise.resolve().then(() => (init_milestones(), milestones_exports))).homeShotForPostcard(dir, { repeatOk: handover });
74391
74819
  if ("image" in auto) images.push(auto.image);
74392
74820
  else imageNote = `no picture attached: ${auto.skipped}`;
74393
74821
  }
@@ -74396,7 +74824,8 @@ ${handover.hint[0].toUpperCase()}${handover.hint.slice(1)}.`);
74396
74824
  const notified = flags.notify === true ? await notifyDesktop2(`himi \xB7 ${appLabel2(dir) ?? "your app"}`, message) : void 0;
74397
74825
  const { count: count2 } = summarizeProgress2(readProgress2(dir).entries);
74398
74826
  const imageAttached = (entry.images?.length ?? 0) > 0;
74399
- print({ ok: true, entry, journal: journal2, count: count2, imageAttached, ...!imageAttached && imageNote ? { imageNote } : {}, ...notified === void 0 ? {} : { notified }, ...hint });
74827
+ const handed = handover && isContentPackage ? (await Promise.resolve().then(() => (init_milestones(), milestones_exports))).afterHandoverPostcard(dir, { content: str8(flags.content) }) : null;
74828
+ print({ ok: true, entry, journal: journal2, count: count2, imageAttached, ...!imageAttached && imageNote ? { imageNote } : {}, ...notified === void 0 ? {} : { notified }, ...handed ? { makingOf: handed.makingOf, next: handed.next } : {}, ...hint });
74400
74829
  return 0;
74401
74830
  } catch (e) {
74402
74831
  if (e instanceof ProgressError2) {
@@ -74410,9 +74839,9 @@ ${handover.hint[0].toUpperCase()}${handover.hint.slice(1)}.`);
74410
74839
  const sub = pos[1] ?? "audit";
74411
74840
  const dir = contentDir(flags);
74412
74841
  if (sub === "audit") {
74413
- const report2 = auditUx(dir, flags.strict === true);
74414
- print(report2);
74415
- return report2.ok ? 0 : 1;
74842
+ const report3 = auditUx(dir, flags.strict === true);
74843
+ print(report3);
74844
+ return report3.ok ? 0 : 1;
74416
74845
  }
74417
74846
  if (sub !== "record") {
74418
74847
  io.error(JSON.stringify({ error: "ux needs audit | record" }));
@@ -74624,7 +75053,7 @@ ${handover.hint[0].toUpperCase()}${handover.hint.slice(1)}.`);
74624
75053
  }
74625
75054
  const authoringBase = serveBase();
74626
75055
  try {
74627
- const { report: report2 } = await (deps.browserAuthoring ?? runBrowserAuthoring2)({
75056
+ const { report: report3 } = await (deps.browserAuthoring ?? runBrowserAuthoring2)({
74628
75057
  authoringBase,
74629
75058
  job: {
74630
75059
  entry: sources.entry,
@@ -74641,10 +75070,10 @@ ${handover.hint[0].toUpperCase()}${handover.hint.slice(1)}.`);
74641
75070
  log: (line) => io.error(line)
74642
75071
  });
74643
75072
  const { afterBrowserTestRun: afterBrowserTestRun2, watchReminder: watchReminder3 } = await Promise.resolve().then(() => (init_milestones(), milestones_exports));
74644
- const afterRun2 = afterBrowserTestRun2(dir, { screen: screen.screenId, report: report2, boardOpen: boardOpen(dir) });
75073
+ const afterRun2 = afterBrowserTestRun2(dir, { screen: screen.screenId, report: report3, boardOpen: boardOpen(dir) });
74645
75074
  const browserReminder = watchReminder3(dir, { content: str8(flags.content) });
74646
75075
  print({
74647
- ...report2,
75076
+ ...report3,
74648
75077
  screen: screen.screenId,
74649
75078
  ranIn: "browser",
74650
75079
  liveRequested: jobWix !== void 0,
@@ -74653,7 +75082,7 @@ ${handover.hint[0].toUpperCase()}${handover.hint.slice(1)}.`);
74653
75082
  // Also the envelope's LAST key (`tailLast` keeps it there): an agent may read only the tail.
74654
75083
  ...browserReminder ? { reminder: browserReminder } : {}
74655
75084
  });
74656
- return report2.ok ? 0 : 1;
75085
+ return report3.ok ? 0 : 1;
74657
75086
  } catch (e) {
74658
75087
  io.error(JSON.stringify({ error: e.message }));
74659
75088
  return 1;
@@ -74838,6 +75267,7 @@ ${handover.hint[0].toUpperCase()}${handover.hint.slice(1)}.`);
74838
75267
  gaps: ledger,
74839
75268
  artifacts: result.shots?.dir ? [{ kind: "shots-dir", path: result.shots.dir }] : []
74840
75269
  }),
75270
+ ...afterRun.firstLook ? { firstLookPostcard: afterRun.firstLook } : {},
74841
75271
  ...afterRun.postcard ? { postcard: afterRun.postcard } : {},
74842
75272
  next: [...testReminder ? [testReminder] : [], ...afterRun.next],
74843
75273
  // Also the envelope's LAST key (`tailLast` keeps it there): an agent may read only the tail.
@@ -74998,7 +75428,7 @@ ${[...afterRun.next, ...testReminder ? [testReminder] : []].join("\n")}`);
74998
75428
  const pngTarget = typeof flags.png === "string" ? str8(flags.png) : flags.png === true ? shotPath : void 0;
74999
75429
  if (pngTarget && r.pngBase64) {
75000
75430
  pngFile = writePngFile(pngTarget, r.pngBase64);
75001
- attestationPngFile = resolve51(pngTarget) === resolve51(shotPath) ? pngFile : writePngFile(shotPath, r.pngBase64);
75431
+ attestationPngFile = resolve52(pngTarget) === resolve52(shotPath) ? pngFile : writePngFile(shotPath, r.pngBase64);
75002
75432
  }
75003
75433
  if (renderWidth !== void 0 && r.renderable) {
75004
75434
  recordRenderWidth(dir, {
@@ -75090,23 +75520,23 @@ ${[...afterRun.next, ...testReminder ? [testReminder] : []].join("\n")}`);
75090
75520
  return 2;
75091
75521
  }
75092
75522
  const { kitReport: kitReport2 } = await Promise.resolve().then(() => (init_design_kits(), design_kits_exports));
75093
- const report2 = kitReport2(pos[2]);
75094
- print(report2);
75095
- return report2.ok ? 0 : 1;
75523
+ const report3 = kitReport2(pos[2]);
75524
+ print(report3);
75525
+ return report3.ok ? 0 : 1;
75096
75526
  }
75097
75527
  case "fonts": {
75098
75528
  const sub = pos[1];
75099
75529
  const dir = contentDir(flags);
75100
75530
  const { listReport: listReport3, checkReport: checkReport2, addReport: addReport2 } = await Promise.resolve().then(() => (init_fonts(), fonts_exports));
75101
75531
  if (sub === void 0 || sub === "list") {
75102
- const report2 = listReport3({ licenses: flags.licenses === true });
75103
- print(report2);
75104
- return report2.ok ? 0 : 1;
75532
+ const report3 = listReport3({ licenses: flags.licenses === true });
75533
+ print(report3);
75534
+ return report3.ok ? 0 : 1;
75105
75535
  }
75106
75536
  if (sub === "check") {
75107
- const report2 = await checkReport2(dir);
75108
- print(report2);
75109
- return report2.ok ? 0 : 1;
75537
+ const report3 = await checkReport2(dir);
75538
+ print(report3);
75539
+ return report3.ok ? 0 : 1;
75110
75540
  }
75111
75541
  if (sub === "add") {
75112
75542
  const family = pos[2];
@@ -75114,9 +75544,9 @@ ${[...afterRun.next, ...testReminder ? [testReminder] : []].join("\n")}`);
75114
75544
  print({ ok: false, problems: ["usage: himi fonts add <Family>"] });
75115
75545
  return 2;
75116
75546
  }
75117
- const report2 = addReport2(dir, family);
75118
- print(report2);
75119
- return report2.ok ? 0 : 1;
75547
+ const report3 = addReport2(dir, family);
75548
+ print(report3);
75549
+ return report3.ok ? 0 : 1;
75120
75550
  }
75121
75551
  print({ ok: false, problems: [`unknown subcommand "${sub}"`], hint: "himi fonts [list|check|add]" });
75122
75552
  return 2;
@@ -75137,7 +75567,7 @@ ${[...afterRun.next, ...testReminder ? [testReminder] : []].join("\n")}`);
75137
75567
  summarize: summarize4
75138
75568
  } = await Promise.resolve().then(() => (init_icon(), icon_exports));
75139
75569
  const requireVerified = sub === "check";
75140
- const report2 = async (status, extra = {}) => {
75570
+ const report3 = async (status, extra = {}) => {
75141
75571
  const findings = [
75142
75572
  // Not a store rule, so it is not in byteIssues — but it is the one `himi dist`
75143
75573
  // will refuse, and the point of `check` is to say so before the build does.
@@ -75175,7 +75605,7 @@ ${[...afterRun.next, ...testReminder ? [testReminder] : []].join("\n")}`);
75175
75605
  }
75176
75606
  return blocksRelease2(status, { requireVerified }) ? 1 : 0;
75177
75607
  };
75178
- if (sub === "status" || sub === "check") return report2(await iconStatus2(dir));
75608
+ if (sub === "status" || sub === "check") return report3(await iconStatus2(dir));
75179
75609
  if (sub === "set") {
75180
75610
  const file = pos[2];
75181
75611
  if (!file) {
@@ -75183,7 +75613,7 @@ ${[...afterRun.next, ...testReminder ? [testReminder] : []].join("\n")}`);
75183
75613
  return 1;
75184
75614
  }
75185
75615
  const res = await installIcon2(dir, file, { derivePlay: true });
75186
- return report2(res.status, { wrote: res.wrote, normalized: res.normalized, playListingIcon: res.playDerived });
75616
+ return report3(res.status, { wrote: res.wrote, normalized: res.normalized, playListingIcon: res.playDerived });
75187
75617
  }
75188
75618
  if (sub === "from-site") {
75189
75619
  const book = str8(flags.book) ?? ".himi/business-book";
@@ -75198,11 +75628,11 @@ ${[...afterRun.next, ...testReminder ? [testReminder] : []].join("\n")}`);
75198
75628
  return 1;
75199
75629
  }
75200
75630
  const res = await installIcon2(dir, logo, { derivePlay: true });
75201
- return report2(res.status, { from: logo, wrote: res.wrote, normalized: res.normalized, playListingIcon: res.playDerived });
75631
+ return report3(res.status, { from: logo, wrote: res.wrote, normalized: res.normalized, playListingIcon: res.playDerived });
75202
75632
  }
75203
75633
  if (sub === "generate") {
75204
75634
  const res = await generateIcon2(dir, { glyph: str8(flags.glyph), background: str8(flags.accent), derivePlay: true });
75205
- return report2(res.status, { wrote: res.wrote });
75635
+ return report3(res.status, { wrote: res.wrote });
75206
75636
  }
75207
75637
  if (sub === "glyphs") {
75208
75638
  const names = glyphNames2();
@@ -75228,9 +75658,9 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
75228
75658
  const dir = contentDir(flags);
75229
75659
  const { listReport: listReport3, verifyReport: verifyReport2, addAssetPayload: addAssetPayload2, removeAssetPayload: removeAssetPayload2 } = await Promise.resolve().then(() => (init_assets(), assets_exports));
75230
75660
  if (sub === void 0 || sub === "list") {
75231
- const report2 = listReport3(dir);
75232
- print(report2);
75233
- return report2.ok ? 0 : 1;
75661
+ const report3 = listReport3(dir);
75662
+ print(report3);
75663
+ return report3.ok ? 0 : 1;
75234
75664
  }
75235
75665
  if (sub === "verify") {
75236
75666
  const app = str8(flags.app) ?? await appId(dir);
@@ -75239,9 +75669,9 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
75239
75669
  io.error(JSON.stringify(found));
75240
75670
  return 1;
75241
75671
  }
75242
- const report2 = verifyReport2(dir, app, found.manifest);
75243
- print(report2);
75244
- return report2.ok ? 0 : 1;
75672
+ const report3 = verifyReport2(dir, app, found.manifest);
75673
+ print(report3);
75674
+ return report3.ok ? 0 : 1;
75245
75675
  }
75246
75676
  if (sub === "add" || sub === "rm") {
75247
75677
  const app = str8(flags.app) ?? await appId(dir);
@@ -75334,7 +75764,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
75334
75764
  return 2;
75335
75765
  }
75336
75766
  const renamed = str8(flags.name);
75337
- const dest = resolve51(str8(flags.out) ?? process.cwd(), renamed ?? wanted);
75767
+ const dest = resolve52(str8(flags.out) ?? process.cwd(), renamed ?? wanted);
75338
75768
  const replace = flags.replace === true;
75339
75769
  const force = replace || flags.force === true;
75340
75770
  const linked = symlinkDestinationIssue(dest);
@@ -75407,7 +75837,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
75407
75837
  let removed = [];
75408
75838
  if (replace) removed = removeStaleSource2(restored, dest);
75409
75839
  else left = staleSourcePaths2(restored, dest);
75410
- const refused = deniedArchivePaths2(restored);
75840
+ const refused2 = deniedArchivePaths2(restored);
75411
75841
  const written = writeAppSource2(restored, dest);
75412
75842
  const agent = await seedAgentFilesFor(dest, flags, "restore");
75413
75843
  print({
@@ -75418,8 +75848,8 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
75418
75848
  ...agent,
75419
75849
  ...renamed ? { renamedTo: renamed } : {},
75420
75850
  ...removed.length ? { removed } : {},
75421
- ...refused.length ? {
75422
- refused,
75851
+ ...refused2.length ? {
75852
+ refused: refused2,
75423
75853
  refusedNote: "this archive carries paths a restore will not write (the deny-list has grown since it was published) \u2014 they were skipped, not written over"
75424
75854
  } : {},
75425
75855
  // EVERY path, not a count and not a sample: the whole failure was that a hybrid tree
@@ -75503,7 +75933,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
75503
75933
  collect: { lint: true, strict }
75504
75934
  });
75505
75935
  const { navIssues: navIssues2 } = await Promise.resolve().then(() => (init_nav_lint(), nav_lint_exports));
75506
- const { report: report2, hasErrors } = summarizeLint(declared, bres, outcome, strict, [
75936
+ const { report: report3, hasErrors } = summarizeLint(declared, bres, outcome, strict, [
75507
75937
  ...await cmsIssues(dir, declared, strict, { local: true }),
75508
75938
  ...surfaceIssues(dir, outcome.surfaceNav, strict),
75509
75939
  ...tokenColorIssues(dir),
@@ -75513,7 +75943,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
75513
75943
  ...await navIssues2(dir, strict)
75514
75944
  ]);
75515
75945
  if (hasErrors && !force) {
75516
- print({ ...report2, pushed: false, hint: "lint found errors \u2014 fix them, or re-run `himi push --force` to publish anyway" });
75946
+ print({ ...report3, pushed: false, hint: "lint found errors \u2014 fix them, or re-run `himi push --force` to publish anyway" });
75517
75947
  return 1;
75518
75948
  }
75519
75949
  const declaredData = await loadDataResourcesQuietly(dir);
@@ -75612,7 +76042,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
75612
76042
  // Reaches here ONLY on the forced path — the refusal above returned otherwise — so
75613
76043
  // this is the override's record, and the case it matters most for is a `failed` drive.
75614
76044
  ux: { evidence: patchUxEvidence, ctx: patchUxContext },
75615
- lint: report2.counts ?? null,
76045
+ lint: report3.counts ?? null,
75616
76046
  extra: { selector: parsed.selectors.map((s) => "pattern" in s ? `${s.kind}:${s.pattern}` : s.kind) }
75617
76047
  });
75618
76048
  }
@@ -75832,6 +76262,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
75832
76262
  releaseId: release,
75833
76263
  draft,
75834
76264
  ...unchanged ? { unchanged: true, unchangedNote: "the serve plane already holds this exact release under this id, so nothing was written" } : {},
76265
+ ...afterDraft?.firstLook ? { firstLookPostcard: afterDraft.firstLook } : {},
75835
76266
  ...afterDraft?.postcard ? { postcard: afterDraft.postcard } : {},
75836
76267
  // No picture on the draft postcard: why, and the render that makes a current one.
75837
76268
  ...afterDraft?.picture ? { postcardPicture: afterDraft.picture } : {},
@@ -75863,7 +76294,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
75863
76294
  // print a `himi pull` command that answers app_source_not_found later — the worst
75864
76295
  // shape for a backup, which is one you are told you have.
75865
76296
  ...sourceSummary ? sourceStored === true ? { source: { ...sourceSummary, restore: `himi pull ${app} --release ${release}` } } : sourceStored === false ? { source: { ...sourceSummary, archived: false, note: "this serve target cannot store source archives, so this release cannot be restored with `himi pull`" } } : { source: { ...sourceSummary, archived: "unconfirmed", note: `this serve target did not report whether it stored the source (it may predate the field). Verify with \`himi pull ${app} --release ${release}\` before relying on it.` } } : { source: { archived: false, note: "pushed with --no-source \u2014 this release cannot be restored with `himi pull`" } },
75866
- lint: report2.counts,
76297
+ lint: report3.counts,
75867
76298
  // A push ships content, not backends. If the app declares CMS
75868
76299
  // collections, say so and point at the verbs — but never mutate the
75869
76300
  // site as a side effect of publishing a release.
@@ -75895,7 +76326,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
75895
76326
  nondeterministicWorkers: drifting
75896
76327
  }),
75897
76328
  ...loopbackWarning ? { warning: loopbackWarning } : {},
75898
- ...report2.issues ? { issues: report2.issues } : {},
76329
+ ...report3.issues ? { issues: report3.issues } : {},
75899
76330
  previewWeb: base ? previewWebUrl(base, app, release, void 0, void 0, previewToken, previewHandover) : null,
75900
76331
  // A timestamp alone leaves the reader to work out both how long they have and how to
75901
76332
  // get another one. Print the duration and the re-mint command beside it (#2502 item 3).
@@ -75992,7 +76423,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
75992
76423
  return 2;
75993
76424
  }
75994
76425
  try {
75995
- const res = writePack(resolve51(releaseDir), only, resolve51(str8(flags.out) ?? process.cwd()));
76426
+ const res = writePack(resolve52(releaseDir), only, resolve52(str8(flags.out) ?? process.cwd()));
75996
76427
  print({
75997
76428
  ok: true,
75998
76429
  app: res.app,
@@ -76048,7 +76479,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
76048
76479
  collect: { lint: true, strict }
76049
76480
  });
76050
76481
  const { navIssues: navIssues2 } = await Promise.resolve().then(() => (init_nav_lint(), nav_lint_exports));
76051
- const { report: report2, hasErrors } = summarizeLint(app, bres, outcome, strict, [
76482
+ const { report: report3, hasErrors } = summarizeLint(app, bres, outcome, strict, [
76052
76483
  ...await cmsIssues(dir, app, strict, { local: true }),
76053
76484
  ...surfaceIssues(dir, outcome.surfaceNav, strict),
76054
76485
  ...tokenColorIssues(dir),
@@ -76059,11 +76490,11 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
76059
76490
  ]);
76060
76491
  if (hasErrors && !force) {
76061
76492
  rmSync15(built, { recursive: true, force: true });
76062
- print({ ...report2, packed: false, hint: "lint found errors \u2014 fix them, or re-run `himi pack --force` to pack anyway" });
76493
+ print({ ...report3, packed: false, hint: "lint found errors \u2014 fix them, or re-run `himi pack --force` to pack anyway" });
76063
76494
  return 1;
76064
76495
  }
76065
76496
  try {
76066
- const res = writePack(built, app, resolve51(str8(flags.out) ?? dir), sourceArchive);
76497
+ const res = writePack(built, app, resolve52(str8(flags.out) ?? dir), sourceArchive);
76067
76498
  print({
76068
76499
  ok: true,
76069
76500
  app: res.app,
@@ -76073,12 +76504,12 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
76073
76504
  entries: res.entryCount,
76074
76505
  format,
76075
76506
  launchMode,
76076
- lint: report2.counts,
76507
+ lint: report3.counts,
76077
76508
  contents: withSource ? "dev" : "release",
76078
76509
  ...sourceSummary ? { source: sourceSummary } : {},
76079
76510
  ...res.skipped.length ? { skipped: res.skipped } : {},
76080
76511
  ...hasErrors && force ? { forcedPastErrors: true } : {},
76081
- ...report2.issues ? { issues: report2.issues } : {},
76512
+ ...report3.issues ? { issues: report3.issues } : {},
76082
76513
  open: [
76083
76514
  ...withSource ? [`himi import ${res.out}`] : [],
76084
76515
  `himi run ${res.out}`,
@@ -76108,7 +76539,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
76108
76539
  const { HIMI_PACKAGE_SOURCE_PATH: HIMI_PACKAGE_SOURCE_PATH2, isSafeAppId: isSafeAppId2, packageContents: packageContents2 } = await Promise.resolve().then(() => (init_himi_package(), himi_package_exports));
76109
76540
  let opened;
76110
76541
  try {
76111
- opened = openPackageFile2(resolve51(file));
76542
+ opened = openPackageFile2(resolve52(file));
76112
76543
  } catch (e) {
76113
76544
  io.error(JSON.stringify({ error: e.message }));
76114
76545
  return 1;
@@ -76121,7 +76552,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
76121
76552
  io.error(JSON.stringify({
76122
76553
  ok: false,
76123
76554
  error: "app_source_not_in_package",
76124
- detail: `${resolve51(file)} is a release package: it carries built output (screens, bundles, tokens) and no source`,
76555
+ detail: `${resolve52(file)} is a release package: it carries built output (screens, bundles, tokens) and no source`,
76125
76556
  hint: "re-pack it from the content package with `himi pack --dev`, or restore from the serve plane with `himi pull <app>`"
76126
76557
  }));
76127
76558
  return 1;
@@ -76144,7 +76575,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
76144
76575
  return 1;
76145
76576
  }
76146
76577
  }
76147
- const dest = resolve51(str8(flags.out) ?? process.cwd(), renamed ?? opened.header.app);
76578
+ const dest = resolve52(str8(flags.out) ?? process.cwd(), renamed ?? opened.header.app);
76148
76579
  const issue2 = restoreDestinationIssue(dest, flags.force === true);
76149
76580
  if (issue2) {
76150
76581
  io.error(JSON.stringify(issue2));
@@ -76183,7 +76614,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
76183
76614
  return 1;
76184
76615
  }
76185
76616
  const left = staleSourcePaths2(restored, dest);
76186
- const refused = deniedArchivePaths2(restored);
76617
+ const refused2 = deniedArchivePaths2(restored);
76187
76618
  const written = writeAppSource2(restored, dest);
76188
76619
  const agent = await seedAgentFilesFor(dest, flags, "restore");
76189
76620
  print({
@@ -76194,8 +76625,8 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
76194
76625
  files: written.length,
76195
76626
  ...agent,
76196
76627
  ...renamed ? { renamedTo: renamed } : {},
76197
- ...refused.length ? {
76198
- refused,
76628
+ ...refused2.length ? {
76629
+ refused: refused2,
76199
76630
  refusedNote: "this package carries paths a restore will not write (the deny-list has grown since it was packed) \u2014 they were skipped, not written over"
76200
76631
  } : {},
76201
76632
  ...left.length ? {
@@ -76231,7 +76662,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
76231
76662
  }
76232
76663
  try {
76233
76664
  const { openPackageFile: openPackageFile2 } = await Promise.resolve().then(() => (init_pack(), pack_exports));
76234
- const opened = openPackageFile2(resolve51(file));
76665
+ const opened = openPackageFile2(resolve52(file));
76235
76666
  if (!opened.validation.ok || !opened.header) {
76236
76667
  io.error(JSON.stringify({ error: "refusing to share an invalid package", issues: opened.validation.errors }));
76237
76668
  return 1;
@@ -76241,12 +76672,12 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
76241
76672
  io.error(JSON.stringify({
76242
76673
  ok: false,
76243
76674
  error: "refusing to share a package that carries the app's source",
76244
- detail: `${resolve51(file)} was packed with --dev, and a share link is public and unauthenticated \u2014 anyone with the URL could \`himi import\` your codebase`,
76675
+ detail: `${resolve52(file)} was packed with --dev, and a share link is public and unauthenticated \u2014 anyone with the URL could \`himi import\` your codebase`,
76245
76676
  hint: "share a release package instead (`himi pack` with no --dev), or hand the dev package over directly"
76246
76677
  }));
76247
76678
  return 1;
76248
76679
  }
76249
- const archive = readFileSync79(resolve51(file));
76680
+ const archive = readFileSync79(resolve52(file));
76250
76681
  const result = await client2().createPackageShare({
76251
76682
  app: opened.header.app,
76252
76683
  releaseId: opened.header.releaseId,
@@ -76297,6 +76728,21 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
76297
76728
  return 2;
76298
76729
  }
76299
76730
  }
76731
+ let idleExitMs;
76732
+ if (flags["idle-exit"] !== void 0) {
76733
+ const raw = str8(flags["idle-exit"]);
76734
+ const minutes = raw === void 0 ? Number.NaN : Number(raw);
76735
+ const IDLE_EXIT_MAX_MINUTES = Math.floor(2147483647 / 6e4);
76736
+ if (!watch2 || !Number.isFinite(minutes) || minutes <= 0 || minutes > IDLE_EXIT_MAX_MINUTES) {
76737
+ io.error(JSON.stringify({
76738
+ ok: false,
76739
+ error: watch2 ? "invalid_idle_exit" : "idle_exit_needs_watch",
76740
+ message: watch2 ? `--idle-exit needs a number of minutes above 0 and at most ${IDLE_EXIT_MAX_MINUTES}, got ${JSON.stringify(raw ?? "")}` : "--idle-exit stops a --watch preview that has seen no save for that long; add --watch, or drop --idle-exit"
76741
+ }));
76742
+ return 2;
76743
+ }
76744
+ idleExitMs = minutes * 6e4;
76745
+ }
76300
76746
  let served = null;
76301
76747
  let browserCopy = null;
76302
76748
  let servedMissing = [];
@@ -76370,9 +76816,9 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
76370
76816
  });
76371
76817
  await announce(run2);
76372
76818
  print({ ok: true, app: run2.app, url: run2.url, port: run2.port, plane: "browser" });
76373
- await new Promise((resolve52) => {
76819
+ await new Promise((resolve53) => {
76374
76820
  const stop = () => {
76375
- void retract().catch((e) => io.error(JSON.stringify({ run: "stop_cleanup_failed", message: e.message }))).finally(() => run2.close().finally(() => resolve52()));
76821
+ void retract().catch((e) => io.error(JSON.stringify({ run: "stop_cleanup_failed", message: e.message }))).finally(() => run2.close().finally(() => resolve53()));
76376
76822
  };
76377
76823
  process.once("SIGINT", stop);
76378
76824
  process.once("SIGTERM", stop);
@@ -76399,6 +76845,13 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
76399
76845
  let watcher2;
76400
76846
  let rebuild;
76401
76847
  let stopping = false;
76848
+ let stopNow;
76849
+ let idleTimer;
76850
+ const armIdle = () => {
76851
+ if (idleExitMs === void 0) return;
76852
+ clearTimeout(idleTimer);
76853
+ idleTimer = setTimeout(() => stopNow?.("idle"), idleExitMs);
76854
+ };
76402
76855
  try {
76403
76856
  await announce(running);
76404
76857
  if (watchKit && bus) {
@@ -76456,7 +76909,10 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
76456
76909
  rmSync15(next, { recursive: true, force: true });
76457
76910
  }
76458
76911
  });
76459
- watcher2 = watchKit.watchContent(dir, () => rebuild?.());
76912
+ watcher2 = watchKit.watchContent(dir, () => {
76913
+ armIdle();
76914
+ rebuild?.();
76915
+ });
76460
76916
  }
76461
76917
  } catch (e) {
76462
76918
  await retract().catch(() => {
@@ -76484,11 +76940,19 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
76484
76940
  ...watch2 ? { watching: dir } : {},
76485
76941
  ...recordKit && boardOpen(dir) ? { board: `${running.origin}/__himi/studio/` } : {},
76486
76942
  ...running.warnings.length ? { warnings: running.warnings } : {},
76943
+ ...idleExitMs !== void 0 ? { idleExit: { minutes: idleExitMs / 6e4, note: "stops itself after this long with no source save" } } : {},
76487
76944
  stop: "Ctrl-C"
76488
76945
  });
76489
76946
  await new Promise((done) => {
76490
- const stop = async () => {
76947
+ const stop = async (why) => {
76948
+ if (stopping) return;
76491
76949
  stopping = true;
76950
+ clearTimeout(idleTimer);
76951
+ if (why === "idle") {
76952
+ const { shellQuote: shellQuote3 } = await Promise.resolve().then(() => (init_shell_quote(), shell_quote_exports));
76953
+ io.error(JSON.stringify({ watch: "idle_exit", minutes: idleExitMs / 6e4, message: `no source save for ${idleExitMs / 6e4} min, so this preview stopped \u2014 \`himi run --watch --content ${shellQuote3(dir)} &\` starts it again` }));
76954
+ }
76955
+ for (const sig of ["SIGINT", "SIGTERM", "SIGHUP"]) process.removeListener(sig, onSignal);
76492
76956
  watcher2?.close();
76493
76957
  try {
76494
76958
  await rebuild?.stop();
@@ -76500,8 +76964,10 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
76500
76964
  done();
76501
76965
  }
76502
76966
  };
76503
- process.once("SIGINT", () => void stop());
76504
- process.once("SIGTERM", () => void stop());
76967
+ const onSignal = () => void stop();
76968
+ for (const sig of ["SIGINT", "SIGTERM", "SIGHUP"]) process.once(sig, onSignal);
76969
+ stopNow = (why) => void stop(why);
76970
+ armIdle();
76505
76971
  });
76506
76972
  return 0;
76507
76973
  } catch (e) {
@@ -78392,13 +78858,13 @@ ${label2}`);
78392
78858
  // advertisable for the target this report is about.
78393
78859
  hasOwnerIssuer: (resolved2?.ownerIssue ?? "") !== ""
78394
78860
  });
78395
- const report2 = buildDoctorReport({ target, version: cliVersion(), data, serve, auth, loginModes, dpxStatus, versions });
78396
- if (targetError) report2.targetError = targetError;
78861
+ const report3 = buildDoctorReport({ target, version: cliVersion(), data, serve, auth, loginModes, dpxStatus, versions });
78862
+ if (targetError) report3.targetError = targetError;
78397
78863
  const { agentContextStatus: agentContextStatus2 } = await Promise.resolve().then(() => (init_agent_context(), agent_context_exports));
78398
78864
  const agentContext = agentContextStatus2(contentDir(flags), cliVersion());
78399
- if (agentContext) report2.agentContext = agentContext;
78865
+ if (agentContext) report3.agentContext = agentContext;
78400
78866
  if (flags.json === true) {
78401
- print(report2);
78867
+ print(report3);
78402
78868
  return 0;
78403
78869
  }
78404
78870
  const mark = (r) => r.reachable ? `reachable${r.status ? ` [${r.status}]` : ""}` : `UNREACHABLE${r.error ? ` (${r.error})` : ""}`;
@@ -78408,7 +78874,7 @@ ${label2}`);
78408
78874
  io.log(` ${mark(data)}`);
78409
78875
  io.log(` serve plane ${serveUrl || "(no serve url)"}`);
78410
78876
  io.log(` ${mark(serve)}`);
78411
- const serveCode = report2.servePlane.code;
78877
+ const serveCode = report3.servePlane.code;
78412
78878
  if (serveCode) io.log(` ${serveCode}: ${serveUnreachableHint(target)}`);
78413
78879
  io.log(` auth dpx session: ${auth.dpxSession ? "yes" : "no"} control token: ${auth.controlToken ? "yes" : controlHostRefused ? "REFUSED" : auth.controlTokenPresent ? "STALE" : "no"}`);
78414
78880
  if (auth.controlTokenHint) io.log(` ${auth.controlTokenHint}`);
@@ -78431,7 +78897,7 @@ ${label2}`);
78431
78897
  }
78432
78898
  io.log(
78433
78899
  `
78434
- ${report2.ok ? "OK \u2014 data + serve plane reachable." : typeof report2.hint === "string" && report2.hint ? report2.hint : serve.reachable ? "Data plane unreachable \u2014 check basic connectivity." : serveUnreachableHint(target)}`
78900
+ ${report3.ok ? "OK \u2014 data + serve plane reachable." : typeof report3.hint === "string" && report3.hint ? report3.hint : serve.reachable ? "Data plane unreachable \u2014 check basic connectivity." : serveUnreachableHint(target)}`
78435
78901
  );
78436
78902
  return 0;
78437
78903
  }
@@ -78544,9 +79010,9 @@ ${report2.ok ? "OK \u2014 data + serve plane reachable." : typeof report2.hint =
78544
79010
  }
78545
79011
  const scope = { app, siteId: site, environment: environment2 };
78546
79012
  try {
78547
- const report2 = sub === "plan" ? await planData({ scope, data, kv, call }) : sub === "create" ? await createData({ scope, data, kv, call, dryRun: flags["dry-run"] === true }) : await seedData({ scope, data, kv, call, dir, dryRun: flags["dry-run"] === true, force: flags.force === true });
78548
- print(report2);
78549
- return report2.ok ? 0 : 1;
79013
+ const report3 = sub === "plan" ? await planData({ scope, data, kv, call }) : sub === "create" ? await createData({ scope, data, kv, call, dryRun: flags["dry-run"] === true }) : await seedData({ scope, data, kv, call, dir, dryRun: flags["dry-run"] === true, force: flags.force === true });
79014
+ print(report3);
79015
+ return report3.ok ? 0 : 1;
78550
79016
  } catch (error) {
78551
79017
  io.error(JSON.stringify({ error: error.message, app, site, environment: environment2, hint: "run `himi data plan` to re-read actual state before retrying" }));
78552
79018
  return 1;
@@ -78561,6 +79027,14 @@ ${report2.ok ? "OK \u2014 data + serve plane reachable." : typeof report2.hint =
78561
79027
  const { runNotifySetup: runNotifySetup2 } = await Promise.resolve().then(() => (init_notify_setup(), notify_setup_exports));
78562
79028
  return runNotifySetup2(pos, flags, io);
78563
79029
  }
79030
+ if (pos[1] === "send" && flags.provider !== void 0) {
79031
+ if (flags.provider !== "ping") {
79032
+ io.error(JSON.stringify({ ok: false, error: "unknown_provider", hint: "The hosted sender is --provider ping." }));
79033
+ return 2;
79034
+ }
79035
+ const { runNotifyPing: runNotifyPing2 } = await Promise.resolve().then(() => (init_notify_ping(), notify_ping_exports));
79036
+ return runNotifyPing2(flags, io);
79037
+ }
78564
79038
  const { runNotify: runNotify2 } = await Promise.resolve().then(() => (init_notify(), notify_exports));
78565
79039
  return await runNotify2(pos, flags, io);
78566
79040
  case "cms": {
@@ -78619,9 +79093,9 @@ ${report2.ok ? "OK \u2014 data + serve plane reachable." : typeof report2.hint =
78619
79093
  return 2;
78620
79094
  }
78621
79095
  const common = { app, cms, call };
78622
- let report2;
79096
+ let report3;
78623
79097
  try {
78624
- report2 = sub === "query" ? await queryCms({ ...common, all: flags.all === true }) : sub === "create" ? await createCms({ ...common, dryRun: flags["dry-run"] === true, allowDestructive: flags["allow-destructive"] === true, applyPermissions: flags["apply-permissions"] === true }) : await seedCms({ ...common, dir, dryRun: flags["dry-run"] === true, force: flags.force === true, only: str8(flags.only) });
79098
+ report3 = sub === "query" ? await queryCms({ ...common, all: flags.all === true }) : sub === "create" ? await createCms({ ...common, dryRun: flags["dry-run"] === true, allowDestructive: flags["allow-destructive"] === true, applyPermissions: flags["apply-permissions"] === true }) : await seedCms({ ...common, dir, dryRun: flags["dry-run"] === true, force: flags.force === true, only: str8(flags.only) });
78625
79099
  } catch (e) {
78626
79100
  const partial = e.partial;
78627
79101
  io.error(JSON.stringify({
@@ -78633,8 +79107,8 @@ ${report2.ok ? "OK \u2014 data + serve plane reachable." : typeof report2.hint =
78633
79107
  }));
78634
79108
  return 1;
78635
79109
  }
78636
- print({ ...report2, site });
78637
- return report2.ok ? 0 : 1;
79110
+ print({ ...report3, site });
79111
+ return report3.ok ? 0 : 1;
78638
79112
  }
78639
79113
  case "site": {
78640
79114
  const sub = pos[1];