@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.
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/sdk/calendar/src/index.d.ts +42 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/sdk/motion/src/index.d.ts +51 -5
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/sdk/sdk-types/src/capabilities.generated.d.ts +1 -1
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/media/src/index.d.ts +1 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/media/src/rendition.d.ts +42 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/audio-record.mjs +1 -1
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/calendar.mjs +46 -1
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/calls.mjs +1 -1
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/contacts.mjs +1 -1
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/location.mjs +1 -1
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/motion.mjs +23 -3
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/photos.mjs +1 -1
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/sdk-types.mjs +1 -1
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/tasks.mjs +1 -1
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-media.mjs +78 -0
- package/dist/cli.mjs +9027 -7450
- package/dist/preview-assets.generated.bin +0 -0
- package/dist/quickjs-intl.js +462 -0
- package/dist/quickjs-web-globals.js +540 -0
- package/dist/quickjs-worker-entry.js +313 -0
- package/guides/himi-json-output.md +30 -1
- package/guides/one-app-section-mappings.md +30 -5
- package/guides/one-app-to-himi-app.md +214 -6
- package/guides/one-app-viewer.md +2 -2
- package/guides/sdk-reference.json +79 -7
- package/guides/web-page-sdk-reference.md +1 -1
- package/package.json +3 -2
- package/templates/beauty-salon/dev/index.ts +14 -0
- package/templates/beauty-salon/tier5-src/_shared/himi-bookings.ts +21 -0
- package/templates/beauty-salon/tier5-src/_shared/himi-copy.ts +8 -3
- package/templates/beauty-salon/tier5-src/himi-booking-services/index.ts +36 -12
- package/templates/beauty-salon-owner/tier5-src/_shared/himi-copy.ts +8 -3
- package/templates/branded-lite/dev/index.ts +14 -0
- package/templates/branded-lite/tier5-src/_shared/himi-bookings.ts +21 -0
- package/templates/branded-lite/tier5-src/_shared/himi-copy.ts +8 -3
- package/templates/branded-lite/tier5-src/himi-booking-services/index.ts +36 -12
- package/templates/branded-lite/tier5-src/himi-branded-lite-programs/index.ts +40 -2
- package/templates/brandy/tier5-src/_shared/himi-copy.ts +8 -3
- package/templates/community-club/tier5-src/_shared/himi-copy.ts +8 -3
- package/templates/community-club-owner/tier5-src/_shared/himi-copy.ts +8 -3
- package/templates/events-meetups/tier5-src/_shared/himi-copy.ts +8 -3
- package/templates/events-meetups-owner/tier5-src/_shared/himi-copy.ts +8 -3
- package/templates/gym-fitness/dev/index.ts +14 -0
- package/templates/gym-fitness/tier5-src/_shared/himi-bookings.ts +21 -0
- package/templates/gym-fitness/tier5-src/_shared/himi-copy.ts +8 -3
- package/templates/gym-fitness/tier5-src/himi-booking-services/index.ts +36 -12
- package/templates/gym-fitness-owner/tier5-src/_shared/himi-copy.ts +8 -3
- package/templates/himi-chat/tier5-src/_shared/himi-copy.ts +8 -3
- package/templates/nonprofit-cause/tier5-src/_shared/himi-copy.ts +8 -3
- package/templates/nonprofit-cause-owner/tier5-src/_shared/himi-copy.ts +8 -3
- package/templates/owner-lite/tier5-src/_shared/himi-copy.ts +8 -3
- package/templates/podcast-creator/tier5-src/_shared/himi-copy.ts +8 -3
- package/templates/podcast-creator-owner/tier5-src/_shared/himi-copy.ts +8 -3
- package/templates/portfolio-creative/tier5-src/_shared/himi-copy.ts +8 -3
- package/templates/portfolio-creative-owner/tier5-src/_shared/himi-copy.ts +8 -3
- package/templates/restaurant/tier5-src/_shared/himi-copy.ts +8 -3
- package/templates/restaurant-owner/tier5-src/_shared/himi-copy.ts +8 -3
- package/templates/retail-store/tier5-src/_shared/himi-copy.ts +8 -3
- package/templates/retail-store-owner/tier5-src/_shared/himi-copy.ts +8 -3
- package/templates/yoga-studio/dev/index.ts +14 -0
- package/templates/yoga-studio/tier5-src/_shared/himi-bookings.ts +21 -0
- package/templates/yoga-studio/tier5-src/_shared/himi-copy.ts +8 -3
- package/templates/yoga-studio/tier5-src/himi-booking-services/index.ts +36 -12
- package/templates/yoga-studio/tier5-src/himi-branded-lite-programs/index.ts +40 -2
- 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`,
|
|
504
|
-
|
|
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
|
|
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
|
|
1214
|
-
|
|
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
|
package/guides/one-app-viewer.md
CHANGED
|
@@ -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
|
|
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
|
|
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.
|
|
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.
|
|
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.
|
|
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": "
|
|
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)
|
|
53
|
-
*
|
|
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
|
}
|