@wix/himalaya-cli 0.1151.0 → 0.1153.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.
Files changed (41) hide show
  1. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/bookings-v2/src/extensions.d.ts +33 -2
  2. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/wix-data/src/index.d.ts +14 -2
  3. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-bookings.mjs +71 -5
  4. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-data.mjs +3 -2
  5. package/dist/cli.mjs +141 -29
  6. package/dist/preview-assets.generated.bin +0 -0
  7. package/guides/cms-collections.md +4 -1
  8. package/guides/getting-started.md +3 -1
  9. package/guides/himi-skill.md +3 -1
  10. package/guides/owner-lite-site-binding.md +26 -0
  11. package/guides/push-cli-onboarding.md +42 -1
  12. package/guides/sdk-reference.json +23 -11
  13. package/guides/templates.md +1 -0
  14. package/guides/wix-api-usage.md +5 -1
  15. package/package.json +2 -2
  16. package/templates/beauty-salon/dev/index.ts +6 -3
  17. package/templates/community-club/dev/index.ts +6 -3
  18. package/templates/community-club-owner/dev/index.ts +44 -1
  19. package/templates/community-club-owner/tier5-src/himi-owner-lite-event/index.ts +52 -8
  20. package/templates/events-meetups/dev/index.ts +6 -3
  21. package/templates/events-meetups-owner/dev/index.ts +44 -1
  22. package/templates/events-meetups-owner/tier5-src/himi-owner-lite-event/index.ts +52 -8
  23. package/templates/gym-fitness/dev/index.ts +6 -3
  24. package/templates/gym-fitness-owner/dev/index.ts +44 -1
  25. package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-event/index.ts +52 -8
  26. package/templates/nonprofit-cause/dev/index.ts +6 -3
  27. package/templates/nonprofit-cause-owner/dev/index.ts +44 -1
  28. package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-event/index.ts +52 -8
  29. package/templates/owner-lite/dev/index.ts +44 -1
  30. package/templates/owner-lite/tier5-src/himi-owner-lite-event/index.ts +52 -8
  31. package/templates/podcast-creator/dev/index.ts +6 -3
  32. package/templates/podcast-creator-owner/dev/index.ts +44 -1
  33. package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-event/index.ts +52 -8
  34. package/templates/portfolio-creative/dev/index.ts +6 -3
  35. package/templates/restaurant/dev/index.ts +6 -3
  36. package/templates/restaurant-owner/dev/index.ts +44 -1
  37. package/templates/restaurant-owner/tier5-src/himi-owner-lite-event/index.ts +52 -8
  38. package/templates/retail-store/dev/index.ts +7 -3
  39. package/templates/yoga-studio/dev/index.ts +6 -3
  40. package/templates/yoga-studio-owner/dev/index.ts +44 -1
  41. package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-event/index.ts +52 -8
@@ -21,6 +21,8 @@ export interface WixServicePayment {
21
21
  }
22
22
  export interface WixService {
23
23
  id: string;
24
+ /** Optimistic-concurrency token — required by every v2 write (updateService reads it if omitted). */
25
+ revision?: string;
24
26
  name?: string;
25
27
  description?: string;
26
28
  /** Session length in minutes. */
@@ -84,13 +86,34 @@ export interface WixGetServiceResponse {
84
86
  /** Fetch a single service by id — GET /bookings/v2/services/{id}. Visitor-readable
85
87
  * (public GA route, not the owner `_api/` gateway). The detail/edit-prefill source. */
86
88
  export declare function getService(serviceId: string): Promise<WixGetServiceResponse>;
87
- /** Writable service fields for create/update. `name` + `duration` are required on create. */
89
+ /** Writable service fields for create/update, in the ERGONOMIC shape a form has. The helpers
90
+ * below encode it onto the Bookings v2 entity — they never send these flat fields as-is,
91
+ * because v2 silently ignores a flat `price`/`duration` (HTTP 200, revision bumps, nothing
92
+ * written: #3605, verified live 2026-10-01).
93
+ * - `price` → `payment.fixed.price.{value,currency}` (value is a decimal STRING), merged
94
+ * with the service's existing `payment` so `payment.options` (mandatory: online and/or
95
+ * in-person) and the currency carry over.
96
+ * - `duration` (minutes) → `schedule.availabilityConstraints.{durations,sessionDurations}`. */
88
97
  export interface WixServiceWrite {
89
98
  name?: string;
90
99
  description?: string;
91
100
  /** Session length in minutes; required, > 0 on create. */
92
101
  duration?: number;
102
+ /** Fixed price as a number (e.g. 120). Encoded onto `payment.fixed.price`. */
93
103
  price?: number;
104
+ /** ISO currency for `price`. Defaults to the service's current currency on update; required on create with a price. */
105
+ currency?: string;
106
+ /** Resource ids of the staff who deliver it (`queryStaff()` → `id`). An APPOINTMENT service needs ≥1;
107
+ * createService defaults to the site's `defaultStaff` resource when omitted. NOT staff-member ids. */
108
+ staffMemberIds?: string[];
109
+ /** v2 service type; createService defaults to "APPOINTMENT". */
110
+ type?: "APPOINTMENT" | "CLASS" | "COURSE";
111
+ onlineBooking?: {
112
+ enabled?: boolean;
113
+ };
114
+ /** Raw v2 payment object — wins over `price`/`currency` when both are given. */
115
+ payment?: Record<string, unknown>;
116
+ /** @deprecated v1 only — v2 services carry `category.id`; ignored by create/update. */
94
117
  category?: string;
95
118
  }
96
119
  export interface WixCreateServiceRequest {
@@ -107,7 +130,15 @@ export interface WixUpdateServiceResponse {
107
130
  }
108
131
  export interface WixDeleteServiceResponse {
109
132
  }
110
- /** Create a service — POST /bookings/v1/services. `name`, `duration` are required. */
133
+ /** Encode an ergonomic write onto the v2 service entity. `existing` (the current service, for
134
+ * an update) supplies the currency and `payment.options` a price change must carry. Exported
135
+ * for tests; throws instead of sending a payload Bookings would accept and then ignore. */
136
+ export declare function encodeServiceWrite(write: WixServiceWrite, existing?: WixService): Record<string, unknown>;
137
+ /** Create a service — POST /bookings/v2/services (the v1 route 400s for a v2 site: #3605).
138
+ * `name` + `duration` are required. Defaults: type APPOINTMENT, online booking on, and the
139
+ * site's `defaultStaff` resource when `staffMemberIds` is omitted (an APPOINTMENT service
140
+ * 400s MISSING_APPOINTMENT_RESOURCES without one), and — for a `price` without `currency` —
141
+ * the currency the site's existing services use. */
111
142
  export declare function createService(service: WixServiceWrite): Promise<WixCreateServiceResponse>;
112
143
  export declare function updateService(serviceId: string, service: WixServiceWrite & {
113
144
  revision?: string;
@@ -15,12 +15,17 @@ export interface WixDataPaging {
15
15
  limit?: number;
16
16
  offset?: number;
17
17
  }
18
+ /** One sort key — `{ fieldName: "postedDate", order: "DESC" }`. Keys apply in array order. */
19
+ export interface WixDataSort {
20
+ fieldName: string;
21
+ order?: "ASC" | "DESC";
22
+ }
18
23
  export interface WixQueryDataItemsRequest {
19
24
  dataCollectionId: string;
20
25
  query?: {
21
26
  filter?: Record<string, unknown>;
22
27
  paging?: WixDataPaging;
23
- sort?: unknown[];
28
+ sort?: WixDataSort[];
24
29
  };
25
30
  /** When true, `pagingMetadata.total` is filled (offset paging only). Default omitted. */
26
31
  returnTotalCount?: boolean;
@@ -72,9 +77,16 @@ export interface WixUpdateDataItemRequest {
72
77
  * const rest = await queryDataItems("Listings", 50, undefined, false, 50);
73
78
  * ```
74
79
  *
80
+ * `sort` orders server-side, so "newest first" is one call instead of fetching every row
81
+ * and sorting on the client (which also breaks once the collection outgrows `limit`):
82
+ *
83
+ * ```ts
84
+ * await queryDataItems("Jobs", 1, undefined, false, 0, [{ fieldName: "postedDate", order: "DESC" }]);
85
+ * ```
86
+ *
75
87
  * To read ONE row, use `getDataItem` rather than a page plus a client-side
76
88
  * `.find()` — that pattern breaks silently once the collection outgrows `limit`. */
77
- export declare function queryDataItems(dataCollectionId: string, limit?: number, filter?: Record<string, unknown>, returnTotalCount?: boolean, offset?: number): Promise<WixQueryDataItemsResponse>;
89
+ export declare function queryDataItems(dataCollectionId: string, limit?: number, filter?: Record<string, unknown>, returnTotalCount?: boolean, offset?: number, sort?: WixDataSort[]): Promise<WixQueryDataItemsResponse>;
78
90
  /** Read ONE item by id — GET /wix-data/v2/items/{dataItemId}?dataCollectionId=…
79
91
  *
80
92
  * Reach for this on a detail screen. Before it existed the only read this package
@@ -23,12 +23,77 @@ async function getService(serviceId) {
23
23
  const res = await wixCall("GET", `/bookings/v2/services/${serviceId}`);
24
24
  return { ...res, service: decodeService(res.service) };
25
25
  }
26
- function createService(service) {
27
- return wixCall("POST", "/bookings/v1/services", { service });
26
+ function encodeServiceWrite(write, existing) {
27
+ const { price, currency, duration, payment, category: _legacyCategory, ...rest } = write;
28
+ const out = { ...rest };
29
+ if (payment) {
30
+ out.payment = payment;
31
+ } else if (price != null) {
32
+ if (!Number.isFinite(price) || price < 0) throw new Error(`Bookings service price must be a number >= 0 (got ${price})`);
33
+ const prior = existing?.payment ?? {};
34
+ const cur = currency ?? prior.fixed?.price?.currency ?? prior.rateLabel?.currency ?? prior.currency;
35
+ if (!cur) throw new Error('Bookings service price needs a currency: pass `currency` (e.g. "USD") \u2014 the service has none to inherit');
36
+ const options = prior.options;
37
+ const { price: _flat, currency: _flatCur, ...priorRest } = prior;
38
+ out.payment = {
39
+ ...priorRest,
40
+ rateType: "FIXED",
41
+ fixed: { price: { value: String(price), currency: cur } },
42
+ // v2 400s unless online or inPerson is true; keep the service's own choice when it has one.
43
+ options: options && (options.online || options.inPerson) ? options : { ...options ?? {}, online: true }
44
+ };
45
+ }
46
+ if (duration != null) {
47
+ if (!Number.isFinite(duration) || duration <= 0) throw new Error(`Bookings service duration must be > 0 minutes (got ${duration})`);
48
+ out.schedule = { availabilityConstraints: { durations: [{ minutes: duration }], sessionDurations: [duration] } };
49
+ }
50
+ return out;
51
+ }
52
+ async function siteServiceCurrency() {
53
+ const PAGE = 100;
54
+ for (let offset = 0; offset < 500; offset += PAGE) {
55
+ const { services, pagingMetadata } = await queryServices({ query: { paging: { limit: PAGE, offset } } });
56
+ for (const x of services) {
57
+ const p = x.payment;
58
+ const cur = p?.fixed?.price?.currency ?? p?.varied?.defaultPrice?.currency ?? p?.varied?.minPrice?.currency ?? p?.varied?.maxPrice?.currency ?? p?.subscription?.amountPerBillingCycle?.currency ?? p?.subscription?.enrollmentFeeAmount?.currency ?? p?.rateLabel?.currency ?? p?.currency;
59
+ if (cur) return cur;
60
+ }
61
+ const meta = pagingMetadata;
62
+ if (services.length < PAGE || meta?.hasNext === false) return void 0;
63
+ }
64
+ return void 0;
28
65
  }
29
- function updateService(serviceId, service) {
66
+ async function createService(service) {
67
+ if (!service?.name) throw new Error("createService: name is required");
68
+ if (service.duration == null) throw new Error("createService: duration (minutes) is required");
69
+ const type = service.type ?? "APPOINTMENT";
70
+ let staffMemberIds = service.staffMemberIds;
71
+ if (type === "APPOINTMENT" && !staffMemberIds?.length) {
72
+ const staff = await queryStaff();
73
+ const pick = staff.find((r) => (r.tags ?? []).includes("defaultStaff")) ?? staff[0];
74
+ if (!pick?.id) throw new Error("createService: an APPOINTMENT service needs a staff member, and this site has none \u2014 add staff in Bookings, or pass staffMemberIds (resource ids from queryStaff())");
75
+ staffMemberIds = [pick.id];
76
+ }
77
+ let currency = service.currency;
78
+ if (service.price != null && !service.payment && !currency) currency = await siteServiceCurrency();
79
+ const body = encodeServiceWrite({ ...service, type, ...currency ? { currency } : {}, ...staffMemberIds ? { staffMemberIds } : {} });
80
+ if (!body.payment) body.payment = { rateType: "NO_FEE", options: { inPerson: true } };
81
+ const res = await wixCall("POST", "/bookings/v2/services", {
82
+ service: { onlineBooking: { enabled: true }, ...body }
83
+ });
84
+ return { ...res, service: decodeService(res.service) };
85
+ }
86
+ async function updateService(serviceId, service) {
30
87
  if (!serviceId) throw new Error("updateService: serviceId is required");
31
- return wixCall("PATCH", `/bookings/v2/services/${serviceId}`, { service: { id: serviceId, ...service } });
88
+ const { revision, ...write } = service;
89
+ const needsCurrent = !revision || write.price != null && !write.payment;
90
+ const current = needsCurrent ? (await getService(serviceId)).service : void 0;
91
+ const rev = revision ?? current?.revision;
92
+ if (!rev) throw new Error(`updateService: could not read the current revision of service ${serviceId}`);
93
+ const res = await wixCall("PATCH", `/bookings/v2/services/${serviceId}`, {
94
+ service: { id: serviceId, revision: rev, ...encodeServiceWrite(write, current) }
95
+ });
96
+ return { ...res, service: decodeService(res.service) };
32
97
  }
33
98
  function confirmBooking(bookingId, revision) {
34
99
  if (!bookingId || !revision) throw new Error("confirmBooking: bookingId + revision required");
@@ -198,7 +263,7 @@ function deleteService(serviceId, opts) {
198
263
  if (!serviceId) throw new Error("deleteService: serviceId is required");
199
264
  return wixCall(
200
265
  "DELETE",
201
- `/bookings/v1/services/${encodeURIComponent(serviceId)}`,
266
+ `/bookings/v2/services/${encodeURIComponent(serviceId)}`,
202
267
  void 0,
203
268
  opts
204
269
  );
@@ -213,6 +278,7 @@ export {
213
278
  declineBooking,
214
279
  decodeService,
215
280
  deleteService,
281
+ encodeServiceWrite,
216
282
  getBooking,
217
283
  getBookingFull,
218
284
  getService,
@@ -7,7 +7,7 @@ function wixCall(method, path, body) {
7
7
  }
8
8
  return globalThis.himalaya.wixApi.call(method, path, body);
9
9
  }
10
- function queryDataItems(dataCollectionId, limit = 50, filter, returnTotalCount = false, offset = 0) {
10
+ function queryDataItems(dataCollectionId, limit = 50, filter, returnTotalCount = false, offset = 0, sort) {
11
11
  if (!dataCollectionId) throw new Error("queryDataItems: dataCollectionId is required");
12
12
  const body = {
13
13
  dataCollectionId,
@@ -15,7 +15,8 @@ function queryDataItems(dataCollectionId, limit = 50, filter, returnTotalCount =
15
15
  // `offset` is omitted when 0 so a caller that does not page sends the byte-identical
16
16
  // body it sent before this parameter existed.
17
17
  paging: { limit, ...offset ? { offset } : {} },
18
- ...filter ? { filter } : {}
18
+ ...filter ? { filter } : {},
19
+ ...sort?.length ? { sort: sort.map((k) => ({ fieldName: k.fieldName, order: k.order ?? "ASC" })) } : {}
19
20
  },
20
21
  ...returnTotalCount ? { returnTotalCount: true } : {}
21
22
  };
package/dist/cli.mjs CHANGED
@@ -6765,7 +6765,6 @@ function walk(node, registry, path2, out2) {
6765
6765
  var SYSTEM_FONT_FAMILY, TONAL_ELEVATION_ALPHA;
6766
6766
  var init_tokens = __esm({
6767
6767
  "../../core/ts/src/tokens.ts"() {
6768
- "use strict";
6769
6768
  SYSTEM_FONT_FAMILY = "$system";
6770
6769
  TONAL_ELEVATION_ALPHA = [0, 0.05, 0.08, 0.11, 0.12, 0.14];
6771
6770
  }
@@ -6792,7 +6791,6 @@ function resolveTranslation(value, catalog, sourceCatalog) {
6792
6791
  var BINDING_SIGILS, BINDING_SIGIL_RE, TRANSLATION_PREFIX, TRANSLATION_KEY_RE;
6793
6792
  var init_bindings = __esm({
6794
6793
  "../../core/ts/src/bindings.ts"() {
6795
- "use strict";
6796
6794
  BINDING_SIGILS = ["service", "flag", "item", "t"];
6797
6795
  BINDING_SIGIL_RE = new RegExp(
6798
6796
  `^\\$(?:${BINDING_SIGILS.filter((s) => s !== "t").join("|")})\\b|^\\$t:`
@@ -21566,6 +21564,7 @@ __export(template_adopt_exports, {
21566
21564
  ADOPT_MATRIX_FILE: () => ADOPT_MATRIX_FILE,
21567
21565
  JOURNEY_STUB_FILE: () => JOURNEY_STUB_FILE,
21568
21566
  PLACEHOLDER: () => PLACEHOLDER,
21567
+ addScreen: () => addScreen,
21569
21568
  classifyAction: () => classifyAction,
21570
21569
  copyHits: () => copyHits,
21571
21570
  droppedPrimaryTaps: () => droppedPrimaryTaps,
@@ -21832,7 +21831,7 @@ function inventorySummary(inv) {
21832
21831
  function writeNoFollow(dir, rel, body) {
21833
21832
  const p = join34(dir, rel);
21834
21833
  const parent = dirname20(p);
21835
- if (existsSync32(parent) && lstatSync8(parent).isSymbolicLink()) throw new TrimRefusal(`${dirname20(rel)} is a symlink \u2014 refusing to write the adoption inventory through it`);
21834
+ if (existsSync32(parent) && lstatSync8(parent).isSymbolicLink()) throw new TrimRefusal(`${dirname20(rel)} is a symlink \u2014 refusing to write through it`);
21836
21835
  mkdirSync17(parent, { recursive: true });
21837
21836
  if (existsSync32(p) && lstatSync8(p).isSymbolicLink()) throw new TrimRefusal(`${rel} is a symlink \u2014 refusing to write through it`);
21838
21837
  const tmp = `${p}.${process.pid}.${Date.now()}.tmp`;
@@ -21890,6 +21889,50 @@ function replaceScreen(dir, id, entry) {
21890
21889
  writeFileSync15(devPath, next);
21891
21890
  return { ok: true, screen: id, bytes: body.length };
21892
21891
  }
21892
+ function addScreen(dir, id, entry, opts = {}) {
21893
+ if (!/^[a-z][a-z0-9_]*$/.test(id)) throw new TrimRefusal(`screen id "${id}" must be lowercase snake_case (e.g. himi_sellers)`);
21894
+ const src = readAppSources(dir);
21895
+ const app = parseApp(src.dev, src.config);
21896
+ if (findRoute(app, id)) throw new TrimRefusal(`"${id}" already exists \u2014 edit it with \`himi template screens --write ${id} --from <file>\``);
21897
+ const lastScreen = [...app.routes].filter((r) => r.kind === "screen").sort((a, b) => b.end - a.end)[0];
21898
+ if (!lastScreen) throw new TrimRefusal("dev/index.ts has no SCREENS entry to add after");
21899
+ const body = entry.endsWith("\n") ? entry : `${entry}
21900
+ `;
21901
+ const dev = src.dev.slice(0, lastScreen.end) + body + src.dev.slice(lastScreen.end);
21902
+ const nav = JSON.parse(JSON.stringify(app.nav.value));
21903
+ if (nav.linkableScreens === void 0) nav.linkableScreens = [];
21904
+ if (!Array.isArray(nav.linkableScreens)) throw new TrimRefusal("config.ts navigation.linkableScreens is not an array \u2014 fix it before adding a screen");
21905
+ const linkable = true;
21906
+ if (!nav.linkableScreens.some((l) => l.screenId === id)) {
21907
+ const parent = opts.parent ? nav.linkableScreens.find((l) => l.screenId === opts.parent) : void 0;
21908
+ if (opts.parent && !parent) throw new TrimRefusal(`--parent "${opts.parent}" is not a linkable screen in config.ts`);
21909
+ nav.linkableScreens.push({
21910
+ screenId: id,
21911
+ isPublic: true,
21912
+ paramKeys: [],
21913
+ parentScreenId: opts.parent ?? "",
21914
+ tabId: opts.tab ?? (typeof parent?.tabId === "string" ? parent.tabId : "")
21915
+ });
21916
+ }
21917
+ const config = linkable ? src.config.slice(0, app.nav.open) + serializeAt(nav, src.config, app.nav.open) + src.config.slice(app.nav.close) : src.config;
21918
+ let reparsed;
21919
+ try {
21920
+ reparsed = parseApp(dev, config);
21921
+ } catch (e) {
21922
+ throw new TrimRefusal(`the new entry does not parse, nothing written: ${e.message}`);
21923
+ }
21924
+ if (!findRoute(reparsed, id)) throw new TrimRefusal(`the entry does not declare "${id}" (its key and "screenId" must both be "${id}") \u2014 nothing written`);
21925
+ if (reparsed.routes.length !== app.routes.length + 1) {
21926
+ throw new TrimRefusal(`the entry changed the screen count by ${reparsed.routes.length - app.routes.length}, not 1; add one screen at a time \u2014 nothing written`);
21927
+ }
21928
+ for (const rel of ["dev", "dev/index.ts", ...config !== src.config ? ["config.ts"] : []]) {
21929
+ const p = join34(dir, rel);
21930
+ if (existsSync32(p) && lstatSync8(p).isSymbolicLink()) throw new TrimRefusal(`${rel} is a symlink \u2014 refusing to write through it (nothing written)`);
21931
+ }
21932
+ writeNoFollow(dir, "dev/index.ts", dev);
21933
+ if (config !== src.config) writeNoFollow(dir, "config.ts", config);
21934
+ return { ok: true, screen: id, bytes: body.length, linkable };
21935
+ }
21893
21936
  function specScreens(spec) {
21894
21937
  const body = spec.replace(/<!--[\s\S]*?(?:-->|$)/g, "");
21895
21938
  const m = /(?:^|\n)##[ \t]+Screen catalogue\b[^\n]*([\s\S]*?)(?=\n##[ \t]|$)/i.exec(body);
@@ -21930,6 +21973,7 @@ var ADOPT_FILE, JOURNEY_STUB_FILE, ADOPT_MATRIX_FILE, WIX_MODULE, RELATIVE_IMPOR
21930
21973
  var init_template_adopt = __esm({
21931
21974
  "src/template-adopt.ts"() {
21932
21975
  init_template_trim();
21976
+ init_home_descriptor();
21933
21977
  ADOPT_FILE = ".himi/adopt.json";
21934
21978
  JOURNEY_STUB_FILE = ".himi/himi-test.stub.json";
21935
21979
  ADOPT_MATRIX_FILE = ".himi/adopt-matrix.md";
@@ -23592,7 +23636,6 @@ function shouldRevealError(args) {
23592
23636
  var EMAIL_RE, URL_HOST_RE, INTEGER_RE, NUMBER_RE;
23593
23637
  var init_validation = __esm({
23594
23638
  "../../core/ts/src/validation.ts"() {
23595
- "use strict";
23596
23639
  EMAIL_RE = /^[A-Za-z0-9._%+-]+@[A-Za-z0-9-]+(\.[A-Za-z0-9-]+)*\.[A-Za-z]{2,}$/;
23597
23640
  URL_HOST_RE = /^[A-Za-z0-9-]+(\.[A-Za-z0-9-]+)*(:[0-9]+)?$/;
23598
23641
  INTEGER_RE = /^[+-]?[0-9]+$/;
@@ -24252,7 +24295,6 @@ function validateWorkerSubtree(raw, slotId, policy, actionCatalog) {
24252
24295
  var WORKER_SUBTREE_CEILINGS, ID_PART, ACTION_ID;
24253
24296
  var init_worker_subtree = __esm({
24254
24297
  "../../core/ts/src/worker-subtree.ts"() {
24255
- "use strict";
24256
24298
  init_worker_subtree_safe_types_generated();
24257
24299
  WORKER_SUBTREE_CEILINGS = Object.freeze({
24258
24300
  maxEncodedBytes: 65536,
@@ -38729,7 +38771,6 @@ var init_wix_cache_defaults_generated = __esm({
38729
38771
  { method: "DELETE", pathPrefix: "/blog/v3/posts", cacheable: false, ttl: 0, swr: 0, tag: "blog" },
38730
38772
  { method: "PATCH", pathPrefix: "/blog/v3/posts", cacheable: false, ttl: 0, swr: 0, tag: "blog" },
38731
38773
  { method: "POST", pathPrefix: "/blog/v3/posts", cacheable: false, ttl: 0, swr: 0, tag: "blog" },
38732
- { method: "DELETE", pathPrefix: "/bookings/v1/services", cacheable: false, ttl: 0, swr: 0, tag: "bookings/services" },
38733
38774
  { method: "POST", pathPrefix: "/bookings/v1/services", cacheable: false, ttl: 0, swr: 0, tag: "bookings/services" },
38734
38775
  { method: "PUT", pathPrefix: "/bookings/v1/services", cacheable: false, ttl: 0, swr: 0, tag: "bookings/services" },
38735
38776
  { method: "DELETE", pathPrefix: "/bookings/v2/services", cacheable: false, ttl: 0, swr: 0, tag: "bookings/services" },
@@ -46605,7 +46646,37 @@ function countingWixCall(base) {
46605
46646
  }
46606
46647
  async function liveRenderServices(bundleJs, opts) {
46607
46648
  const bus = hostDispatch(opts.localDbSchema, declaredCapabilitiesOf(opts.descriptor));
46608
- return loadBundleServicesLive(bundleJs, {
46649
+ const appear = rootOnAppear(opts.descriptor);
46650
+ const fired = appear ? parseWorkerAction(appear.actionId) : null;
46651
+ if (fired) {
46652
+ const w = await bootBundleWorker(bundleJs, { ...liveBootOptions(opts, bus), skipLoad: true });
46653
+ try {
46654
+ const handle = w.handles[fired.service];
46655
+ if (handle && handle.actionNames().includes(fired.action)) {
46656
+ try {
46657
+ await handle.dispatch(fired.action, ...workerArgs(appear));
46658
+ } catch (e) {
46659
+ w.errors.push({ service: fired.service, message: `onAppear ${fired.action}: ${e?.message ?? String(e)}` });
46660
+ }
46661
+ } else {
46662
+ for (const [service, h] of Object.entries(w.handles)) {
46663
+ if (!h.actionNames().includes("load")) continue;
46664
+ try {
46665
+ await h.dispatch("load");
46666
+ } catch (e) {
46667
+ w.errors.push({ service, message: e?.message ?? String(e) });
46668
+ }
46669
+ }
46670
+ }
46671
+ return { services: snapshot(w).state, calls: w.calls, errors: w.errors };
46672
+ } finally {
46673
+ w.dispose();
46674
+ }
46675
+ }
46676
+ return loadBundleServicesLive(bundleJs, liveBootOptions(opts, bus));
46677
+ }
46678
+ function liveBootOptions(opts, bus) {
46679
+ return {
46609
46680
  wixCall: opts.wixCall,
46610
46681
  input: opts.input,
46611
46682
  budgetMs: opts.budgetMs,
@@ -46619,7 +46690,7 @@ async function liveRenderServices(bundleJs, opts) {
46619
46690
  // holding this ONE verb on `null` keeps the previous behaviour while the capability bus
46620
46691
  // joins. Caught in pre-PR review; revisit when --live can mint a member token.
46621
46692
  dispatch: (action, params) => MEMBER_IDENTITY_ACTION.test(action) ? null : bus(action, params)
46622
- });
46693
+ };
46623
46694
  }
46624
46695
  async function buildReleaseFromContent(contentDir2, dest, opts = {}) {
46625
46696
  const { freshModule, ...rest } = opts;
@@ -59116,21 +59187,21 @@ var init_preview_assets_generated = __esm({
59116
59187
  "../../packages/serve/src/preview-assets.generated.ts"() {
59117
59188
  init_preview_assets_format();
59118
59189
  PREVIEW_ASSET_MANIFEST = [
59119
- { key: "assets/index-DHaDddKI.js", contentType: "application/javascript; charset=utf-8", offset: 8, length: 1118974, sha256: "126f946f40d25539e528fd40423974a6dab7231f6ccc0159108c5b7d4f2cdecb" },
59120
- { key: "fonts/Roboto-LICENSE.txt", contentType: "application/octet-stream", offset: 1118982, length: 4610, sha256: "994062dd7af4186a739de55dd9e151c63b6301f8e2cbafc608d35c2e87da0cd2" },
59121
- { key: "fonts/Roboto-Variable.ttf", contentType: "font/ttf", offset: 1123592, length: 488584, sha256: "d7598e12c5dbef095ff8272cfc55da0250bd07fbdecbac8a530b9b277872a134" },
59122
- { key: "fonts/WixMadeforDisplay-Bold.ttf", contentType: "font/ttf", offset: 1612176, length: 90924, sha256: "ec4c7d63772d980d6e147b2df6215fd2737e4b04cebe0cf7e11dc695fb63cdcf" },
59123
- { key: "fonts/WixMadeforDisplay-ExtraBold.ttf", contentType: "font/ttf", offset: 1703100, length: 90984, sha256: "bf4c16a2774edd38a921edf3099469866cd74c094fd596f3fa4e81f54a04dfaa" },
59124
- { key: "fonts/WixMadeforDisplay-Medium.ttf", contentType: "font/ttf", offset: 1794084, length: 90980, sha256: "368faca44fb9f7207e4af784e94750b1ee59c1098b8689e0f4a918926f244fb5" },
59125
- { key: "fonts/WixMadeforDisplay-Regular.ttf", contentType: "font/ttf", offset: 1885064, length: 90872, sha256: "97ed8f1a6772abe26e3d8af5cecfc4b02faa94497d585b0398b5053b739a70c1" },
59126
- { key: "fonts/WixMadeforDisplay-SemiBold.ttf", contentType: "font/ttf", offset: 1975936, length: 90980, sha256: "642feab8a48ddf6c7a89d7c2a7c0edfa0e6e773f671287e460e222703563191e" },
59127
- { key: "fonts/WixMadeforText-Bold.ttf", contentType: "font/ttf", offset: 2066916, length: 108636, sha256: "37c179b4fefb63ea9637322b84583a2df53aa226c150294eea1675c14b1b8e29" },
59128
- { key: "fonts/WixMadeforText-ExtraBold.ttf", contentType: "font/ttf", offset: 2175552, length: 109084, sha256: "62e2fe4718129f36ecdb0ae4de385bd6b81ecc876146447167c6c140fad699a9" },
59129
- { key: "fonts/WixMadeforText-Medium.ttf", contentType: "font/ttf", offset: 2284636, length: 109048, sha256: "4ecf270136af928612c76fe4ebade91d8156975e5ea4c0cf9225501dd1788e02" },
59130
- { key: "fonts/WixMadeforText-Regular.ttf", contentType: "font/ttf", offset: 2393684, length: 107700, sha256: "dee56f09fd3efc127c1ea95557edc74110c1460a46287da36e0af25efd341ef5" },
59131
- { key: "fonts/WixMadeforText-SemiBold.ttf", contentType: "font/ttf", offset: 2501384, length: 88108, sha256: "dced97a77624bc5f8b1ae65850360908297f4588e09feb6f22792d29dc7e765b" },
59132
- { key: "index.html", contentType: "text/html; charset=utf-8", offset: 2589492, length: 4947, sha256: "89671c9093bca734f83172536bbf14aed6f119a28cd677e45c09195058c78264" },
59133
- { key: "t5-frame.html", contentType: "text/html; charset=utf-8", offset: 2594439, length: 17344, sha256: "d057298d75e7373e5e2c4d11b7d1bcbea88e03af555ba63d97641c78509c58e4" }
59190
+ { key: "assets/index-DHO0WElX.js", contentType: "application/javascript; charset=utf-8", offset: 8, length: 1127482, sha256: "9a198f7af12c683263ba55641ec1802d4ca9addb9b8d38170e6b4f60387c61c3" },
59191
+ { key: "fonts/Roboto-LICENSE.txt", contentType: "application/octet-stream", offset: 1127490, length: 4610, sha256: "994062dd7af4186a739de55dd9e151c63b6301f8e2cbafc608d35c2e87da0cd2" },
59192
+ { key: "fonts/Roboto-Variable.ttf", contentType: "font/ttf", offset: 1132100, length: 488584, sha256: "d7598e12c5dbef095ff8272cfc55da0250bd07fbdecbac8a530b9b277872a134" },
59193
+ { key: "fonts/WixMadeforDisplay-Bold.ttf", contentType: "font/ttf", offset: 1620684, length: 90924, sha256: "ec4c7d63772d980d6e147b2df6215fd2737e4b04cebe0cf7e11dc695fb63cdcf" },
59194
+ { key: "fonts/WixMadeforDisplay-ExtraBold.ttf", contentType: "font/ttf", offset: 1711608, length: 90984, sha256: "bf4c16a2774edd38a921edf3099469866cd74c094fd596f3fa4e81f54a04dfaa" },
59195
+ { key: "fonts/WixMadeforDisplay-Medium.ttf", contentType: "font/ttf", offset: 1802592, length: 90980, sha256: "368faca44fb9f7207e4af784e94750b1ee59c1098b8689e0f4a918926f244fb5" },
59196
+ { key: "fonts/WixMadeforDisplay-Regular.ttf", contentType: "font/ttf", offset: 1893572, length: 90872, sha256: "97ed8f1a6772abe26e3d8af5cecfc4b02faa94497d585b0398b5053b739a70c1" },
59197
+ { key: "fonts/WixMadeforDisplay-SemiBold.ttf", contentType: "font/ttf", offset: 1984444, length: 90980, sha256: "642feab8a48ddf6c7a89d7c2a7c0edfa0e6e773f671287e460e222703563191e" },
59198
+ { key: "fonts/WixMadeforText-Bold.ttf", contentType: "font/ttf", offset: 2075424, length: 108636, sha256: "37c179b4fefb63ea9637322b84583a2df53aa226c150294eea1675c14b1b8e29" },
59199
+ { key: "fonts/WixMadeforText-ExtraBold.ttf", contentType: "font/ttf", offset: 2184060, length: 109084, sha256: "62e2fe4718129f36ecdb0ae4de385bd6b81ecc876146447167c6c140fad699a9" },
59200
+ { key: "fonts/WixMadeforText-Medium.ttf", contentType: "font/ttf", offset: 2293144, length: 109048, sha256: "4ecf270136af928612c76fe4ebade91d8156975e5ea4c0cf9225501dd1788e02" },
59201
+ { key: "fonts/WixMadeforText-Regular.ttf", contentType: "font/ttf", offset: 2402192, length: 107700, sha256: "dee56f09fd3efc127c1ea95557edc74110c1460a46287da36e0af25efd341ef5" },
59202
+ { key: "fonts/WixMadeforText-SemiBold.ttf", contentType: "font/ttf", offset: 2509892, length: 88108, sha256: "dced97a77624bc5f8b1ae65850360908297f4588e09feb6f22792d29dc7e765b" },
59203
+ { key: "index.html", contentType: "text/html; charset=utf-8", offset: 2598e3, length: 4947, sha256: "72e531f20412d0f07433cb52fac32ff7f5be87a4ef435c2d919cd5e21ec84ad3" },
59204
+ { key: "t5-frame.html", contentType: "text/html; charset=utf-8", offset: 2602947, length: 17344, sha256: "d057298d75e7373e5e2c4d11b7d1bcbea88e03af555ba63d97641c78509c58e4" }
59134
59205
  ];
59135
59206
  }
59136
59207
  });
@@ -75883,7 +75954,7 @@ function auditUx(content, strict = false) {
75883
75954
  if (usesAppNetwork(source)) {
75884
75955
  const mocksPath = join25(dir, "dev", "net-mocks.json");
75885
75956
  if (!existsSync22(mocksPath)) {
75886
- issue(issues, strict, "missing-app-mocks", "This package uses a Wix/network client but has no app-specific dev/net-mocks.json. Add fixtures for dynamic, entitlement, or recovery flows.", "dev/net-mocks.json");
75957
+ issue(issues, strict, "missing-app-mocks", "This package uses a Wix/network client but has no app-specific dev/net-mocks.json. `himi test --record` writes one from the live responses it sees; then add fixtures for dynamic, entitlement, or recovery flows.", "dev/net-mocks.json");
75887
75958
  } else {
75888
75959
  try {
75889
75960
  const parsed2 = JSON.parse(readFileSync22(mocksPath, "utf8"));
@@ -76117,10 +76188,11 @@ var USAGE = `himi \u2014 the Himalaya authoring CLI (remote dev loop)
76117
76188
  before data consent), placeholder + sample copy, the proposed Data access rows
76118
76189
  and typed-journey stubs. Writes .himi/adopt.json, .himi/adopt-matrix.md and
76119
76190
  .himi/himi-test.stub.json (--dry-run writes nothing)
76120
- himi template screens [--show <id>] [--write <id> --from <file>] [--content <dir>]
76191
+ himi template screens [--show <id>] [--write <id> --from <file>] [--add <id> --from <file> [--parent <id>] [--tab <id>]] [--content <dir>]
76121
76192
  list dev/index.ts screens (size, workers, onAppear); --show prints ONE entry so
76122
76193
  nobody reads the whole file; --write puts an edited entry back (re-parsed, refused
76123
76194
  if it breaks the file or changes the screen count)
76195
+ --add appends a NEW screen entry and registers it in linkableScreens (--parent/--tab)
76124
76196
  himi template sweep --set "Old=>New" [--set \u2026] | --map <file.json> [--content <dir>] [--dry-run]
76125
76197
  replace the template's sample identity/copy inside string literals only (never
76126
76198
  identifiers) across dev/index.ts, config.ts, tokens.json and tier5-src; re-parses
@@ -76801,7 +76873,7 @@ WHAT A PATCH DOES NOT CARRY \u2014 all reported in the output, never silent
76801
76873
  var COMMANDS2 = [
76802
76874
  { 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>]" },
76803
76875
  { 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 --mocks --device iphone` (offline \u2014 no request; never --live; --mocks loads the app's recorded data when it has any), 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 a `CI` environment, a test runner, a watch already running, or a start that failed (never a failed init). Nothing else skips it: outside CI it is started, and `watch.note` says an opt-out was ignored. 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] | --templates" },
76804
- { 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). Adopting one: `inspect` writes the adoption inventory (.himi/adopt.json: screens, workers, which screens can call Wix/network on appear \u2014 bootsOffline \u2014 placeholder and sample copy, proposed Data access rows, typed-journey stubs); `screens` lists, shows or rewrites ONE dev/index.ts entry; `sweep` replaces sample identity/copy inside string literals; `recommend` ranks the catalog for a business description or a site's verticals; `trim --from-spec` takes the kept screens from SPEC.md's Screen catalogue", 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>|--from-spec [SPEC.md] [--content <dir>] [--links follow|drop] [--dry-run] [--force] | inspect [--content <dir>] [--dry-run] | screens [--show <id>] [--write <id> --from <file>] | sweep --set 'Old=>New' | --map <file> [--dry-run] | recommend [--text <t>] [--verticals a,b] [--book <dir>] [--remote]" },
76876
+ { 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). Adopting one: `inspect` writes the adoption inventory (.himi/adopt.json: screens, workers, which screens can call Wix/network on appear \u2014 bootsOffline \u2014 placeholder and sample copy, proposed Data access rows, typed-journey stubs); `screens` lists, shows, rewrites or adds ONE dev/index.ts entry; `sweep` replaces sample identity/copy inside string literals; `recommend` ranks the catalog for a business description or a site's verticals; `trim --from-spec` takes the kept screens from SPEC.md's Screen catalogue", 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>|--from-spec [SPEC.md] [--content <dir>] [--links follow|drop] [--dry-run] [--force] | inspect [--content <dir>] [--dry-run] | screens [--show <id>] [--write <id> --from <file>] [--add <id> --from <file>] | sweep --set 'Old=>New' | --map <file> [--dry-run] | recommend [--text <t>] [--verticals a,b] [--book <dir>] [--remote]" },
76805
76877
  { 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]" },
76806
76878
  { 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>]" },
76807
76879
  { name: "eject", summary: "take ownership of a shared flow module (auth|checkout|screens): copies its source into tier5-src/_ejected/ and repoints this package's imports at the copy. The ladder's escape hatch \u2014 for when the factory options cannot express what you need. AFTER EJECTING, CENTRAL FIXES NO LONGER REACH THIS APP, including future corrections to the fail-closed entitlement and confirm-the-session rules. The floor is unaffected: credentials still go only through the native auth actions and money only through the cashier rail.", required: "<auth|checkout|screens> [--content <dir>]" },
@@ -77944,7 +78016,13 @@ async function adoptAtInit(dir) {
77944
78016
  writeInventory2(dir, inv);
77945
78017
  return { adopt: inventorySummary2(inv) };
77946
78018
  } catch (e) {
77947
- return { adopt: { skipped: e.message } };
78019
+ const msg = e.message;
78020
+ const layout = /SCREENS = runtimeSetting/.test(msg);
78021
+ return {
78022
+ adopt: {
78023
+ skipped: layout ? "no adoption inventory for this template: its dev/index.ts is not in the generated SCREENS layout, so `himi template inspect | screens | trim` do not apply to it \u2014 edit its source directly" : `no adoption inventory: ${msg}`
78024
+ }
78025
+ };
77948
78026
  }
77949
78027
  }
77950
78028
  function keptPlan(kept) {
@@ -79035,6 +79113,13 @@ ${doNow.map((l) => ` ${l}`).join("\n")}`);
79035
79113
  const app = parseApp2(src.dev, src.config);
79036
79114
  const show = str9(flags.show);
79037
79115
  const target = str9(flags.write);
79116
+ const adding = str9(flags.add);
79117
+ if (adding) {
79118
+ const from = str9(flags.from);
79119
+ if (!from) throw new TrimRefusal2('--add <id> needs --from <file> holding the whole new SCREENS entry (` "<id>": { "screenId": "<id>", \u2026 },`)');
79120
+ print(adopt.addScreen(dir, adding, readFileSync88(from, "utf8"), { parent: str9(flags.parent), tab: str9(flags.tab) }));
79121
+ return 0;
79122
+ }
79038
79123
  if (target) {
79039
79124
  const from = str9(flags.from);
79040
79125
  if (!from) throw new TrimRefusal2("--write <id> needs --from <file> holding the whole entry, as --show prints it");
@@ -83468,6 +83553,10 @@ ${label2}`);
83468
83553
  const deadline = Date.now() + timeoutMs;
83469
83554
  const pending = new Map(jobs.map((j) => [String(j.jobId), String(j.platform)]));
83470
83555
  const done = [];
83556
+ const statusCmd = (id) => `himi dist status ${id}${str9(flags.target) ? ` --target ${str9(flags.target)}` : ""}`;
83557
+ for (const j of jobs) (flags.json === true ? io.error : io.log)(` ${j.platform} dispatched as ${j.jobId}`);
83558
+ const MAX_POLL_ERRORS = 3;
83559
+ const pollErrors = /* @__PURE__ */ new Map();
83471
83560
  while (pending.size > 0) {
83472
83561
  if (Date.now() >= deadline) {
83473
83562
  await printDistQrsForJobs(done);
@@ -83483,9 +83572,32 @@ ${label2}`);
83483
83572
  }
83484
83573
  await new Promise((res) => setTimeout(res, intervalMs));
83485
83574
  for (const jobId of [...pending.keys()]) {
83486
- const s = await c.distStatus(jobId);
83575
+ let s;
83576
+ let why = "";
83577
+ try {
83578
+ s = await c.distStatus(jobId);
83579
+ if (!s.ok) why = `HTTP ${s.status}`;
83580
+ } catch (e) {
83581
+ why = e.message;
83582
+ }
83583
+ if (why) {
83584
+ const n = (pollErrors.get(jobId) ?? 0) + 1;
83585
+ pollErrors.set(jobId, n);
83586
+ if (n < MAX_POLL_ERRORS) continue;
83587
+ await printDistQrsForJobs(done);
83588
+ print({
83589
+ ok: false,
83590
+ error: "dist_poll_failed",
83591
+ message: `could not read the status of ${pending.get(jobId)} job ${jobId} ${MAX_POLL_ERRORS} times in a row (${why}) \u2014 the build${pending.size > 1 ? "s were" : " was"} dispatched and may still finish`,
83592
+ pending: [...pending].map(([id, platform2]) => ({ platform: platform2, jobId: id, check: statusCmd(id) })),
83593
+ finished: flags.json === true ? done : done.map(humanDistJob),
83594
+ ...failures.length ? { failures } : {}
83595
+ });
83596
+ return 1;
83597
+ }
83598
+ pollErrors.delete(jobId);
83487
83599
  const rec = s.json ?? {};
83488
- if (!s.ok || !TERMINAL_DIST_STATUSES.has(String(rec.status))) continue;
83600
+ if (!TERMINAL_DIST_STATUSES.has(String(rec.status))) continue;
83489
83601
  pending.delete(jobId);
83490
83602
  done.push(rec);
83491
83603
  if (!flags.json) {
Binary file
@@ -25,7 +25,10 @@ must be impossible for it to touch something it didn't create.
25
25
  | **Adopted** | `wixId: "<existing id>"` | **read only** — never create, never alter schema, never seed | CMS data the site already has |
26
26
 
27
27
  An owned id must be exactly **`himi_<appSlug>_<name>`**, where `appSlug` is the
28
- content-package app id with `-` folded to `_` and lowercased (`my-app` → `himi_my_app_`),
28
+ content package's **`name` in `himalaya.content.json`** — the name you passed to `himi init`,
29
+ or the folder's name when there is no manifest; **not** the dotted `id` in `config.ts` — with
30
+ `-` folded to `_` and lowercased (`my-app` → `himi_my_app_`). `himi cms create --dry-run`
31
+ prints the resolved prefix before anything is written,
29
32
  and **`<name>` is alphanumeric — no underscores** (`menuItems`, not `menu_items`). This is
30
33
  checked by `himi validate` and re-checked by every mutating verb. It is deliberately **not**
31
34
  auto-prefixed: what you declare is what lands on the site, and an unprefixed id is
@@ -77,7 +77,9 @@ worker layer — trim it or mine it (`himi docs trim-a-template`): **trim** to t
77
77
  `tier5-src/_shared/` helpers and Wix worker chains and write your own screens. Do not delete
78
78
  `tier5-src/` + `dev/` and start from blank. Never read that file whole: `himi template screens`
79
79
  lists its screens (size, workers, `onAppear`), `--show <id>` prints one entry, and
80
- `--write <id> --from <file>` puts an edited entry back (refused if it breaks the file). Not sure
80
+ `--write <id> --from <file>` puts an edited entry back (refused if it breaks the file), and
81
+ `--add <id> --from <file>` appends a NEW screen and registers it as linkable — the authoring half
82
+ of mining a template. Not sure
81
83
  which template fits? `himi template recommend --text "<the business>"` (or `--book <site analyze
82
84
  dir>` for a site's verticals) ranks the catalog with reasons. For a small **non-Wix** pattern, `himi init --template chat-bot --remote`. `himi template
83
85
  list --remote` shows the full catalog (title/description/tags) — including templates published
@@ -106,7 +106,9 @@ worker layer — trim it or mine it (`himi docs trim-a-template`): **trim** to t
106
106
  `tier5-src/_shared/` helpers and Wix worker chains and write your own screens. Do not delete
107
107
  `tier5-src/` + `dev/` and start from blank. Never read that file whole: `himi template screens`
108
108
  lists its screens (size, workers, `onAppear`), `--show <id>` prints one entry, and
109
- `--write <id> --from <file>` puts an edited entry back (refused if it breaks the file). Not sure
109
+ `--write <id> --from <file>` puts an edited entry back (refused if it breaks the file), and
110
+ `--add <id> --from <file>` appends a NEW screen and registers it as linkable — the authoring half
111
+ of mining a template. Not sure
110
112
  which template fits? `himi template recommend --text "<the business>"` (or `--book <site analyze
111
113
  dir>` for a site's verticals) ranks the catalog with reasons. For a small **non-Wix** pattern, `himi init --template chat-bot --remote`. `himi template
112
114
  list --remote` shows the full catalog (title/description/tags) — including templates published
@@ -112,6 +112,32 @@ grep -oE 'package="[^"]*"|android:label="[^"]*"' \
112
112
  himi-owner-lite-app/build/intermediates/merged_manifest/debug/*/AndroidManifest.xml
113
113
  ```
114
114
 
115
+ ## Off-repo: an app made with `himi init --template owner-lite`
116
+
117
+ Everything above describes Owner Lite as Wix builds it, with its per-customer binary. An
118
+ off-repo content package made with `himi init --template owner-lite` (or one of the `*-owner`
119
+ templates) has no `provisioning.json` and no `prepare:customer` step. What it does have is the
120
+ same split between the **owner** and **visitor** scopes, and that split decides what you can see:
121
+
122
+ - **The web preview is visitor-scope.** `himi run`, `himi render --live` and a pushed draft
123
+ preview all run with an anonymous visitor session. A visitor can read public catalog data
124
+ (Bookings services, Stores products, published posts), so those screens render real data
125
+ there. Anything owner-scoped cannot: a business's appointments and orders, its drafts, and
126
+ every management write (create or edit a service, product or post) answer 401/403. The
127
+ owner-only screens therefore show their empty or error state on the web, which is not a bug.
128
+ - **Owner flows are proven on a native build.** The owner signs in through the native owner
129
+ login (`himi dist` → install the APK/IPA). That build is the surface for "today's
130
+ appointments" and for the write round trips; the web preview cannot stand in for it.
131
+ - **Before that build exists**, prove a write with the site owner's own token against the same
132
+ REST endpoint the worker calls, then re-read it independently. That proves the payload, not
133
+ the app's sign-in. Record each native surface you could not drive as unavailable, with the
134
+ reason, rather than calling it passed:
135
+ `himi ux record --content . --platform ios --status unavailable --reason "owner flows need a native build; not driven yet"`
136
+ (and the same with `--platform android`).
137
+ - **An app that must render something to a visitor** (a public menu next to the owner tools)
138
+ binds the visitor-readable surfaces with a consumer provider and keeps the owner screens
139
+ behind the owner gate. The owner gate fails closed in a visitor session; that is its job.
140
+
115
141
  ## See also
116
142
 
117
143
  - [owner-lite-publish-binary.md](owner-lite-publish-binary.md) — pointing that binary at a serve app.