@wix/himalaya-cli 0.1578.0 → 0.1580.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 (65) hide show
  1. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/sdk/calendar/src/index.d.ts +42 -0
  2. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/sdk/motion/src/index.d.ts +51 -5
  3. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/sdk/sdk-types/src/capabilities.generated.d.ts +1 -1
  4. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/media/src/index.d.ts +1 -0
  5. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/media/src/rendition.d.ts +42 -0
  6. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/audio-record.mjs +1 -1
  7. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/calendar.mjs +46 -1
  8. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/calls.mjs +1 -1
  9. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/contacts.mjs +1 -1
  10. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/location.mjs +1 -1
  11. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/motion.mjs +23 -3
  12. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/photos.mjs +1 -1
  13. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/sdk-types.mjs +1 -1
  14. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/tasks.mjs +1 -1
  15. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-media.mjs +78 -0
  16. package/dist/cli.mjs +9027 -7450
  17. package/dist/preview-assets.generated.bin +0 -0
  18. package/dist/quickjs-intl.js +462 -0
  19. package/dist/quickjs-web-globals.js +540 -0
  20. package/dist/quickjs-worker-entry.js +313 -0
  21. package/guides/himi-json-output.md +30 -1
  22. package/guides/one-app-section-mappings.md +30 -5
  23. package/guides/one-app-to-himi-app.md +214 -6
  24. package/guides/one-app-viewer.md +2 -2
  25. package/guides/sdk-reference.json +79 -7
  26. package/guides/web-page-sdk-reference.md +1 -1
  27. package/package.json +3 -2
  28. package/templates/beauty-salon/dev/index.ts +14 -0
  29. package/templates/beauty-salon/tier5-src/_shared/himi-bookings.ts +21 -0
  30. package/templates/beauty-salon/tier5-src/_shared/himi-copy.ts +8 -3
  31. package/templates/beauty-salon/tier5-src/himi-booking-services/index.ts +36 -12
  32. package/templates/beauty-salon-owner/tier5-src/_shared/himi-copy.ts +8 -3
  33. package/templates/branded-lite/dev/index.ts +14 -0
  34. package/templates/branded-lite/tier5-src/_shared/himi-bookings.ts +21 -0
  35. package/templates/branded-lite/tier5-src/_shared/himi-copy.ts +8 -3
  36. package/templates/branded-lite/tier5-src/himi-booking-services/index.ts +36 -12
  37. package/templates/branded-lite/tier5-src/himi-branded-lite-programs/index.ts +40 -2
  38. package/templates/brandy/tier5-src/_shared/himi-copy.ts +8 -3
  39. package/templates/community-club/tier5-src/_shared/himi-copy.ts +8 -3
  40. package/templates/community-club-owner/tier5-src/_shared/himi-copy.ts +8 -3
  41. package/templates/events-meetups/tier5-src/_shared/himi-copy.ts +8 -3
  42. package/templates/events-meetups-owner/tier5-src/_shared/himi-copy.ts +8 -3
  43. package/templates/gym-fitness/dev/index.ts +14 -0
  44. package/templates/gym-fitness/tier5-src/_shared/himi-bookings.ts +21 -0
  45. package/templates/gym-fitness/tier5-src/_shared/himi-copy.ts +8 -3
  46. package/templates/gym-fitness/tier5-src/himi-booking-services/index.ts +36 -12
  47. package/templates/gym-fitness-owner/tier5-src/_shared/himi-copy.ts +8 -3
  48. package/templates/himi-chat/tier5-src/_shared/himi-copy.ts +8 -3
  49. package/templates/nonprofit-cause/tier5-src/_shared/himi-copy.ts +8 -3
  50. package/templates/nonprofit-cause-owner/tier5-src/_shared/himi-copy.ts +8 -3
  51. package/templates/owner-lite/tier5-src/_shared/himi-copy.ts +8 -3
  52. package/templates/podcast-creator/tier5-src/_shared/himi-copy.ts +8 -3
  53. package/templates/podcast-creator-owner/tier5-src/_shared/himi-copy.ts +8 -3
  54. package/templates/portfolio-creative/tier5-src/_shared/himi-copy.ts +8 -3
  55. package/templates/portfolio-creative-owner/tier5-src/_shared/himi-copy.ts +8 -3
  56. package/templates/restaurant/tier5-src/_shared/himi-copy.ts +8 -3
  57. package/templates/restaurant-owner/tier5-src/_shared/himi-copy.ts +8 -3
  58. package/templates/retail-store/tier5-src/_shared/himi-copy.ts +8 -3
  59. package/templates/retail-store-owner/tier5-src/_shared/himi-copy.ts +8 -3
  60. package/templates/yoga-studio/dev/index.ts +14 -0
  61. package/templates/yoga-studio/tier5-src/_shared/himi-bookings.ts +21 -0
  62. package/templates/yoga-studio/tier5-src/_shared/himi-copy.ts +8 -3
  63. package/templates/yoga-studio/tier5-src/himi-booking-services/index.ts +36 -12
  64. package/templates/yoga-studio/tier5-src/himi-branded-lite-programs/index.ts +40 -2
  65. package/templates/yoga-studio-owner/tier5-src/_shared/himi-copy.ts +8 -3
@@ -116,6 +116,15 @@ user request. Preserve the collected files as evidence. Read the [versioned
116
116
  reference index](https://apps.wix.com/_api/himi-serve/docs/one-app-reference.md) and its five guides before interpreting
117
117
  settings or claiming coverage.
118
118
 
119
+ When raw source placements reference `pages_section`, collection also saves
120
+ `pages-content.json`. It selects `settings.value.pageId`, reads only those unique
121
+ documents as the site's Visitor, and retains every original placement separately.
122
+ The evidence records capture time, source identity, a serialized response JSON SHA-256,
123
+ decoded content and an editable native RichContent component per placement.
124
+ The hash identifies that captured response, not an invented server revision.
125
+ Recollect to refresh editorial content; a changed response produces a changed hash.
126
+ Never import the evidence directory into runtime code.
127
+
119
128
  An unavailable supporting runtime view is recorded as `runtime_context_unavailable`
120
129
  with its platform and typed failure code; that enriched file is absent. Continue
121
130
  from authored intent and mark the platform behavior as unverified in the report.
@@ -500,16 +509,69 @@ Apply these interpretation rules:
500
509
  alone does not prove native navigation or control availability. Record any
501
510
  unavailable device check as unverified.
502
511
  - Business context contains `phoneNumber`, `website`, `services`, optional public
503
- `contact`, and an optional validated public `memberCount`, with their permitted
504
- nested values. Structure wins on authored phone/URL/design.
512
+ `contact`, an optional validated public `memberCount`, and optional `displayName`
513
+ from the same-site Business title, with their permitted nested values.
514
+ Use `displayName` for business identity; structure wins on authored screen titles,
515
+ phone/URL/design.
505
516
  Installed services do not add features. Installation IDs are not record IDs.
506
- Do not restore excluded Business titles, colors, identity or member fields.
517
+ Do not restore other excluded Business colors, identity or member fields.
507
518
  - Referenced editor-authored documents, such as Pages rich text, are presentation
508
519
  evidence too. Fetch only the referenced documents through verified read-only
509
520
  routes, record their IDs and versions, and reconstruct their authored content
510
521
  as editable native components. Keep changing business records live; do not
511
522
  substitute captured catalogs, memberships or transaction data for runtime reads.
512
523
 
524
+ ### Referenced Pages content
525
+
526
+ `pages_section` is an editor-authored document selected by
527
+ `settings.value.pageId`. It is not a CMS collection, Blog post or placeholder.
528
+ Use `pages-content.json` to reconstruct each ready placement as editable app
529
+ source, in its original layout array order, interleaved with the other sections.
530
+ Repeated page IDs may share one captured document but retain separate placement
531
+ IDs. Preserve source visibility/access rules and every screen alias that uses
532
+ that layout; do not multiply canonical placements by navigation aliases.
533
+
534
+ The collector's fixed read is
535
+ `GET https://apps.wix.com/social-content/api/v1/pages/{pageId}` using the source
536
+ site's Visitor signed instance. The response contains
537
+ `{pageId, content: {title, body: {contentType, content}}}`. It verifies the returned
538
+ page ID. `RICH_CONTENT` and `DRAFTJS` contain serialized JSON; `PLAIN_TEXT` stays
539
+ literal. The pinned [original reader](https://github.com/wix-private/wix-one-app-pages/blob/5055bf29109c51c48f0009a403bb70976c54e8dc/packages/wix-one-app-pages/src/services/pages-api.ts "audience-lint-ok: optional pinned upstream evidence, not a local authoring dependency") documents this contract.
540
+ No page listing, source mutation or manager credential fallback is needed.
541
+
542
+ Use the emitted `placement.component`, or rebuild it with the screens SDK's
543
+ `decodeOneAppPage(response, pageId)` and `oneAppPageSection(page, {id})`.
544
+ Copy the resulting component into an editable app source file, then insert it
545
+ into the actual generated screen's payload. Keep it outside collection-bound
546
+ rows that a worker replaces on load. Rendering a separate unused sample or merely
547
+ changing the catalog classification does not restore a migrated placement.
548
+ The component uses literal `RichContent.documentJson`; no source prose is a
549
+ service binding. The existing viewer preserves text, supported formatting, media
550
+ and safe http(s)/mailto/tel links. Legacy bare website domains gain HTTPS before
551
+ encoding; `normalizedWebsiteLinks` records the count. Relative paths, credentials
552
+ and unsafe schemes do not gain an implicit origin. Root editor metadata is omitted from rendered
553
+ content. Keep normal RichContent links or use the existing rich-link handler;
554
+ do not replace them with generic navigation or a WebView.
555
+
556
+ `ready` and genuine `empty` are successful captures. The section helper renders an
557
+ empty page's message from optional `emptyTitle` / `emptyDescription`; localize
558
+ that copy to the app and preserve the source empty-state intent. `invalid` (including missing
559
+ or malformed selectors/content), `unsupported`, `forbidden`, `unauthorized`,
560
+ `not_found` and `unavailable` remain distinct gaps. A failed document has no
561
+ emitted component. Preserve the gap and resolve it before claiming its coverage;
562
+ never substitute an empty paragraph, demo copy or owner read. Supported DraftJS
563
+ text, styled links, flat lists, headings and images are converted; nested lists
564
+ and unknown plugins remain explicit `unsupported` gaps.
565
+
566
+ This reconstruction captures editorial presentation only. Changing catalogs,
567
+ transactions and member records remain live. It uses the existing cross-platform
568
+ RichContent component and does not require a new Pages endpoint in native/web
569
+ runtime transports. A CLI/SDK source merge still needs its normal publication,
570
+ and an existing customer app must be regenerated, checked and published before
571
+ its text changes. Validate the actual generated screens and record every source
572
+ placement's text/link and native checks in the coverage ledger; collection alone
573
+ is not customer acceptance or permission to mark all rows supported.
574
+
513
575
  ### Navigation from top tabs
514
576
 
515
577
  `navigation-plan.json` records the source navigation kind and the planned target.
@@ -984,6 +1046,43 @@ cannot be inspected: call the builder in your generator and serialize the value
984
1046
  returns. Run the generator inside the app (after `npm install`), so
985
1047
  `@wix/himalaya/screens` resolves to the app's own dependency.
986
1048
 
1049
+ ### Preserve the template features the source exposes
1050
+
1051
+ Treat trimming as a source-feature decision, not a list of screens reached during
1052
+ one visitor walkthrough. Before removing a template destination, compare the source
1053
+ Account and native widget journeys under the appropriate visitor/member state.
1054
+ Keep the supported destination and its data/permission contract when the source
1055
+ exposes it. Do not add unrelated verticals merely because the template has them.
1056
+
1057
+ | Source feature | Preserve in the generated app |
1058
+ | --- | --- |
1059
+ | Business identity | Use the collected `business-context.json.displayName` for business identity and bind live chat header/sender/app-info values to the actual business or host state. Preserve the source logo as an image or image binding. Keep the authored app/screen title separate. The technical app slug is an identifier, not a replacement display name. Avoid a global string replacement across source, routes and URLs. |
1060
+ | Account Orders and Subscriptions | Retain source-exposed My activity links and their supported `himi_orders` / `himi_my_subscriptions` destinations, including their workers and sign-in/return routing. A visitor gate is not evidence that a member destination should be deleted. Installed services alone do not prove source exposure; compare the source Account. |
1061
+ | Programs | Reuse the supported Program card/detail and worker behavior. Preserve access type and the actual login/request/join/pay/Continue action for the visitor or participant state. Do not replace these with an image/title-only card or a generic authenticated Continue. Preserve paid/private policy; do not invoke a live purchase or membership mutation as part of a read-only comparison. |
1062
+ | Feed attachments | Reuse the template's rich post card and its `RichContent`/attachment presentation. Preserve inline images, files and their destinations together with text and actions. A text-only replacement is a functional loss even when it can read the post body. |
1063
+
1064
+ After applying identity and before claiming completion, run:
1065
+
1066
+ ```sh
1067
+ himi one-app validate-identity --evidence-dir .himi/one-app --content . --json
1068
+ ```
1069
+
1070
+ The command verifies the collected business-name hash, builds the current content,
1071
+ and checks emitted screen titles, supported component display fields and the known
1072
+ business-chat identity seed for accidental literal slugs. It ignores route IDs,
1073
+ URLs and arbitrary API/action values. Exact authored source copy containing the
1074
+ slug and a real business whose display name is its slug are preserved. An unrelated
1075
+ source page title cannot waive the business-chat identity or its name seed. An older
1076
+ collection without a business display name remains unverified; recollect rather
1077
+ than editing its evidence or guessing a name.
1078
+
1079
+ This is a narrow identity check. It cannot prove every dynamic or translated
1080
+ string, branding image, account journey, access policy or attachment is correct.
1081
+ Inspect the actual generated descriptors and workers, exercise recorded examples,
1082
+ then compare those four source journeys in the real host. Keep their missing
1083
+ behavior explicit in coverage and the report. A stock template screenshot or a
1084
+ passing identity check cannot substitute for the fresh reconstruction/device pass.
1085
+
987
1086
  ### Screen-content contract
988
1087
 
989
1088
  Use the current [screen design guide](https://apps.wix.com/_api/himi-serve/docs/designing-himi-screens.md) and the app's
@@ -1108,6 +1207,116 @@ configuration, workers, evidence, screenshots or logs.
1108
1207
 
1109
1208
  ## 4. Verify the reconstructed app and publish its draft
1110
1209
 
1210
+ ### Check the publish budget before final evidence
1211
+
1212
+ Plan for the **8,388,608-byte** publish JSON budget before spending time on final
1213
+ screenshots, behavioral runs or native UX records. `.himi` file size and source
1214
+ directory totals are different measurements: publication carries JSON-escaped
1215
+ descriptors, base64 bundles/assets/icon and the compressed source archive together.
1216
+
1217
+ First repoint navigation and retained detail/back-link destinations at the implemented
1218
+ generated screens, including destination IDs configured in shared workers. Keep every required
1219
+ source journey and its shared destinations in `SPEC.md`'s Screen catalogue, including
1220
+ cart, checkout, authentication and detail routes. Inspect a trim plan before applying it:
1221
+
1222
+ ```sh
1223
+ himi template trim --from-spec SPEC.md --links follow --dry-run
1224
+ ```
1225
+
1226
+ Review the retained/removed screens, workers and navigation. Trim only unused template
1227
+ definitions that the generated screens replace; `--links follow` can retain linked
1228
+ destinations that still matter. Review `workerRefs` and `reparented` results, and preserve
1229
+ overflow entries and back navigation instead of accepting their deletion. Do not remove a required vertical or drop its actions
1230
+ merely to fit. Apply the same reviewed command without `--dry-run`, then measure:
1231
+
1232
+ ```sh
1233
+ himi publish-size --app <intended-publish-slug> --json
1234
+ ```
1235
+
1236
+ This builds locally using the same release payload, source collector and JSON serializer
1237
+ as push. It never looks up an owner, claims a slug or publishes; no credentials or final
1238
+ UX/attestation records are required. It can write local build output. It reports
1239
+ `bodyBytes`, `limit`, `remainingBytes`, `overageBytes`, source archive bytes and screen/
1240
+ bundle counts. Failed static record-ID enumeration, missing declared icons/screens/bundles, unavailable font assets, failed declared widgets
1241
+ and failed token snapshots are refused instead of undercounted. The builder's optional
1242
+ token absence (404) is reported without inventing token bytes; it does not certify styling.
1243
+ The default `reserveBytes` is a **65,536-byte planning allowance** for final metadata;
1244
+ `planningBytes` includes it. `--reserve-bytes <n>` changes that allowance.
1245
+
1246
+ Measurement uses an isolated contained build: put the intended configuration and
1247
+ collected static seed IDs in the package explicitly. Host environment substitutions
1248
+ are not its input. A source module that needs host code or live catalog enumeration
1249
+ is refused as `build_failed`; materialize the verified source IDs from the collection
1250
+ before measuring, or report that prerequisite as blocked. Never swallow that refusal
1251
+ and treat missing detail screens as saved bytes.
1252
+
1253
+ `bodyBytes` is exact for the stated slug, source-included draft and **no publish metadata**.
1254
+ The allowance is not a verified upper bound: final label/notes, evidence and migration
1255
+ metadata must fit within the remaining bytes. A changed slug changes both app fields;
1256
+ `minimumBodyBytes` uses empty slugs and no metadata as a lower bound. If even that exceeds
1257
+ the limit, unknown owner metadata cannot rescue the build. A passing size check is
1258
+ conditional on those remaining bytes; the final push still measures its exact envelope
1259
+ and runs every evidence gate. The push command has no size-only dry run; use the
1260
+ measurement command for probes. Transport compression is not evidence that the current push guard accepts
1261
+ an oversized JSON body.
1262
+
1263
+ Init-generated `.agents/`, `.claude/` and `HIMALAYA.md` are already excluded by the
1264
+ source archive policy shown in `himi push --help`.
1265
+ Keep source included; do not work around the budget with `--no-source` or `--force`.
1266
+
1267
+ For a large static 8-bit PNG icon, lossless recompression is optional. Keep the original and candidate
1268
+ under `.himi/` while checking, use no resize, palette reduction, quantization or lossy
1269
+ format, and prove decoded RGBA pixels, dimensions and color profile are unchanged before
1270
+ replacing the declared icon. For example, with the optional `sharp` image tool installed
1271
+ locally (`npm install --prefix .himi/pixel-tools --no-save --package-lock=false sharp@0.33.5`):
1272
+
1273
+ ```sh
1274
+ node --input-type=module - <declared-icon.png> .himi/icon-lossless.png <<'JS'
1275
+ import assert from 'node:assert/strict';
1276
+ import { createRequire } from 'node:module';
1277
+ import { createHash } from 'node:crypto';
1278
+ import { resolve } from 'node:path';
1279
+ import { readFileSync, statSync } from 'node:fs';
1280
+ const sharp = createRequire(resolve('.himi/pixel-tools/tool.cjs'))('sharp');
1281
+ const [input, output] = process.argv.slice(2);
1282
+ assert.notEqual(resolve(input), resolve(output), 'preserve the original');
1283
+ const beforeMeta = await sharp(input).metadata();
1284
+ assert.equal(beforeMeta.format, 'png');
1285
+ assert.equal(beforeMeta.depth, 'uchar', 'retain higher-precision originals');
1286
+ assert.equal(beforeMeta.pages ?? 1, 1, 'retain animated originals');
1287
+ const before = await sharp(input).ensureAlpha().raw().toBuffer({ resolveWithObject: true });
1288
+ await sharp(input).keepMetadata().png({ compressionLevel: 9, adaptiveFiltering: true, palette: false }).toFile(output);
1289
+ const afterMeta = await sharp(output).metadata();
1290
+ const after = await sharp(output).ensureAlpha().raw().toBuffer({ resolveWithObject: true });
1291
+ assert.deepEqual(after.info, before.info);
1292
+ assert.deepEqual(afterMeta.icc, beforeMeta.icc);
1293
+ assert.equal(afterMeta.orientation ?? 1, beforeMeta.orientation ?? 1);
1294
+ assert.ok(after.data.equals(before.data), 'decoded RGBA pixels must match exactly');
1295
+ const hash = bytes => createHash('sha256').update(bytes).digest('hex');
1296
+ console.log(JSON.stringify({ pixelsEqual: true, width: after.info.width, height: after.info.height,
1297
+ channels: after.info.channels, beforeBytes: statSync(input).size, afterBytes: statSync(output).size,
1298
+ beforeSHA256: hash(readFileSync(input)), afterSHA256: hash(readFileSync(output)), rgbaSHA256: hash(after.data) }));
1299
+ JS
1300
+ ```
1301
+
1302
+ Only copy the candidate over the declared icon if that proof passes and the file is
1303
+ smaller. If the tool is unavailable, retain the original and report that optimization
1304
+ as unverified. Re-run `himi icon check`, size measurement and affected visual checks
1305
+ after replacing it. Save the equality result and original/candidate hashes.
1306
+
1307
+ If required journeys still exceed the budget, stop before final evidence and report the
1308
+ measured bytes, limit and overage, source archive size, major contributing journeys/
1309
+ assets, reviewed trim results, compression/pixel proof, what was tried and what was
1310
+ preserved. Attach the numerical output and sanitized manifest/inventory; file sizes can
1311
+ explain contributors but never substitute for the assembled measurement. Hand this to
1312
+ the migration project owner for the maintained
1313
+ [Himalaya issue tracker](https://github.com/wix-private/himalaya/issues) (repository access
1314
+ required). If you lack access, return the prepared report to that owner for escalation;
1315
+ do not guess a person's contact or claim a hosted preview exists. This step does not
1316
+ authorize posting an issue, changing the server limit or publishing.
1317
+
1318
+ ### Run the final checks
1319
+
1111
1320
  After the source composition and same-site binding are implemented, use the
1112
1321
  existing authoring loop:
1113
1322
 
@@ -1210,9 +1419,8 @@ himi push --draft --target bo
1210
1419
  Keep the editable source archive included. The publish body is limited to 8,388,608
1211
1420
  bytes. It is about base64(source archive + worker bundles + assets + icons) plus the
1212
1421
  descriptor JSON, so a large app can exceed it: one 33-page migration measured
1213
- 11,259,935 bytes. Measure early, not at the end: after `himi build`, add up
1214
- `dist/bundles/*`, the icons and a zip of the content directory without the excluded
1215
- paths below. `himi push` refuses an oversized body locally, before it sends anything,
1422
+ 11,259,935 bytes. Measure early, not at the end, with `himi publish-size` (see
1423
+ "Check the publish budget before final evidence"). `himi push` refuses an oversized body locally, before it sends anything,
1216
1424
  with `publish_body_too_large`; its `breakdown`
1217
1425
  gives each part's bytes (`appSource`, `screens`, `bundles`, `assets`, `icon`, `other`)
1218
1426
  and the largest entries by name. Reduce it in this order, and repeat affected
@@ -26,7 +26,7 @@ Two runtime refinements below take precedence over simplified examples in the St
26
26
  - Retain source IDs as relationship keys. Give generated files, components, and routes meaningful target names; keep a source-to-target lookup.
27
27
  - Do not turn lifecycle or collection metadata into app requirements.
28
28
  - The current scope does not introduce unconfigured drawer navigation or independent per-screen header surfaces. Use shared Himi header presentation where appropriate; preserve supplied header/drawer inputs that affect top-bar intent. Record configured behavior that exceeds this target scope as an adaptation or unresolved gap.
29
- - Retain only `phoneNumber`, `website`, and `services` from the business object, including their permitted nested fields. Do not recover design or titles from excluded business fields.
29
+ - Retain only `phoneNumber`, `website`, and `services` from the business object, including their permitted nested fields, plus the same-site Business `displayName` as business identity only (the current workflow defines its use). Do not recover design or screen titles from other excluded business fields.
30
30
  - An unmapped historical catalog row starts a current capability investigation. Implement app-local composition, workers and adapters when feasible. Do not silently drop it, invent different behavior, or substitute a WebView. Escalate only genuine platform-core or access unknowns that prevent the requested reconstruction.
31
31
 
32
32
  ## Step 1 — Establish the source interpretation
@@ -136,7 +136,7 @@ The source rules below describe the original viewer. **For the current One App m
136
136
  Relevant source behavior to preserve when applicable:
137
137
 
138
138
  - Header definition selects the renderer; a missing definition ID falls back to the detailed header in the inspected domain code.
139
- - Title and subtitle come from the header domain. Do not silently substitute excluded business-title fields when a dynamic title cannot be resolved from permitted inputs.
139
+ - Title and subtitle come from the header domain. Do not silently substitute excluded business-title fields when a dynamic title cannot be resolved from permitted inputs; the Business `displayName` names the business, not a header or screen title.
140
140
  - Detailed-header empty `coverImagesUrls` falls back to global Structure covers for compatibility. Preserve image order and transition timing where configured.
141
141
  - `isCollapsible` coordinates the header with the page's scroll container. A static hero image alone does not reproduce collapse behavior.
142
142
  - Logo, invite button, CTA, member count/avatars, and business-profile entry point have separate controls. A CTA needs both its configuration and its show flag.
@@ -681,7 +681,7 @@
681
681
  {
682
682
  "name": "createEvent",
683
683
  "kind": "function",
684
- "jsdoc": "",
684
+ "jsdoc": "/** Create an event directly. Needs calendar access — to let the user add an event with\n * NO permission prompt, use {@link presentEditor} (the system editor) instead. */",
685
685
  "decl": "export async function createEvent(event: NewCalendarEvent): Promise<{ id: string | null }>;"
686
686
  },
687
687
  {
@@ -696,6 +696,48 @@
696
696
  "jsdoc": "",
697
697
  "decl": "export async function deleteEvent(id: string): Promise<{ deleted: boolean }>;"
698
698
  },
699
+ {
700
+ "name": "CalendarInfo",
701
+ "kind": "interface",
702
+ "jsdoc": "/** A calendar the user has, from {@link listCalendars}. `color` is `#RRGGBB` (\"\" if unknown). */",
703
+ "decl": "export interface CalendarInfo {\n id: string;\n title: string;\n color: string;\n allowsModifications: boolean;\n isPrimary: boolean;\n}"
704
+ },
705
+ {
706
+ "name": "CalendarEditorResult",
707
+ "kind": "interface",
708
+ "jsdoc": "/** What {@link presentEditor} resolved with. `saved` is false on cancel. `id` is the new\n * event's id when the platform can report it — always `null` on Android (ACTION_INSERT\n * returns no id) and on web, and `null` on iOS without full access. On web, `downloaded`\n * is true when an .ics file was handed to the browser (the user imports it themselves,\n * so `saved` stays false). */",
709
+ "decl": "export interface CalendarEditorResult {\n saved: boolean;\n id: string | null;\n downloaded?: boolean;\n}"
710
+ },
711
+ {
712
+ "name": "CalendarViewerAction",
713
+ "kind": "type",
714
+ "jsdoc": "/** How the system viewer was closed. Android reports `done` always (ACTION_VIEW has no result). */",
715
+ "decl": "export type CalendarViewerAction = \"done\" | \"responded\" | \"deleted\";"
716
+ },
717
+ {
718
+ "name": "presentEditor",
719
+ "kind": "function",
720
+ "jsdoc": "/**\n * Open the system event editor prefilled with `event`; the user saves or cancels. Needs\n * NO calendar permission (the OS editor writes the event, not the app). Resolves\n * `{ saved: false, id: null }` on cancel. Web downloads an .ics instead; macOS rejects\n * `unavailable` (no system editor there).\n */",
721
+ "decl": "export async function presentEditor(event: Partial<NewCalendarEvent> = {}): Promise<CalendarEditorResult>;"
722
+ },
723
+ {
724
+ "name": "presentViewer",
725
+ "kind": "function",
726
+ "jsdoc": "/**\n * Show an existing event in the system viewer (iOS EKEventViewController, Android\n * ACTION_VIEW). Needs full calendar access — rejects `permission_denied` without it, and\n * `not_found` when the id does not resolve. Web and macOS reject `unavailable`.\n */",
727
+ "decl": "export async function presentViewer(id: string): Promise<{ action: CalendarViewerAction }>;"
728
+ },
729
+ {
730
+ "name": "listCalendars",
731
+ "kind": "function",
732
+ "jsdoc": "/** The user's calendars. Needs full access — rejects `permission_denied` without it. */",
733
+ "decl": "export async function listCalendars(): Promise<CalendarInfo[]>;"
734
+ },
735
+ {
736
+ "name": "getEvent",
737
+ "kind": "function",
738
+ "jsdoc": "/** One event by id, or `null` when it does not resolve. Needs full access — rejects\n * `permission_denied` without it. */",
739
+ "decl": "export async function getEvent(id: string): Promise<CalendarEvent | null>;"
740
+ },
699
741
  {
700
742
  "name": "version",
701
743
  "kind": "const",
@@ -3672,8 +3714,8 @@
3672
3714
  {
3673
3715
  "name": "MotionSensor",
3674
3716
  "kind": "type",
3675
- "jsdoc": "/** Which sensor a watch streams. */",
3676
- "decl": "export type MotionSensor = \"steps\" | \"altitude\" | \"heading\";"
3717
+ "jsdoc": "/** Which sensor (or gesture event) a watch streams. */",
3718
+ "decl": "export type MotionSensor =\n | \"steps\" | \"altitude\" | \"heading\"\n | \"accelerometer\" | \"gyroscope\" | \"magnetometer\"\n | \"shake\" | \"tilt\" | \"significantMotion\";"
3677
3719
  },
3678
3720
  {
3679
3721
  "name": "StepHistoryStatus",
@@ -3685,7 +3727,7 @@
3685
3727
  "name": "MotionAvailability",
3686
3728
  "kind": "interface",
3687
3729
  "jsdoc": "/** What this device can actually do. Never assume — a barometer is common but not universal. */",
3688
- "decl": "export interface MotionAvailability {\n /** Pedometer / step counter present (and, on Android, the runtime permission held). */\n steps: boolean;\n /** Barometer present. False on simulators and low-end hardware. */\n altimeter: boolean;\n /** Magnetometer / compass present. */\n heading: boolean;\n /** Whether `getStepCount` can answer a past window — see {@link StepHistoryStatus}. An\n * unknown or absent value reads as \"unsupported\", never as \"available\". */\n stepHistory: StepHistoryStatus;\n}"
3730
+ "decl": "export interface MotionAvailability {\n /** Pedometer / step counter present (and, on Android, the runtime permission held). */\n steps: boolean;\n /** Barometer present. False on simulators and low-end hardware. */\n altimeter: boolean;\n /** Magnetometer / compass present. */\n heading: boolean;\n /** Whether `getStepCount` can answer a past window — see {@link StepHistoryStatus}. An\n * unknown or absent value reads as \"unsupported\", never as \"available\". */\n stepHistory: StepHistoryStatus;\n /** Accelerometer present (web: DeviceMotion exists — false on desktop browsers). */\n accelerometer: boolean;\n /** Gyroscope present (web: DeviceMotion rotationRate). */\n gyroscope: boolean;\n /** Raw magnetometer present. Always false on web — no browser exposes one by default. */\n magnetometer: boolean;\n /** Shake events can be detected (needs an accelerometer). */\n shake: boolean;\n /** Tilt events can be detected (needs an accelerometer). */\n tilt: boolean;\n /** Significant-motion events can be detected (a hardware trigger or an accelerometer). */\n significantMotion: boolean;\n}"
3689
3731
  },
3690
3732
  {
3691
3733
  "name": "StepSample",
@@ -3709,7 +3751,7 @@
3709
3751
  "name": "MotionSample",
3710
3752
  "kind": "type",
3711
3753
  "jsdoc": "",
3712
- "decl": "export type MotionSample = StepSample | AltitudeSample | HeadingSample;"
3754
+ "decl": "export type MotionSample =\n | StepSample | AltitudeSample | HeadingSample\n /** accelerometer / gyroscope / magnetometer: one raw reading. Units: accelerometer g (gravity\n * included; flat face-up ⇒ z ≈ +1 on every platform), gyroscope rad/s, magnetometer µT. */\n | { x: number; y: number; z: number; timestampMs: number }\n /** shake: ONE event per shake. `magnitude` is the shake's peak total acceleration, g. */\n | { magnitude: number; timestampMs: number }\n /** tilt: attitude relative to gravity, degrees. Flat face-up is { pitch: 0, roll: 0 }; pitch is\n * positive when the top edge rises, roll positive when the right edge dips. */\n | { pitch: number; roll: number; timestampMs: number }\n /** significantMotion: the device started moving after a period at rest. */\n | { timestampMs: number };"
3713
3755
  },
3714
3756
  {
3715
3757
  "name": "getPermissionStatus",
@@ -3757,7 +3799,7 @@
3757
3799
  "name": "MotionWatchOptions",
3758
3800
  "kind": "interface",
3759
3801
  "jsdoc": "",
3760
- "decl": "export interface MotionWatchOptions {\n /** Which sensor to stream. Defaults to \"steps\". */\n sensor?: MotionSensor;\n /** Requested sample interval, ms. The platform treats this as a hint and may deliver\n * faster (heading is event-driven) or slower (a step sample needs steps to have happened). */\n intervalMs?: number;\n /** Per-poll bridge timeout. Rarely needs changing — see DEFAULT_POLL_TIMEOUT_MS. */\n pollTimeoutMs?: number;\n}"
3802
+ "decl": "export interface MotionWatchOptions {\n /** Which sensor to stream. Defaults to \"steps\". */\n sensor?: MotionSensor;\n /** Requested sample interval, ms. For the raw sensors (accelerometer / gyroscope /\n * magnetometer) it is clamped to 16–1000 ms (default 100) and honoured, subject to the\n * 60-samples/s ceiling. For steps / altitude / heading the platform treats it as a hint and\n * may deliver faster (heading is event-driven) or slower (a step sample needs steps to have\n * happened). Ignored by the gesture events, which fire when the gesture happens. */\n intervalMs?: number;\n /** Per-poll bridge timeout. Rarely needs changing — see DEFAULT_POLL_TIMEOUT_MS. */\n pollTimeoutMs?: number;\n}"
3761
3803
  },
3762
3804
  {
3763
3805
  "name": "MotionWatcher",
@@ -3768,7 +3810,7 @@
3768
3810
  {
3769
3811
  "name": "watch",
3770
3812
  "kind": "function",
3771
- "jsdoc": "/**\n * Open a sensor stream. The native listener starts EAGERLY (not on first iteration), so\n * samples buffer from the moment you call `watch()` even if the consumer iterates late —\n * which matters for steps, where a missed window is lost data rather than a stale value.\n *\n * ```ts\n * // Elevation gain over a hike.\n * let gain = 0, prev: number | undefined;\n * const w = watch({ sensor: \"altitude\", intervalMs: 5000 });\n * for await (const s of w) {\n * if (prev != null && s.relativeAltitudeM > prev) gain += s.relativeAltitudeM - prev;\n * prev = s.relativeAltitudeM;\n * if (done) break; // break ⇒ auto-stop\n * }\n * ```\n *\n * Type the stream by passing the sample type: `watch<HeadingSample>({ sensor: \"heading\" })`.\n */",
3813
+ "jsdoc": "/**\n * Open a sensor stream. The native listener starts EAGERLY (not on first iteration), so\n * samples buffer from the moment you call `watch()` even if the consumer iterates late —\n * which matters for steps, where a missed window is lost data rather than a stale value.\n *\n * ```ts\n * // Elevation gain over a hike.\n * let gain = 0, prev: number | undefined;\n * const w = watch({ sensor: \"altitude\", intervalMs: 5000 });\n * for await (const s of w) {\n * if (prev != null && s.relativeAltitudeM > prev) gain += s.relativeAltitudeM - prev;\n * prev = s.relativeAltitudeM;\n * if (done) break; // break ⇒ auto-stop\n * }\n * ```\n *\n * Type the stream by passing the sample type: `watch<HeadingSample>({ sensor: \"heading\" })`.\n *\n * ```ts\n * // Shake to reroll.\n * for await (const _ of watch({ sensor: \"shake\" })) reroll();\n * // Tilt to steer.\n * for await (const t of watch<{ pitch: number; roll: number; timestampMs: number }>({ sensor: \"tilt\" })) steer(t.roll);\n * ```\n */",
3772
3814
  "decl": "export function watch<T extends MotionSample = MotionSample>(options?: MotionWatchOptions): MotionWatcher<T>;"
3773
3815
  },
3774
3816
  {
@@ -23430,6 +23472,36 @@
23430
23472
  "kind": "function",
23431
23473
  "jsdoc": "/**\n * True when `url` is Wix media that is not PROVABLY bounded below the pixel ceiling\n * the renderers decode at: either dimension missing from the final stage, or either\n * one above `WIX_DETAIL_IMAGE`.\n *\n * Both dimensions are required, and one is genuinely not enough: `fit/w_1400` on a\n * 1400x9000 source derives a 9000px height, so a single stated side proves nothing\n * about the other.\n *\n * This reports the DEFECT. It is deliberately WIDER than what `wixMediaUrl` repairs\n * — the predicate says \"these pixels are not provably bounded\", the rewriter only\n * changes what it can without altering the picture. Four kinds stay flagged after a\n * round-trip:\n * - an oversized `crop` — NOT rewritten; moving `w_`/`h_` selects a different\n * source rectangle, so it needs a human to pick a smaller crop;\n * - EITHER mode stating one dimension — IS rewritten (that side gets bounded, and\n * one side is not a box, so nothing is re-cropped), but the derived side still\n * cannot be proven, so it remains flagged;\n * - a CHAINED transform (`crop/…/fill/…`) — this predicate reads its final stage,\n * but `WIX_TRANSFORM` matches a single stage, so `wixMediaUrl` returns it\n * verbatim. A chained url above the ceiling is therefore reported and not\n * repaired. Rewriting the last stage of a chain is a deliberate follow-up: it\n * is the media manager's own output shape, so it is worth having, and it is\n * more parsing than this change should carry.\n *\n * NOTE: `tools/ci/lint-wix-media-sizing.mjs` does not call this today — it matches\n * hand-built url construction in worker SOURCE, which is a different (static) check.\n * Wiring a runtime gate to this predicate is tracked separately.\n */",
23432
23474
  "decl": "export function isUnsizedWixMedia(url: string): boolean;"
23475
+ },
23476
+ {
23477
+ "name": "ImageTarget",
23478
+ "kind": "interface",
23479
+ "jsdoc": "/** Pure rendition policy shared by web/RN. Native mirrors use the same request fixtures. */",
23480
+ "decl": "export interface ImageTarget {\n width: number;\n height: number;\n scale: number;\n mode?: string;\n maxEdge?: number;\n}"
23481
+ },
23482
+ {
23483
+ "name": "imagePixelSize",
23484
+ "kind": "function",
23485
+ "jsdoc": "/**\n * The pixel size an image is requested at for a box of `width`×`height` points at `scale`: the box in pixels,\n * its longer edge rounded up to a shared bucket (96 … 1400, or up to `maxEdge`, at most 4096) so nearby sizes\n * share one cached rendition. A size it returns maps to itself. An invalid box asks for `maxEdge` square.\n */",
23486
+ "decl": "export function imagePixelSize(width: number, height: number, scale: number, maxEdge = 1400);"
23487
+ },
23488
+ {
23489
+ "name": "resolveImageRendition",
23490
+ "kind": "function",
23491
+ "jsdoc": "/** Keep the caller's source untouched: this URL belongs to a display request, not entity data. */",
23492
+ "decl": "export function resolveImageRendition(source: string, target: ImageTarget);"
23493
+ },
23494
+ {
23495
+ "name": "ImageCandidate",
23496
+ "kind": "interface",
23497
+ "jsdoc": "",
23498
+ "decl": "export interface ImageCandidate { url: string; width: number; height: number }"
23499
+ },
23500
+ {
23501
+ "name": "imageCandidates",
23502
+ "kind": "function",
23503
+ "jsdoc": "/**\n * The renditions a renderer offers for one image, smallest first, so the HOST picks among them by\n * its own layout and pixel density (web `srcset`, a React Native `source` array) with no\n * measurement code of ours. A fixed box gets one candidate per screen density; a fluid one (no\n * numeric width) gets fluid widths, its height from `ratio` when known. A source that cannot be\n * resized safely yields exactly itself.\n */",
23504
+ "decl": "export function imageCandidates(\n source: string,\n box: { width?: number; height?: number; ratio?: number; mode?: string },\n scales: number[] = [1, 2, 3],\n): ImageCandidate[];"
23433
23505
  }
23434
23506
  ]
23435
23507
  },
@@ -19,7 +19,7 @@ the SDK sources, so it matches the shipped SDKs.
19
19
  - [`base44`](https://apps.wix.com/_api/himi-serve/authoring/resource/web-sdk%2Fbase44?format=md) — A Base44 app's backend. 11 methods · Brandy: conditional.
20
20
  - [`biometrics`](https://apps.wix.com/_api/himi-serve/authoring/resource/web-sdk%2Fbiometrics?format=md) — Biometric authentication capability. 2 methods.
21
21
  - [`browser_auth`](https://apps.wix.com/_api/himi-serve/authoring/resource/web-sdk%2Fbrowser_auth?format=md) — Native browser SSO. 1 method.
22
- - [`calendar`](https://apps.wix.com/_api/himi-serve/authoring/resource/web-sdk%2Fcalendar?format=md) — Device calendar. 6 methods.
22
+ - [`calendar`](https://apps.wix.com/_api/himi-serve/authoring/resource/web-sdk%2Fcalendar?format=md) — Device calendar. 10 methods.
23
23
  - [`calls`](https://apps.wix.com/_api/himi-serve/authoring/resource/web-sdk%2Fcalls?format=md) — Real-time 1:1 audio calls (Stage 1). 16 methods.
24
24
  - [`camera`](https://apps.wix.com/_api/himi-serve/authoring/resource/web-sdk%2Fcamera?format=md) — Camera capability. 4 methods · returns FileHandles.
25
25
  - [`clipboard`](https://apps.wix.com/_api/himi-serve/authoring/resource/web-sdk%2Fclipboard?format=md) — System clipboard. 5 methods.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wix/himalaya-cli",
3
- "version": "0.1578.0",
3
+ "version": "0.1580.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": [
@@ -34,6 +34,7 @@
34
34
  "test": "node ../../tools/ci/run-tests.mjs --package tools/himi-cli --"
35
35
  },
36
36
  "dependencies": {
37
+ "@jitl/quickjs-wasmfile-release-sync": "^0.32.0",
37
38
  "@modelcontextprotocol/server": "^2.0.0",
38
39
  "@rive-app/canvas": "2.38.3",
39
40
  "ajv": "^8.17.1",
@@ -71,5 +72,5 @@
71
72
  "artifactId": "himalaya-cli"
72
73
  }
73
74
  },
74
- "falconPackageHash": "0a206d4a3d13e9f1302334dcaeb9d70a507f8c916a8ac190fe8bfd8e"
75
+ "falconPackageHash": "d3dfbed77e6075f41bbb6a017ff1d9817b572fd8623f52af03471520"
75
76
  }
@@ -20977,6 +20977,20 @@ const SCREENS = runtimeSetting({
20977
20977
  "hiddenPath": "$item.noTypeTag"
20978
20978
  }
20979
20979
  },
20980
+ {
20981
+ "id": "himi_services.row.$item.id/ended",
20982
+ "body": {
20983
+ "package": "himalaya",
20984
+ "type": "Text",
20985
+ "props": {
20986
+ "text": "$item.endedText",
20987
+ "style": "caption",
20988
+ "color": "textSecondary",
20989
+ "maxLines": 1,
20990
+ "collapseWhenEmpty": true
20991
+ }
20992
+ }
20993
+ },
20980
20994
  {
20981
20995
  "id": "",
20982
20996
  "body": {
@@ -80,6 +80,19 @@ type CoursePolicyShape = {
80
80
  };
81
81
  };
82
82
 
83
+ /** A completed course, independent of whether bookings are currently allowed. Missing,
84
+ * invalid or reversed dates are unknown, never evidence that a course has ended. */
85
+ export function courseHasEnded(service: CoursePolicyShape, now: number): boolean {
86
+ const start = service.schedule?.firstSessionStart ?? "";
87
+ const end = service.schedule?.lastSessionEnd ?? "";
88
+ // The schedule has no zone beside these fields. A local wall time must not be interpreted
89
+ // in the device's zone and presented as a confirmed business end instant.
90
+ if (!HAS_OFFSET.test(start) || !HAS_OFFSET.test(end)) return false;
91
+ const first = validDate(start)?.getTime() ?? NaN;
92
+ const last = validDate(end)?.getTime() ?? NaN;
93
+ return Number.isFinite(now) && Number.isFinite(first) && Number.isFinite(last) && first <= last && last <= now;
94
+ }
95
+
83
96
  /** Whether the business takes course bookings at `now` (ms): the course has not ended, has not
84
97
  * started (unless the policy lets people join late), and `now` is inside its booking window —
85
98
  * which the early and late limits count from the first session. */
@@ -238,6 +251,14 @@ export function serviceDurationText(service: PricedServiceShape, label: BookingL
238
251
 
239
252
  function validDate(value: string | undefined): Date | undefined {
240
253
  if (!value) return undefined;
254
+ const parts = /^(\d{4})-(\d{2})-(\d{2})(?:T|$)/i.exec(value);
255
+ if (!parts) return undefined;
256
+ const year = Number(parts[1]), month = Number(parts[2]) - 1, day = Number(parts[3]);
257
+ // Date accepts February 30 by rolling into March. Check the stated calendar day first;
258
+ // setUTCFullYear also preserves years 0–99, which Date.UTC would reinterpret as 1900–1999.
259
+ const calendar = new Date(0);
260
+ calendar.setUTCFullYear(year, month, day);
261
+ if (calendar.getUTCFullYear() !== year || calendar.getUTCMonth() !== month || calendar.getUTCDate() !== day) return undefined;
241
262
  const date = new Date(value);
242
263
  return Number.isFinite(date.getTime()) ? date : undefined;
243
264
  }
@@ -21,6 +21,7 @@
21
21
  export type CopyPackValue = string | { source: string; translation: string };
22
22
 
23
23
  let stashedCopy: Record<string, CopyPackValue> = Object.create(null);
24
+ let copyReceived = false;
24
25
 
25
26
  /** Absorb the `copy` onAppear param (customer strings or stock translation records)
26
27
  * from an action arg. Call first in every action that can receive onAppear
@@ -44,15 +45,19 @@ export function stashCopy(arg: unknown): void {
44
45
  }
45
46
  }
46
47
  stashedCopy = next;
48
+ copyReceived = true;
47
49
  } catch {
48
50
  // Malformed pack param — keep the stock copy rather than dying mid-action.
49
51
  }
50
52
  }
51
53
 
52
- /** The provisioned override for `key`, or `fallback` (the stock copy) when no
53
- * pack / no such key reached this worker. */
54
- export function copyParam(key: string, fallback: string): string {
54
+ /** The provisioned override for `key`, or `fallback` (the stock copy). An optional
55
+ * restored label survives only until a current copy pack arrives. */
56
+ export function copyParam(key: string, fallback: string, restoredFallback = fallback): string {
55
57
  const value = stashedCopy[key];
58
+ // A disk-restored row may paint before onAppear supplies its copy pack. Keep its
59
+ // last label only before a pack arrives; even an empty current pack selects stock copy.
60
+ if (value === undefined) return copyReceived ? fallback : restoredFallback;
56
61
  if (typeof value === "string") return value !== "" ? value : fallback;
57
62
  return value?.source === fallback && value.translation !== "" ? value.translation : fallback;
58
63
  }