@wix/himalaya-cli 0.1031.0 → 0.1033.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
@@ -1264,6 +1264,26 @@ var init_compiler_options = __esm({
1264
1264
  import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync3, existsSync as existsSync2, readdirSync as readdirSync2, readFileSync as readFileSync3, statSync, lstatSync, copyFileSync } from "node:fs";
1265
1265
  import { resolve as resolve2, join as join3, dirname as dirname2 } from "node:path";
1266
1266
  import { fileURLToPath } from "node:url";
1267
+ function keptPlanNote(kept) {
1268
+ if (!kept?.length) return void 0;
1269
+ const pronoun = kept.length === 1 ? "it" : "them";
1270
+ return `kept your existing ${kept.join(" and ")} \u2014 init did not overwrite ${pronoun}. Check ${pronoun} against \`himi docs mobile-ux\` (the headings \`validate --strict\` requires) before implementing.`;
1271
+ }
1272
+ function gitignoreKey(line) {
1273
+ return line.trim().replace(/^\//, "").replace(/\/$/, "");
1274
+ }
1275
+ function ensurePackageGitignore(res) {
1276
+ const dest = join3(res.dir, ".gitignore");
1277
+ const existing = existsSync2(dest) ? readFileSync3(dest, "utf8") : null;
1278
+ const present = new Set((existing ?? "").split(/\r?\n/).map(gitignoreKey));
1279
+ const missing = PACKAGE_GITIGNORE.filter((entry) => !present.has(gitignoreKey(entry)));
1280
+ if (missing.length === 0) return res;
1281
+ const prefix = existing === null ? "" : existing.length === 0 || existing.endsWith("\n") ? existing : `${existing}
1282
+ `;
1283
+ writeFileSync3(dest, `${prefix}${missing.join("\n")}
1284
+ `);
1285
+ return res.files.includes(".gitignore") ? res : { ...res, files: [...res.files, ".gitignore"].sort() };
1286
+ }
1267
1287
  function defaultPackageJson(name) {
1268
1288
  return JSON.stringify(
1269
1289
  {
@@ -1364,9 +1384,14 @@ function sentinelizeArchive(raw, fallbackName) {
1364
1384
  }
1365
1385
  function materializeSource(files, name, destDir, slug = name) {
1366
1386
  const written = [];
1387
+ const kept = [];
1367
1388
  for (const [rel, body] of Object.entries(files)) {
1368
1389
  if (rel === TEMPLATE_META_FILE) continue;
1369
1390
  const dest = join3(destDir, rel);
1391
+ if (PLAN_FILES.has(rel) && existsSync2(dest)) {
1392
+ kept.push(rel);
1393
+ continue;
1394
+ }
1370
1395
  mkdirSync3(resolve2(dest, ".."), { recursive: true });
1371
1396
  const materialized = substituteIdentity(body, name, slug);
1372
1397
  writeFileSync3(dest, MODULE_SOURCE_FILE.test(rel) ? offRepoImports(materialized) : materialized);
@@ -1375,6 +1400,10 @@ function materializeSource(files, name, destDir, slug = name) {
1375
1400
  for (const [rel, body] of [["SPEC.md", SPEC_TEMPLATE], ["MOBILE-UX.md", MOBILE_UX_TEMPLATE]]) {
1376
1401
  if (Object.prototype.hasOwnProperty.call(files, rel)) continue;
1377
1402
  const dest = join3(destDir, rel);
1403
+ if (existsSync2(dest)) {
1404
+ kept.push(rel);
1405
+ continue;
1406
+ }
1378
1407
  mkdirSync3(resolve2(dest, ".."), { recursive: true });
1379
1408
  writeFileSync3(dest, body);
1380
1409
  written.push(rel);
@@ -1390,19 +1419,20 @@ function materializeSource(files, name, destDir, slug = name) {
1390
1419
  written.push("tsconfig.json");
1391
1420
  }
1392
1421
  written.sort();
1393
- return { dir: destDir, files: written };
1422
+ return { dir: destDir, files: written, ...kept.length ? { kept: kept.sort() } : {} };
1394
1423
  }
1395
- function finishScaffold(res, template) {
1424
+ function finishScaffold(scaffolded, template) {
1425
+ const res = ensurePackageGitignore(scaffolded);
1396
1426
  if (!template) return res;
1397
1427
  const dest = join3(res.dir, "icon.png");
1398
1428
  if (existsSync2(dest)) {
1399
1429
  if (res.files.includes("icon.png")) return res;
1400
- return { dir: res.dir, files: [...res.files, "icon.png"].sort() };
1430
+ return { ...res, files: [...res.files, "icon.png"].sort() };
1401
1431
  }
1402
1432
  const src = templateIconPath(template);
1403
1433
  if (!src) return res;
1404
1434
  copyFileSync(src, dest);
1405
- return { dir: res.dir, files: [...res.files, "icon.png"].sort() };
1435
+ return { ...res, files: [...res.files, "icon.png"].sort() };
1406
1436
  }
1407
1437
  function isBinaryTemplateFile(rel) {
1408
1438
  return BINARY_EXTENSIONS.has(extOf(rel));
@@ -1448,8 +1478,13 @@ function scaffoldContentPackage(name, parentDir, template) {
1448
1478
  const idSeg = name.replace(/[^a-z0-9]/gi, "").toLowerCase() || "app";
1449
1479
  const appId2 = `com.example.${idSeg}`;
1450
1480
  const files = [];
1481
+ const kept = [];
1451
1482
  const write2 = (rel, body) => {
1452
1483
  const p = join3(dir, rel);
1484
+ if (PLAN_FILES.has(rel) && existsSync2(p)) {
1485
+ kept.push(rel);
1486
+ return;
1487
+ }
1453
1488
  mkdirSync3(resolve2(p, ".."), { recursive: true });
1454
1489
  writeFileSync3(p, body);
1455
1490
  files.push(rel);
@@ -1641,14 +1676,16 @@ A per-app \`npm i @wix/himalaya\` is optional and only buys your editor local ty
1641
1676
  himi agent-init # seed this dir with HIMALAYA.md + reference + example
1642
1677
  `
1643
1678
  );
1644
- return { dir, files };
1679
+ return finishScaffold({ dir, files, ...kept.length ? { kept: kept.sort() } : {} });
1645
1680
  }
1646
- var MODULE_SOURCE_FILE, TEMPLATES_DIR, SHOWCASE_TEMPLATES_DIR, SHOWCASE_TEMPLATE_NAMES, TEMPLATE_META_FILE, TEMPLATE_ICONS_DIR, HEX_COLOR, PALETTE_KEYS, IDENTITY_SENTINELS, escapeRe, NAME_FIELDS, IDENTITY_FILES, DOTID_RE, BINARY_EXTENSIONS, extOf;
1681
+ var PLAN_FILES, PACKAGE_GITIGNORE, MODULE_SOURCE_FILE, TEMPLATES_DIR, SHOWCASE_TEMPLATES_DIR, SHOWCASE_TEMPLATE_NAMES, TEMPLATE_META_FILE, TEMPLATE_ICONS_DIR, HEX_COLOR, PALETTE_KEYS, IDENTITY_SENTINELS, escapeRe, NAME_FIELDS, IDENTITY_FILES, DOTID_RE, BINARY_EXTENSIONS, extOf;
1647
1682
  var init_scaffold = __esm({
1648
1683
  "src/scaffold.ts"() {
1649
1684
  init_source_deny();
1650
1685
  init_mobile_ux_contract();
1651
1686
  init_compiler_options();
1687
+ PLAN_FILES = /* @__PURE__ */ new Set(["SPEC.md", "MOBILE-UX.md"]);
1688
+ PACKAGE_GITIGNORE = [".himi/", "dist/", "node_modules/"];
1652
1689
  MODULE_SOURCE_FILE = /\.(?:[cm]?[jt]sx?)$/i;
1653
1690
  TEMPLATES_DIR = resolve2(dirname2(fileURLToPath(import.meta.url)), "../templates");
1654
1691
  SHOWCASE_TEMPLATES_DIR = resolve2(dirname2(fileURLToPath(import.meta.url)), "../showcase-apps");
@@ -24467,11 +24504,11 @@ function withExtras(name, render) {
24467
24504
  if (!React5.isValidElement(el)) return /* @__PURE__ */ jsx6(Fragment3, { children: el });
24468
24505
  const additive = addIntrinsicPadding(el.props.style, wrap ?? {});
24469
24506
  const inferredDecorative = el.props[INFERRED_DECORATIVE] === true;
24470
- const iconGlyph2 = el.props[ICON_GLYPH] === true;
24507
+ const selfRenderedLeaf = el.props[SELF_RENDERED_LEAF] === true;
24471
24508
  const roleWhenNamed = el.props[ROLE_WHEN_NAMED];
24472
24509
  if (!Object.keys(pass).length) {
24473
- if (inferredDecorative || iconGlyph2 || roleWhenNamed != null) {
24474
- return React5.cloneElement(el, { [INFERRED_DECORATIVE]: void 0, [ICON_GLYPH]: void 0, [ROLE_WHEN_NAMED]: void 0, ...additive ? { style: additive } : {} });
24510
+ if (inferredDecorative || selfRenderedLeaf || roleWhenNamed != null) {
24511
+ return React5.cloneElement(el, { [INFERRED_DECORATIVE]: void 0, [SELF_RENDERED_LEAF]: void 0, [ROLE_WHEN_NAMED]: void 0, [PENDING_RANGE]: void 0, ...additive ? { style: additive } : {} });
24475
24512
  }
24476
24513
  return additive ? React5.cloneElement(el, { style: additive }) : /* @__PURE__ */ jsx6(Fragment3, { children: el });
24477
24514
  }
@@ -24495,13 +24532,29 @@ function withExtras(name, render) {
24495
24532
  next.role = roleWhenNamed;
24496
24533
  }
24497
24534
  }
24498
- if (iconGlyph2) {
24499
- next[ICON_GLYPH] = void 0;
24535
+ const pendingRange = prev[PENDING_RANGE];
24536
+ if (pendingRange != null) {
24537
+ next[PENDING_RANGE] = void 0;
24538
+ const finalRole = next.role ?? pass.role ?? prev.role;
24539
+ if (typeof finalRole === "string" && RANGE_ROLES.has(finalRole) && typeof pendingRange === "object") Object.assign(next, pendingRange);
24540
+ }
24541
+ if (selfRenderedLeaf) {
24542
+ next[SELF_RENDERED_LEAF] = void 0;
24543
+ if (prev.role === "progressbar" && typeof pass.role === "string" && !RANGE_ROLES.has(pass.role)) {
24544
+ next["aria-valuenow"] = void 0;
24545
+ next["aria-valuemin"] = void 0;
24546
+ next["aria-valuemax"] = void 0;
24547
+ next["aria-valuetext"] = void 0;
24548
+ }
24500
24549
  if (pass.role === "presentation" && typeof pass.onClick !== "function" && typeof pass.onDoubleClick !== "function") {
24501
24550
  next["aria-hidden"] = "true";
24502
24551
  next["aria-label"] = void 0;
24503
24552
  next.title = void 0;
24504
24553
  next.role = void 0;
24554
+ next["aria-valuenow"] = void 0;
24555
+ next["aria-valuemin"] = void 0;
24556
+ next["aria-valuemax"] = void 0;
24557
+ next["aria-valuetext"] = void 0;
24505
24558
  }
24506
24559
  }
24507
24560
  const roleIsSynthetic = next[SYNTHETIC_TAP_ROLE] === true;
@@ -26006,7 +26059,7 @@ function renderInner(node, ctx) {
26006
26059
  }
26007
26060
  }
26008
26061
  }
26009
- var useIsomorphicLayoutEffect, ResponsiveGrid, SPLIT_EXPANDED_MIN_WIDTH, SPLIT_COLUMN_GAP, ResponsiveSplitView, ABSOLUTE_URL_RE, appearKeyframesInjected, pressStylesInjected, FOCUS_RING_CSS, focusStylesInjected, HLIST_CHILD_CSS, hListStylesInjected, listAnimateRuntimeInstalled, transitionKeyframesInjected, countUpRuntimeInjected, MEASURE_PREFIX, measureRuntimeInjected, revealRuntimeInjected, vectorAnimStylesInjected, scrollProgressRuntimeInjected, confettiRuntimeInjected, lottieRuntimeInjected, riveRuntimeInjected, gradientKeyframesInjected, parallaxRuntimeInjected, spinKeyframesInjected, marqueeKeyframesInjected, alignItems, zJustifyItems, hAlignItems, bodyFont, kids, rowKids, staggeredKids, asTextL10n, asBoundText, useBoundString, InterpolatedTextView, txt, selectFragmentCase, fragmentValueHash, fragmentSlotContent, FragmentSlotNode, WorkerSubtreeNode, resolveItemDeep2, resolveSwipeActions, resolveModBindings, resolvedComponentId, directServiceBindings, ReactiveDirectScalars, HiddenGate, KEYBOARD_INPUT_MODES, AUTOCAPITALIZE_VALUES, ENTER_KEY_HINTS, AUTOGROW_CEILING, EditableField, MenuNode, LoadMoreSentinel, FieldErrorNode, FormView, MediaSlotProvider, MEDIA_ITEM_BINDABLE, MediaPlayerView, VideoNode, PdfViewNode, WebViewNode, InteractivePicker, InteractiveDatePicker, InteractiveSearchField, InteractiveStepper, SYNTHETIC_TAP_ROLE, SYNTHETIC_TABINDEX, INFERRED_DECORATIVE, ICON_GLYPH, ROLE_WHEN_NAMED, AUTHORED_ROLE, INTERACTIVE_ROLES, ALWAYS_INTERACTIVE_COMPONENT_TYPES, controlNode, ToggleNode, SliderNode, SelectionControlNode, TabsNode, SegmentedControlNode, DisclosureGroupNode, GridNode, SplitViewNode, TextNode, ImageNode, AvatarNode, IconNode, ButtonNode, OtpInputNode, ProgressBarNode, ActivityIndicatorNode, BottomSheetNode, ChipsInputNode, ChipNode, TableNode, GaugeRing, GaugeNode, RATING_METRICS, ratingMetrics, RatingNode, AvatarStripNode, CHART_STROKE_CYCLE, chartStrokeAt, chartBarCenter, ChartNode, donutSliceOpacity, DonutChartNode, LegendMarker, LegendNode, cellAlign, DataTableNode, voiceNoteState, voiceNoteDelivery, VOICE_NOTE_DEFAULT_BARS, clampBarCount, VoiceNoteNode, TimelineNode, WheelPickerNode, ColorPickerNode, FilterChipsNode, TILE_PX, MAX_MERCATOR_LAT, DEFAULT_TILE_MAX_ZOOM, CIRCLE_SEGMENTS, METRES_PER_DEG_LAT, MARKER_HIT_RADIUS, SHAPE_HIT_RADIUS, MapNode, CollapsingHeaderNode, UnsupportedOnSurface, CALENDAR_DATE_ONLY_RE, CAL_DEFAULT_MAX_DOTS, CAL_AGENDA_INITIAL_DAYS, CAL_AGENDA_PAGE_DAYS, CAL_DEFAULT_SNAP_MINUTES, CAL_UNASSIGNED_LANE_ID, MonthCalendarNode, TimeGridNode, AgendaNode, ARIA_ROLE, initialFocusScheduled, initialFocusSeen, PRESENTED_SCOPE, presentedOpeners, presentedObserver, A11yHintDescription, WebRenderer;
26062
+ var useIsomorphicLayoutEffect, ResponsiveGrid, SPLIT_EXPANDED_MIN_WIDTH, SPLIT_COLUMN_GAP, ResponsiveSplitView, ABSOLUTE_URL_RE, appearKeyframesInjected, pressStylesInjected, FOCUS_RING_CSS, focusStylesInjected, HLIST_CHILD_CSS, hListStylesInjected, listAnimateRuntimeInstalled, transitionKeyframesInjected, countUpRuntimeInjected, MEASURE_PREFIX, measureRuntimeInjected, revealRuntimeInjected, vectorAnimStylesInjected, scrollProgressRuntimeInjected, confettiRuntimeInjected, lottieRuntimeInjected, riveRuntimeInjected, gradientKeyframesInjected, parallaxRuntimeInjected, spinKeyframesInjected, marqueeKeyframesInjected, alignItems, zJustifyItems, hAlignItems, bodyFont, kids, rowKids, staggeredKids, asTextL10n, asBoundText, useBoundString, InterpolatedTextView, txt, selectFragmentCase, fragmentValueHash, fragmentSlotContent, FragmentSlotNode, WorkerSubtreeNode, resolveItemDeep2, resolveSwipeActions, resolveModBindings, resolvedComponentId, directServiceBindings, ReactiveDirectScalars, HiddenGate, KEYBOARD_INPUT_MODES, AUTOCAPITALIZE_VALUES, ENTER_KEY_HINTS, AUTOGROW_CEILING, EditableField, MenuNode, LoadMoreSentinel, FieldErrorNode, FormView, MediaSlotProvider, MEDIA_ITEM_BINDABLE, MediaPlayerView, VideoNode, PdfViewNode, WebViewNode, InteractivePicker, InteractiveDatePicker, InteractiveSearchField, InteractiveStepper, SYNTHETIC_TAP_ROLE, SYNTHETIC_TABINDEX, INFERRED_DECORATIVE, SELF_RENDERED_LEAF, ROLE_WHEN_NAMED, AUTHORED_ROLE, PENDING_RANGE, RANGE_ROLES, INTERACTIVE_ROLES, ALWAYS_INTERACTIVE_COMPONENT_TYPES, controlNode, ToggleNode, SliderNode, SelectionControlNode, TabsNode, SegmentedControlNode, DisclosureGroupNode, GridNode, SplitViewNode, TextNode, ImageNode, AvatarNode, IconNode, ButtonNode, OtpInputNode, ProgressBarNode, ActivityIndicatorNode, BottomSheetNode, ChipsInputNode, ChipNode, TableNode, GaugeRing, GaugeNode, RATING_METRICS, ratingMetrics, RatingNode, AvatarStripNode, CHART_STROKE_CYCLE, chartStrokeAt, chartBarCenter, ChartNode, donutSliceOpacity, DonutChartNode, LegendMarker, LegendNode, cellAlign, DataTableNode, voiceNoteState, voiceNoteDelivery, VOICE_NOTE_DEFAULT_BARS, clampBarCount, VoiceNoteNode, TimelineNode, WheelPickerNode, ColorPickerNode, FilterChipsNode, TILE_PX, MAX_MERCATOR_LAT, DEFAULT_TILE_MAX_ZOOM, CIRCLE_SEGMENTS, METRES_PER_DEG_LAT, MARKER_HIT_RADIUS, SHAPE_HIT_RADIUS, MapNode, CollapsingHeaderNode, UnsupportedOnSurface, CALENDAR_DATE_ONLY_RE, CAL_DEFAULT_MAX_DOTS, CAL_AGENDA_INITIAL_DAYS, CAL_AGENDA_PAGE_DAYS, CAL_DEFAULT_SNAP_MINUTES, CAL_UNASSIGNED_LANE_ID, MonthCalendarNode, TimeGridNode, AgendaNode, ARIA_ROLE, initialFocusScheduled, initialFocusSeen, PRESENTED_SCOPE, presentedOpeners, presentedObserver, A11yHintDescription, WebRenderer;
26010
26063
  var init_renderNode = __esm({
26011
26064
  "../../core/web-renderer/src/renderNode.tsx"() {
26012
26065
  "use strict";
@@ -27570,9 +27623,11 @@ var init_renderNode = __esm({
27570
27623
  SYNTHETIC_TAP_ROLE = "data-hm-synthetic-role";
27571
27624
  SYNTHETIC_TABINDEX = "data-hm-synthetic-tabindex";
27572
27625
  INFERRED_DECORATIVE = "data-hm-inferred-decorative";
27573
- ICON_GLYPH = "data-hm-icon-glyph";
27626
+ SELF_RENDERED_LEAF = "data-hm-self-rendered-leaf";
27574
27627
  ROLE_WHEN_NAMED = "data-hm-role-when-named";
27575
27628
  AUTHORED_ROLE = "data-hm-authored-role";
27629
+ PENDING_RANGE = "data-hm-pending-range";
27630
+ RANGE_ROLES = /* @__PURE__ */ new Set(["progressbar", "slider", "scrollbar", "spinbutton", "meter"]);
27576
27631
  INTERACTIVE_ROLES = /* @__PURE__ */ new Set([
27577
27632
  "button",
27578
27633
  "checkbox",
@@ -27754,7 +27809,11 @@ var init_renderNode = __esm({
27754
27809
  }
27755
27810
  return /* @__PURE__ */ jsx6("img", { src, alt: asText2(boundAlt), style: { width: w, height: boxHeight, aspectRatio: ar, objectFit, borderRadius: r, display: "block", flex: "none", background: ph, ...wrap } });
27756
27811
  }
27757
- if (imgInitials && typeof imgInitials === "string") return avatarCircle(String(imgInitials), typeof w === "number" ? w : 40, C, false, bodyFont(ctx));
27812
+ if (imgInitials && typeof imgInitials === "string") {
27813
+ const alt = asText2(boundAlt).trim();
27814
+ const circle = avatarCircle(String(imgInitials), typeof w === "number" ? w : 40, C, false, bodyFont(ctx));
27815
+ return React5.cloneElement(circle, alt ? { role: "img", "aria-label": alt, [SELF_RENDERED_LEAF]: true } : { [ROLE_WHEN_NAMED]: "img", [SELF_RENDERED_LEAF]: true });
27816
+ }
27758
27817
  if (props.icon != null && props.url == null && props.asset == null && imgInitials == null) {
27759
27818
  const side2 = typeof w === "number" ? w : 40;
27760
27819
  return /* @__PURE__ */ jsx6("div", { style: { width: side2, height: side2, borderRadius: "50%", background: ph ?? C("surfaceTertiary", "#e6e6e6"), display: "grid", placeItems: "center", flex: "none", ...wrap }, children: iconSvg(props.icon, { size: Math.max(14, Math.round(side2 * 0.5)), color: C("accentPrimary", "#116DFF") }) });
@@ -27780,7 +27839,7 @@ var init_renderNode = __esm({
27780
27839
  const iconName = isBinding(props.name) ? String(bound ?? "") : props.name;
27781
27840
  const contentDescription = useResolvedScalar(props.contentDescription, ctx);
27782
27841
  const described = contentDescription != null && String(contentDescription).trim() !== "";
27783
- const a11y = described ? { role: "img", "aria-label": String(contentDescription), [ICON_GLYPH]: true } : { "aria-hidden": "true", [INFERRED_DECORATIVE]: true, [ICON_GLYPH]: true, [ROLE_WHEN_NAMED]: "img" };
27842
+ const a11y = described ? { role: "img", "aria-label": String(contentDescription), [SELF_RENDERED_LEAF]: true } : { "aria-hidden": "true", [INFERRED_DECORATIVE]: true, [SELF_RENDERED_LEAF]: true, [ROLE_WHEN_NAMED]: "img" };
27784
27843
  return /* @__PURE__ */ jsx6("span", { ...a11y, style: { display: "inline-grid", placeItems: "center", width: size + 6, height: size + 6, lineHeight: 1, flex: "none", ...wrap }, children: iconSvg(iconName, { size, color: C(resolveScalarStatic(props.tint, ctx) ?? "textPrimary", "#636b78") }) });
27785
27844
  });
27786
27845
  ButtonNode = withExtras("ButtonNode", ({ props, ctx, wrap }) => {
@@ -27906,7 +27965,19 @@ var init_renderNode = __esm({
27906
27965
  const numP = typeof rawP === "number" ? rawP : typeof rawP === "string" && rawP.trim() !== "" && Number.isFinite(Number(rawP)) ? Number(rawP) : void 0;
27907
27966
  const p = numP !== void 0 ? Math.max(0, Math.min(1, numP)) : props.indeterminate ? 0.4 : null;
27908
27967
  const animatedFill = props.animated === true && !ctx.reduceMotion();
27909
- return /* @__PURE__ */ jsx6("div", { style: { width: "100%", height: 6, borderRadius: 999, background: C(boundTokenName(props.trackColorPath, props.trackColor, ctx) ?? "surfaceTertiary", "#eee"), overflow: "hidden", ...wrap }, children: /* @__PURE__ */ jsx6("div", { style: { width: `${Math.round((p ?? 0.4) * 100)}%`, height: "100%", borderRadius: 999, background: C(boundTokenName(props.tintPath, props.tint, ctx) ?? "accentPrimary", "#116DFF"), opacity: p == null ? 0.45 : 1, ...animatedFill ? { transition: ctx.motionTransition(ctx.kit?.motion) } : {} } }) });
27968
+ const a11y = {
27969
+ role: "progressbar",
27970
+ "aria-valuemin": 0,
27971
+ "aria-valuemax": 100,
27972
+ // `Number.isFinite`, not `!== undefined`: a worker's NaN (done/total, total 0) is a number
27973
+ // too, and would be announced as aria-valuenow="NaN".
27974
+ // A number wins over `indeterminate`, in the announcement as in the fill painted above:
27975
+ // `indeterminate` is "DEPRECATED web-only; not read by the native decoders" (core.proto),
27976
+ // so iOS and Android draw and speak the number.
27977
+ ...numP !== void 0 && Number.isFinite(numP) && p != null ? { "aria-valuenow": Math.round(p * 100) } : {},
27978
+ [SELF_RENDERED_LEAF]: true
27979
+ };
27980
+ return /* @__PURE__ */ jsx6("div", { ...a11y, style: { width: "100%", height: 6, borderRadius: 999, background: C(boundTokenName(props.trackColorPath, props.trackColor, ctx) ?? "surfaceTertiary", "#eee"), overflow: "hidden", ...wrap }, children: /* @__PURE__ */ jsx6("div", { style: { width: `${Math.round((p ?? 0.4) * 100)}%`, height: "100%", borderRadius: 999, background: C(boundTokenName(props.tintPath, props.tint, ctx) ?? "accentPrimary", "#116DFF"), opacity: p == null ? 0.45 : 1, ...animatedFill ? { transition: ctx.motionTransition(ctx.kit?.motion) } : {} } }) });
27910
27981
  });
27911
27982
  ActivityIndicatorNode = withExtras("ActivityIndicatorNode", ({ props, ctx, wrap }) => {
27912
27983
  const C = ctx.color;
@@ -27991,11 +28062,11 @@ var init_renderNode = __esm({
27991
28062
  GaugeRing = ({ ring, ctx, index, size, step, thick, track }) => {
27992
28063
  const bound = useResolvedScalar(ring?.valuePath, ctx);
27993
28064
  const rv = ring?.valuePath != null ? bound : ring?.value;
27994
- const rp = Math.max(0, Math.min(1, typeof rv === "number" ? rv : 0));
28065
+ const rp = Math.max(0, Math.min(1, typeof rv === "number" && Number.isFinite(rv) ? rv : 0));
27995
28066
  const rt = ctx.color(ring?.tint ?? "accentPrimary", "#116DFF");
27996
28067
  const d = size - index * step, off = (size - d) / 2;
27997
28068
  const mask = `radial-gradient(farthest-side, transparent calc(100% - ${thick}px), #000 calc(100% - ${thick}px))`;
27998
- return /* @__PURE__ */ jsx6("div", { style: { position: "absolute", top: off, left: off, width: d, height: d, borderRadius: "50%", background: `conic-gradient(${rt} ${rp * 360}deg, ${track} 0deg)`, WebkitMask: mask, mask } });
28069
+ return /* @__PURE__ */ jsx6("div", { "aria-hidden": "true", style: { position: "absolute", top: off, left: off, width: d, height: d, borderRadius: "50%", background: `conic-gradient(${rt} ${rp * 360}deg, ${track} 0deg)`, WebkitMask: mask, mask } });
27999
28070
  };
28000
28071
  GaugeNode = withExtras("GaugeNode", ({ props, ctx, wrap }) => {
28001
28072
  const C = ctx.color;
@@ -28007,19 +28078,25 @@ var init_renderNode = __esm({
28007
28078
  const tint = C(props.tint ?? "accentPrimary", "#116DFF");
28008
28079
  const track = C("surfaceTertiary", "#e3e6ea");
28009
28080
  const gaugeAnim = props.animated === true && !ctx.reduceMotion() ? { transition: ctx.motionTransition(ctx.kit?.motion) } : {};
28081
+ const labelName = useBoundString(props.label, ctx).trim();
28082
+ const hasValue = typeof gRaw === "number" && Number.isFinite(gRaw);
28083
+ const named = labelName ? { "aria-label": labelName } : hasValue && max > min ? { "aria-label": `${Math.round(pct * 100)}%` } : {};
28084
+ const fractionScale = min === 0 && max === 1;
28085
+ const range = max > min ? { "aria-valuemin": min, "aria-valuemax": max, ...hasValue ? { "aria-valuenow": Math.max(min, Math.min(max, v)), ...fractionScale ? { "aria-valuetext": `${Math.round(pct * 100)}%` } : {} } : {} } : {};
28086
+ const a11y = labelName || hasValue && max > min ? { role: "progressbar", ...named, ...range, [SELF_RENDERED_LEAF]: true } : { "aria-hidden": "true", [INFERRED_DECORATIVE]: true, [ROLE_WHEN_NAMED]: "progressbar", [PENDING_RANGE]: range, [SELF_RENDERED_LEAF]: true };
28010
28087
  if (Array.isArray(props.rings) && props.rings.length) {
28011
28088
  const size = 96, thick = 9, step = 22;
28012
- return /* @__PURE__ */ jsx6("div", { style: { position: "relative", width: size, height: size, ...wrap }, children: props.rings.map((r, i) => /* @__PURE__ */ jsx6(GaugeRing, { ring: r, ctx, index: i, size, step, thick, track }, i)) });
28089
+ return /* @__PURE__ */ jsx6("div", { role: "group", ...named, ...{ [SELF_RENDERED_LEAF]: true }, style: { position: "relative", width: size, height: size, ...wrap }, children: props.rings.map((r, i) => /* @__PURE__ */ jsx6(GaugeRing, { ring: r, ctx, index: i, size, step, thick, track }, i)) });
28013
28090
  }
28014
28091
  if (props.kind === "linear") {
28015
- return /* @__PURE__ */ jsxs3("div", { style: { width: "100%", ...wrap }, children: [
28092
+ return /* @__PURE__ */ jsxs3("div", { ...a11y, style: { width: "100%", ...wrap }, children: [
28016
28093
  props.label != null && /* @__PURE__ */ jsx6("div", { style: { fontFamily: bodyFont(ctx), fontSize: 13, color: C("textSecondary", "#636b78"), marginBottom: 4 }, children: txt(props.label, ctx) }),
28017
28094
  /* @__PURE__ */ jsx6("div", { style: { width: "100%", height: 8, borderRadius: 999, background: track, overflow: "hidden" }, children: /* @__PURE__ */ jsx6("div", { style: { width: `${pct * 100}%`, height: "100%", background: tint, ...gaugeAnim } }) })
28018
28095
  ] });
28019
28096
  }
28020
28097
  const ringMask = "radial-gradient(farthest-side, transparent calc(100% - 8px), #000 calc(100% - 8px))";
28021
28098
  const capF = ctx.font("caption");
28022
- return /* @__PURE__ */ jsx6("div", { style: { display: "inline-flex", flexDirection: "column", alignItems: "center", gap: 4, ...wrap }, children: /* @__PURE__ */ jsxs3("div", { style: { position: "relative", width: 72, height: 72 }, children: [
28099
+ return /* @__PURE__ */ jsx6("div", { ...a11y, style: { display: "inline-flex", flexDirection: "column", alignItems: "center", gap: 4, ...wrap }, children: /* @__PURE__ */ jsxs3("div", { style: { position: "relative", width: 72, height: 72 }, children: [
28023
28100
  /* @__PURE__ */ jsx6("div", { style: { width: 72, height: 72, borderRadius: "50%", background: `conic-gradient(${tint} ${pct * 360}deg, ${track} 0deg)`, WebkitMask: ringMask, mask: ringMask, ...gaugeAnim } }),
28024
28101
  /* @__PURE__ */ jsx6("div", { style: { position: "absolute", inset: 0, display: "grid", placeItems: "center", fontFamily: fontStack(capF.family, ctx.idiom), fontSize: capF.remSize, fontWeight: capF.weight, color: C("textPrimary", "#13171e") }, children: txt(props.label, ctx) || `${Math.round(pct * 100)}%` })
28025
28102
  ] }) });
@@ -49498,21 +49575,21 @@ var init_preview_assets_generated = __esm({
49498
49575
  "../../packages/serve/src/preview-assets.generated.ts"() {
49499
49576
  init_preview_assets_format();
49500
49577
  PREVIEW_ASSET_MANIFEST = [
49501
- { key: "assets/index-VOdBPMHE.js", contentType: "application/javascript; charset=utf-8", offset: 8, length: 1048280, sha256: "7351282bf52e78ef541a7dabe577e46b46daafe19a730aaff590c027ab919818" },
49502
- { key: "fonts/Roboto-LICENSE.txt", contentType: "application/octet-stream", offset: 1048288, length: 4610, sha256: "994062dd7af4186a739de55dd9e151c63b6301f8e2cbafc608d35c2e87da0cd2" },
49503
- { key: "fonts/Roboto-Variable.ttf", contentType: "font/ttf", offset: 1052898, length: 488584, sha256: "d7598e12c5dbef095ff8272cfc55da0250bd07fbdecbac8a530b9b277872a134" },
49504
- { key: "fonts/WixMadeforDisplay-Bold.ttf", contentType: "font/ttf", offset: 1541482, length: 90924, sha256: "ec4c7d63772d980d6e147b2df6215fd2737e4b04cebe0cf7e11dc695fb63cdcf" },
49505
- { key: "fonts/WixMadeforDisplay-ExtraBold.ttf", contentType: "font/ttf", offset: 1632406, length: 90984, sha256: "bf4c16a2774edd38a921edf3099469866cd74c094fd596f3fa4e81f54a04dfaa" },
49506
- { key: "fonts/WixMadeforDisplay-Medium.ttf", contentType: "font/ttf", offset: 1723390, length: 90980, sha256: "368faca44fb9f7207e4af784e94750b1ee59c1098b8689e0f4a918926f244fb5" },
49507
- { key: "fonts/WixMadeforDisplay-Regular.ttf", contentType: "font/ttf", offset: 1814370, length: 90872, sha256: "97ed8f1a6772abe26e3d8af5cecfc4b02faa94497d585b0398b5053b739a70c1" },
49508
- { key: "fonts/WixMadeforDisplay-SemiBold.ttf", contentType: "font/ttf", offset: 1905242, length: 90980, sha256: "642feab8a48ddf6c7a89d7c2a7c0edfa0e6e773f671287e460e222703563191e" },
49509
- { key: "fonts/WixMadeforText-Bold.ttf", contentType: "font/ttf", offset: 1996222, length: 108636, sha256: "37c179b4fefb63ea9637322b84583a2df53aa226c150294eea1675c14b1b8e29" },
49510
- { key: "fonts/WixMadeforText-ExtraBold.ttf", contentType: "font/ttf", offset: 2104858, length: 109084, sha256: "62e2fe4718129f36ecdb0ae4de385bd6b81ecc876146447167c6c140fad699a9" },
49511
- { key: "fonts/WixMadeforText-Medium.ttf", contentType: "font/ttf", offset: 2213942, length: 109048, sha256: "4ecf270136af928612c76fe4ebade91d8156975e5ea4c0cf9225501dd1788e02" },
49512
- { key: "fonts/WixMadeforText-Regular.ttf", contentType: "font/ttf", offset: 2322990, length: 107700, sha256: "dee56f09fd3efc127c1ea95557edc74110c1460a46287da36e0af25efd341ef5" },
49513
- { key: "fonts/WixMadeforText-SemiBold.ttf", contentType: "font/ttf", offset: 2430690, length: 88108, sha256: "dced97a77624bc5f8b1ae65850360908297f4588e09feb6f22792d29dc7e765b" },
49514
- { key: "index.html", contentType: "text/html; charset=utf-8", offset: 2518798, length: 4947, sha256: "5bdaf36e319850247a522b19051ace779fb34d67f14211c3197761610d3a81e6" },
49515
- { key: "t5-frame.html", contentType: "text/html; charset=utf-8", offset: 2523745, length: 17209, sha256: "dffc9718e2faa63ae2cc919e0a842f585ed565aaaa7c53778e90379442c731b6" }
49578
+ { key: "assets/index-CGsR5F0F.js", contentType: "application/javascript; charset=utf-8", offset: 8, length: 1049558, sha256: "591c0803688d9807699f9ba9452bb09b73f05ffb51675e199287b6c8885cfeb7" },
49579
+ { key: "fonts/Roboto-LICENSE.txt", contentType: "application/octet-stream", offset: 1049566, length: 4610, sha256: "994062dd7af4186a739de55dd9e151c63b6301f8e2cbafc608d35c2e87da0cd2" },
49580
+ { key: "fonts/Roboto-Variable.ttf", contentType: "font/ttf", offset: 1054176, length: 488584, sha256: "d7598e12c5dbef095ff8272cfc55da0250bd07fbdecbac8a530b9b277872a134" },
49581
+ { key: "fonts/WixMadeforDisplay-Bold.ttf", contentType: "font/ttf", offset: 1542760, length: 90924, sha256: "ec4c7d63772d980d6e147b2df6215fd2737e4b04cebe0cf7e11dc695fb63cdcf" },
49582
+ { key: "fonts/WixMadeforDisplay-ExtraBold.ttf", contentType: "font/ttf", offset: 1633684, length: 90984, sha256: "bf4c16a2774edd38a921edf3099469866cd74c094fd596f3fa4e81f54a04dfaa" },
49583
+ { key: "fonts/WixMadeforDisplay-Medium.ttf", contentType: "font/ttf", offset: 1724668, length: 90980, sha256: "368faca44fb9f7207e4af784e94750b1ee59c1098b8689e0f4a918926f244fb5" },
49584
+ { key: "fonts/WixMadeforDisplay-Regular.ttf", contentType: "font/ttf", offset: 1815648, length: 90872, sha256: "97ed8f1a6772abe26e3d8af5cecfc4b02faa94497d585b0398b5053b739a70c1" },
49585
+ { key: "fonts/WixMadeforDisplay-SemiBold.ttf", contentType: "font/ttf", offset: 1906520, length: 90980, sha256: "642feab8a48ddf6c7a89d7c2a7c0edfa0e6e773f671287e460e222703563191e" },
49586
+ { key: "fonts/WixMadeforText-Bold.ttf", contentType: "font/ttf", offset: 1997500, length: 108636, sha256: "37c179b4fefb63ea9637322b84583a2df53aa226c150294eea1675c14b1b8e29" },
49587
+ { key: "fonts/WixMadeforText-ExtraBold.ttf", contentType: "font/ttf", offset: 2106136, length: 109084, sha256: "62e2fe4718129f36ecdb0ae4de385bd6b81ecc876146447167c6c140fad699a9" },
49588
+ { key: "fonts/WixMadeforText-Medium.ttf", contentType: "font/ttf", offset: 2215220, length: 109048, sha256: "4ecf270136af928612c76fe4ebade91d8156975e5ea4c0cf9225501dd1788e02" },
49589
+ { key: "fonts/WixMadeforText-Regular.ttf", contentType: "font/ttf", offset: 2324268, length: 107700, sha256: "dee56f09fd3efc127c1ea95557edc74110c1460a46287da36e0af25efd341ef5" },
49590
+ { key: "fonts/WixMadeforText-SemiBold.ttf", contentType: "font/ttf", offset: 2431968, length: 88108, sha256: "dced97a77624bc5f8b1ae65850360908297f4588e09feb6f22792d29dc7e765b" },
49591
+ { key: "index.html", contentType: "text/html; charset=utf-8", offset: 2520076, length: 4947, sha256: "98dd4bdbeaa0bdbabf0f9df5b37a3408dec1e7e40d67dd39a89f64932c2c7a59" },
49592
+ { key: "t5-frame.html", contentType: "text/html; charset=utf-8", offset: 2525023, length: 17209, sha256: "dffc9718e2faa63ae2cc919e0a842f585ed565aaaa7c53778e90379442c731b6" }
49516
49593
  ];
49517
49594
  }
49518
49595
  });
@@ -61657,8 +61734,21 @@ function committedIdentity(root) {
61657
61734
  const records = raw === null ? null : committedRecords(raw);
61658
61735
  if (records !== null) return `base\0${base}\0${records}`;
61659
61736
  }
61660
- const tree = tryGit(["rev-parse", "HEAD^{tree}"], root)?.trim();
61661
- return tree ? `tree\0${tree}\0` : `commit\0${head}\0`;
61737
+ const listing = tryGit(["ls-tree", "-rz", "--full-tree", "HEAD"], root);
61738
+ const tree = listing === null ? null : treeRecords(listing);
61739
+ return tree !== null ? `tree-listing\0${tree}` : `commit\0${head}\0`;
61740
+ }
61741
+ function treeRecords(listing) {
61742
+ const records = [];
61743
+ for (const record2 of listing.split("\0")) {
61744
+ if (record2 === "") continue;
61745
+ const tab = record2.indexOf(" ");
61746
+ if (tab <= 0 || tab === record2.length - 1) return null;
61747
+ const path2 = record2.slice(tab + 1);
61748
+ if (isValidationMetadata(path2)) continue;
61749
+ records.push(`${Buffer.byteLength(path2)}:${path2}\0${record2.slice(0, tab)}\0`);
61750
+ }
61751
+ return records.sort().join("");
61662
61752
  }
61663
61753
  function treeEntries(output, index = false) {
61664
61754
  const entries = /* @__PURE__ */ new Map();
@@ -61689,14 +61779,15 @@ function blobOid(contents, algorithm) {
61689
61779
  }
61690
61780
  function validationDiffHash(cwd = process.cwd()) {
61691
61781
  const root = resolveRepoRoot(cwd);
61692
- const staged = git(["diff", "--cached", "--name-only", "--no-renames", "-z", "HEAD"], {
61782
+ const born = tryGit(["rev-parse", "--verify", "--quiet", "HEAD^{commit}"], root) !== null;
61783
+ const staged = git(["diff", "--cached", "--name-only", "--no-renames", "-z", ...born ? ["HEAD"] : []], {
61693
61784
  cwd: root
61694
61785
  }).split("\0").filter(Boolean);
61695
61786
  const unstaged = git(["diff", "--name-only", "--no-renames", "-z"], { cwd: root }).split("\0").filter(Boolean);
61696
61787
  const untracked = git(["ls-files", "--others", "--exclude-standard", "-z"], {
61697
61788
  cwd: root
61698
61789
  }).split("\0").filter(Boolean);
61699
- const headEntries = treeEntries(git(["ls-tree", "-rz", "--full-tree", "HEAD"], { cwd: root }));
61790
+ const headEntries = born ? treeEntries(git(["ls-tree", "-rz", "--full-tree", "HEAD"], { cwd: root })) : /* @__PURE__ */ new Map();
61700
61791
  const indexEntries = treeEntries(git(["ls-files", "--stage", "-z"], { cwd: root }), true);
61701
61792
  const objectAlgorithm = git(["rev-parse", "--show-object-format"], { cwd: root }).trim();
61702
61793
  let trustFilesystemMode = true;
@@ -61955,10 +62046,13 @@ function selfHashFor(attestation) {
61955
62046
  }
61956
62047
  function repoRoot(contentDir2) {
61957
62048
  try {
61958
- return execFileSync2("git", ["-C", contentDir2, "rev-parse", "--show-toplevel"], {
62049
+ const root = execFileSync2("git", ["-C", contentDir2, "rev-parse", "--show-toplevel"], {
61959
62050
  encoding: "utf8",
61960
62051
  stdio: ["ignore", "pipe", "ignore"]
61961
62052
  }).trim();
62053
+ if (!root) return null;
62054
+ execFileSync2("git", ["-C", root, "rev-parse", "--verify", "--quiet", "HEAD^{commit}"], { stdio: "ignore" });
62055
+ return root;
61962
62056
  } catch {
61963
62057
  return null;
61964
62058
  }
@@ -65892,6 +65986,10 @@ function writePngFile(path2, base64) {
65892
65986
  writeFileSync28(path2, Buffer.from(base64, "base64"));
65893
65987
  return path2;
65894
65988
  }
65989
+ function keptPlan(kept) {
65990
+ const note = keptPlanNote(kept);
65991
+ return note ? { kept, note } : {};
65992
+ }
65895
65993
  async function initFromRemoteTemplate(name, parentDir, template, c) {
65896
65994
  const dir = resolve47(parentDir, name);
65897
65995
  if (existsSync61(join58(dir, "himalaya.content.json"))) {
@@ -66737,7 +66835,7 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
66737
66835
  try {
66738
66836
  const res2 = await initFromRemoteTemplate(name, parentDir, template, client2());
66739
66837
  const pushSetup2 = await offerPushSetup2({ contentDir: res2.dir, flags, io });
66740
- print({ ok: true, app: name, dir: res2.dir, template, source: "remote", files: res2.files, pushSetup: pushSetup2 });
66838
+ print({ ok: true, app: name, dir: res2.dir, template, source: "remote", files: res2.files, ...keptPlan(res2.kept), pushSetup: pushSetup2 });
66741
66839
  return 0;
66742
66840
  } catch (e) {
66743
66841
  if (!availableTemplates().includes(template)) {
@@ -66749,7 +66847,7 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
66749
66847
  }
66750
66848
  const res = scaffoldContentPackage(name, parentDir, template);
66751
66849
  const pushSetup = await offerPushSetup2({ contentDir: res.dir, flags, io });
66752
- print({ ok: true, app: name, dir: res.dir, ...template ? { template, source: "embedded" } : {}, files: res.files, pushSetup });
66850
+ print({ ok: true, app: name, dir: res.dir, ...template ? { template, source: "embedded" } : {}, files: res.files, ...keptPlan(res.kept), pushSetup });
66753
66851
  return 0;
66754
66852
  }
66755
66853
  case "template": {
Binary file
@@ -111,8 +111,12 @@ authenticated owner preview. The interactive `himi preview` URLs exist for a **d
111
111
  web host and call it owner-private, and do not let a template worker boot there until its startup
112
112
  network paths are authorized and preflighted.
113
113
 
114
- **Adopt, don't just rename, a template.** `himi init --template` seeds `SPEC.md` and
115
- `MOBILE-UX.md`; replace their boilerplate with the actual customer, data/auth assumptions,
114
+ **Adopt, don't just rename, a template.** The approved `SPEC.md` and `MOBILE-UX.md` are the
115
+ plan, and the plan wins over anything `himi init` seeds. `init` seeds placeholder copies of
116
+ both and can overwrite plan files already in the package root, so keep a copy of the approved
117
+ plan until `init` has run. After `init`, open both files:
118
+ if either holds `init`'s boilerplate rather than the approved plan, put the approved plan back in
119
+ its place — never the reverse — and finish it with the actual customer, data/auth assumptions,
116
120
  placeholder copy, flow/state matrix, and compact/phone/expanded + device evidence before calling
117
121
  the app ready. Keep scenario switches out of customer UI. A labelled preview fallback is acceptable
118
122
  only when the host cannot perform the task and it cannot write customer data.
@@ -199,8 +199,9 @@ tried in this order:
199
199
  `--width`/`--device` records nothing on purpose: without one the PNG viewport is the headless
200
200
  browser's desktop default, and writing that down would manufacture the very expanded render the
201
201
  marker exists to prove. Recording never fails or slows a render — if the file cannot be written
202
- the render still succeeds, and the paths below still apply. `.himi/` is gitignored, so this is
203
- evidence about your checkout rather than something a pull request carries.
202
+ the render still succeeds, and the paths below still apply. `.himi/` is gitignored (`himi init`
203
+ writes that rule into the package's `.gitignore`; add it yourself to a package made another way),
204
+ so this is evidence about your checkout rather than something a pull request carries.
204
205
 
205
206
  - Or name two pixel widths in the document, spanning compact → wider,
206
207
  **each spelled as a width on itself**, within about 400 characters of each other so they read as
@@ -958,10 +958,12 @@ Two details that are easy to get wrong, both verified against a real baked seed
958
958
 
959
959
  Known edges, stated rather than implied: `seed-data.json` (entity snapshots) is **not** release
960
960
  content — it comes from the app's source module, so the pool copies it when the checkout has the
961
- app and otherwise ships `{}`, and those screens show empty states until the first sync. The icon
962
- and widget snapshots are single-slot per app with no release id, so a pinned release gets whatever
963
- is live; the envelope hashes the live bytes against the manifest's `iconHash`/per-widget
964
- `contentEtag` and **warns** on drift while still baking the live asset.
961
+ app and otherwise ships `{}`, and those screens show empty states until the first sync. The widget
962
+ snapshots are single-slot per app with no release id, so a pinned release gets whatever is live;
963
+ the icon is too, but only for an older release published before releases kept their own icon —
964
+ every later one, draft included, bakes its own content-addressed copy. On that live-slot path the envelope hashes the live bytes
965
+ against the manifest's `iconHash`/per-widget `contentEtag` and **warns** on drift while still
966
+ baking the live asset.
965
967
 
966
968
  ## The install link
967
969
 
@@ -999,11 +1001,16 @@ is live; the envelope hashes the live bytes against the manifest's `iconHash`/pe
999
1001
  `identity.appName`, `native.androidApplicationId`, `native.urlScheme` and `native.appLinkHost`
1000
1002
  with unchecked `as String`, so a missing one fails during configure with no mention of branding —
1001
1003
  a test pins that the projection always emits all four.
1002
- - **No icon in the release?** `/v1/dist/contract` projects a published release and `putIcon` is
1003
- live-only single-slot, so a **draft** release (what `--release` usually points at) has no icon and
1004
- the served contract's `appIconPath` is empty — which `prepare:customer` rejects on purpose. The
1005
- pool fills it from the shell's own committed `provisioning.json`, so the build degrades to the
1006
- shell's artwork instead of failing over branding.
1004
+ - **Which icon does the build get?** The pinned release's **own**, draft or live. Publish keeps a
1005
+ content-addressed copy of the launcher icon under the release's `iconHash`, and both
1006
+ `/v1/dist/contract` and `/v1/dist/seed` read that copy first — so a QA build of an approved
1007
+ **draft** carries the customer's icon without promoting anything live. Only an older release,
1008
+ published before that copy existed, falls back to the app's single live icon slot (`putIcon`), and
1009
+ the seed then warns if the live icon is not the one the release recorded.
1010
+ - **No icon at all?** When neither the release nor the live slot has one, the served contract's
1011
+ `appIconPath` is empty — which `prepare:customer` rejects on purpose. The pool fills it from the
1012
+ shell's own committed `provisioning.json`, so the build degrades to the shell's artwork instead
1013
+ of failing over branding.
1007
1014
 
1008
1015
  ## Where the baked seed comes from
1009
1016
 
@@ -1034,10 +1041,12 @@ Two honest edges:
1034
1041
  never served over OTA — it is a build-time input from the app's own source module. The pool copies
1035
1042
  it when this checkout happens to have the app, else ships `{}` and those screens show empty states
1036
1043
  until the first sync.
1037
- - **The icon and widget snapshots are single-slot per app** (`put/getIcon`, `put/getWidget`) with no
1038
- release id, so a pinned release cannot read "its own" copy — it gets whatever is live. The
1039
- endpoint hashes the live bytes against the manifest's `iconHash` / per-widget `contentEtag` and
1040
- **warns** when they differ; the build still bakes the live asset, but the drift is in the log.
1044
+ - **The widget snapshots are single-slot per app** (`put/getWidget`) with no release id, so a
1045
+ pinned release cannot read "its own" copy — it gets whatever is live. The icon reads the
1046
+ release's own content-addressed copy (under `iconHash`) first and falls back to the single live
1047
+ slot (`put/getIcon`) only for an older release published before that copy existed. On a live-slot read the endpoint
1048
+ hashes the bytes against the manifest's `iconHash` / per-widget `contentEtag` and **warns** when
1049
+ they differ; the build still bakes the live asset, but the drift is in the log.
1041
1050
 
1042
1051
  Source-built seeding remains the fallback for the Falcon `[dist]` path, which has no job token and
1043
1052
  therefore no seed endpoint to call.
@@ -208,9 +208,17 @@ images stay as `static.wixstatic.com` URLs in descriptors — do not vendor
208
208
  himi validate
209
209
  himi render <home-screen> --live --png # live visitor data; no VPN
210
210
  himi test --content . --json # wrong-reducer class; no test files
211
- himi push && himi promote # VPN + `himi login --dpx --target bo`
211
+ himi login --browser --target wix # your own Wix account; no VPN
212
+ himi push # a DRAFT by default; prints the exact promote command
213
+ himi promote --app <app> --release <id> --target wix # the one push printed; name the target
212
214
  ```
213
215
 
216
+ Self-serve is the default: `himi login --browser --target wix` signs in with the customer's own
217
+ Wix account and stores a scoped token, so `push` / `promote` need no VPN and no pasted secret
218
+ (`--wix` reuses the machine's Wix CLI credential, for a container with no browser). Wix
219
+ employees can sign in with `himi login --dpx` instead and then use `--target bo`, the in-house
220
+ alternative that does need the corporate network.
221
+
214
222
  Published `@wix/himalaya-cli@0.207.0` has `validate`/`render` but **no** `himi test`
215
223
  (or `himi site`). Use a newer CLI, or the in-repo CLI from a Himalaya checkout
216
224
  (`node --import tsx tools/himi-cli/src/cli.ts`, cwd = the checkout so `tsx` resolves).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wix/himalaya-cli",
3
- "version": "0.1031.0",
3
+ "version": "0.1033.0",
4
4
  "type": "module",
5
5
  "description": "himi — the Himalaya authoring CLI. Build a Tier-5 content package on your own machine, push it to the production serve as a draft, preview it on web + Himi Player, and read worker logs back — the remote dev loop. Wraps @himalaya/serve-cli for transport.",
6
6
  "keywords": [
@@ -65,5 +65,5 @@
65
65
  "artifactId": "himalaya-cli"
66
66
  }
67
67
  },
68
- "falconPackageHash": "b2432c45ff8926fa8325d6f05d7cfaddf683708c33a4537c34b9fa45"
68
+ "falconPackageHash": "7837bf39e796e96ea058ad8a53b90966687ce65908583051a94208a3"
69
69
  }
@@ -579,7 +579,7 @@ const SCREENS = runtimeSetting({
579
579
  "package": "himalaya",
580
580
  "type": "Gauge",
581
581
  "props": {
582
- "value": 68,
582
+ "value": 0.68,
583
583
  "label": "Goal"
584
584
  }
585
585
  }