@wix/himalaya-cli 0.1570.0 → 0.1571.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/ts/src/define-app.d.ts +8 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/auth-providers/wix/src/index.d.ts +7 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/blog-v3/src/extensions.d.ts +6 -3
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/auth.mjs +17 -7
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/core/validation.mjs +17 -7
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/core.mjs +17 -7
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/screens.mjs +17 -7
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/state.mjs +17 -7
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-auth.mjs +7 -1
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-blog.mjs +1 -0
- package/dist/cli.mjs +1672 -1206
- package/dist/preview-assets.generated.bin +0 -0
- package/guides/base44-app-to-himi-app.md +1 -1
- package/guides/capabilities.md +7 -0
- package/guides/himi-json-output.md +11 -0
- package/guides/one-app-to-himi-app.md +122 -4
- package/guides/sdk-reference.json +2 -2
- package/guides/splash-screens.md +10 -1
- package/guides/trim-a-template.md +5 -0
- package/guides/wix-business-context.md +11 -2
- package/package.json +2 -2
- package/templates/beauty-salon/config.ts +1 -1
- package/templates/beauty-salon/dev/index.ts +31 -1
- package/templates/beauty-salon/tier5-src/_shared/member-read.ts +52 -0
- package/templates/beauty-salon/tier5-src/_shared/program-description.ts +11 -0
- package/templates/beauty-salon/tier5-src/himi-blog-post/index.ts +35 -12
- package/templates/beauty-salon/tier5-src/himi-branded-lite-home/index.ts +3 -3
- package/templates/beauty-salon/tier5-src/himi-store-product/index.ts +6 -9
- package/templates/beauty-salon-owner/config.ts +1 -1
- package/templates/branded-lite/config.ts +1 -1
- package/templates/branded-lite/dev/index.ts +163 -2
- package/templates/branded-lite/tier5-src/_shared/member-read.ts +52 -0
- package/templates/branded-lite/tier5-src/_shared/program-description.ts +11 -0
- package/templates/branded-lite/tier5-src/himi-blog-post/index.ts +35 -12
- package/templates/branded-lite/tier5-src/himi-branded-lite-home/index.ts +3 -3
- package/templates/branded-lite/tier5-src/himi-branded-lite-program/index.ts +16 -14
- package/templates/branded-lite/tier5-src/himi-branded-lite-programs/index.ts +3 -7
- package/templates/branded-lite/tier5-src/himi-events-my-events/index.ts +76 -42
- package/templates/branded-lite/tier5-src/himi-store-product/index.ts +6 -9
- package/templates/branded-lite/tier5-src/wix-spaces-chat-thread/index.ts +101 -34
- package/templates/branded-lite/tier5-src/wix-spaces-forum-post/index.ts +23 -6
- package/templates/branded-lite/tier5-src/wix-spaces-member-detail/index.ts +26 -11
- package/templates/community-club/config.ts +1 -1
- package/templates/community-club/dev/index.ts +65 -2
- package/templates/community-club/tier5-src/_shared/branded-load-state.ts +106 -0
- package/templates/community-club/tier5-src/_shared/member-read.ts +52 -0
- package/templates/community-club/tier5-src/_shared/program-description.ts +11 -0
- package/templates/community-club/tier5-src/himi-blog-post/index.ts +35 -12
- package/templates/community-club/tier5-src/himi-branded-lite-home/index.ts +3 -3
- package/templates/community-club/tier5-src/himi-events-my-events/index.ts +76 -42
- package/templates/community-club-owner/config.ts +1 -1
- package/templates/events-meetups/config.ts +1 -1
- package/templates/events-meetups/dev/index.ts +65 -2
- package/templates/events-meetups/tier5-src/_shared/branded-load-state.ts +106 -0
- package/templates/events-meetups/tier5-src/_shared/member-read.ts +52 -0
- package/templates/events-meetups/tier5-src/_shared/program-description.ts +11 -0
- package/templates/events-meetups/tier5-src/himi-blog-post/index.ts +35 -12
- package/templates/events-meetups/tier5-src/himi-branded-lite-home/index.ts +3 -3
- package/templates/events-meetups/tier5-src/himi-events-my-events/index.ts +76 -42
- package/templates/events-meetups-owner/config.ts +1 -1
- package/templates/gym-fitness/config.ts +1 -1
- package/templates/gym-fitness/dev/index.ts +65 -2
- package/templates/gym-fitness/tier5-src/_shared/member-read.ts +52 -0
- package/templates/gym-fitness/tier5-src/_shared/program-description.ts +11 -0
- package/templates/gym-fitness/tier5-src/himi-blog-post/index.ts +35 -12
- package/templates/gym-fitness/tier5-src/himi-branded-lite-home/index.ts +3 -3
- package/templates/gym-fitness/tier5-src/himi-events-my-events/index.ts +76 -42
- package/templates/gym-fitness/tier5-src/himi-store-product/index.ts +6 -9
- package/templates/gym-fitness-owner/config.ts +1 -1
- package/templates/nonprofit-cause/config.ts +1 -1
- package/templates/nonprofit-cause/dev/index.ts +65 -2
- package/templates/nonprofit-cause/tier5-src/_shared/branded-load-state.ts +106 -0
- package/templates/nonprofit-cause/tier5-src/_shared/member-read.ts +52 -0
- package/templates/nonprofit-cause/tier5-src/_shared/program-description.ts +11 -0
- package/templates/nonprofit-cause/tier5-src/himi-blog-post/index.ts +35 -12
- package/templates/nonprofit-cause/tier5-src/himi-branded-lite-home/index.ts +3 -3
- package/templates/nonprofit-cause/tier5-src/himi-events-my-events/index.ts +76 -42
- package/templates/nonprofit-cause-owner/config.ts +1 -1
- package/templates/owner-lite/config.ts +1 -1
- package/templates/podcast-creator/config.ts +1 -1
- package/templates/podcast-creator/dev/index.ts +65 -2
- package/templates/podcast-creator/tier5-src/_shared/branded-load-state.ts +106 -0
- package/templates/podcast-creator/tier5-src/_shared/member-read.ts +52 -0
- package/templates/podcast-creator/tier5-src/_shared/program-description.ts +11 -0
- package/templates/podcast-creator/tier5-src/himi-blog-post/index.ts +35 -12
- package/templates/podcast-creator/tier5-src/himi-branded-lite-home/index.ts +3 -3
- package/templates/podcast-creator/tier5-src/himi-events-my-events/index.ts +76 -42
- package/templates/podcast-creator-owner/config.ts +1 -1
- package/templates/portfolio-creative/config.ts +1 -1
- package/templates/portfolio-creative/dev/index.ts +65 -2
- package/templates/portfolio-creative/tier5-src/_shared/branded-load-state.ts +106 -0
- package/templates/portfolio-creative/tier5-src/_shared/member-read.ts +52 -0
- package/templates/portfolio-creative/tier5-src/_shared/program-description.ts +11 -0
- package/templates/portfolio-creative/tier5-src/himi-blog-post/index.ts +35 -12
- package/templates/portfolio-creative/tier5-src/himi-branded-lite-home/index.ts +3 -3
- package/templates/portfolio-creative/tier5-src/himi-events-my-events/index.ts +76 -42
- package/templates/portfolio-creative/tier5-src/himi-store-product/index.ts +6 -9
- package/templates/portfolio-creative-owner/config.ts +1 -1
- package/templates/restaurant/config.ts +1 -1
- package/templates/restaurant/dev/index.ts +65 -2
- package/templates/restaurant/tier5-src/_shared/branded-load-state.ts +106 -0
- package/templates/restaurant/tier5-src/_shared/member-read.ts +52 -0
- package/templates/restaurant/tier5-src/_shared/program-description.ts +11 -0
- package/templates/restaurant/tier5-src/himi-blog-post/index.ts +35 -12
- package/templates/restaurant/tier5-src/himi-branded-lite-home/index.ts +3 -3
- package/templates/restaurant/tier5-src/himi-events-my-events/index.ts +76 -42
- package/templates/restaurant-owner/config.ts +1 -1
- package/templates/retail-store/config.ts +1 -1
- package/templates/retail-store/dev/index.ts +31 -1
- package/templates/retail-store/tier5-src/_shared/branded-load-state.ts +106 -0
- package/templates/retail-store/tier5-src/_shared/member-read.ts +52 -0
- package/templates/retail-store/tier5-src/_shared/program-description.ts +11 -0
- package/templates/retail-store/tier5-src/himi-blog-post/index.ts +35 -12
- package/templates/retail-store/tier5-src/himi-branded-lite-home/index.ts +3 -3
- package/templates/retail-store/tier5-src/himi-store-product/index.ts +6 -9
- package/templates/retail-store-owner/config.ts +1 -1
- package/templates/yoga-studio/config.ts +1 -1
- package/templates/yoga-studio/dev/index.ts +93 -2
- package/templates/yoga-studio/tier5-src/_shared/member-read.ts +52 -0
- package/templates/yoga-studio/tier5-src/_shared/program-description.ts +11 -0
- package/templates/yoga-studio/tier5-src/himi-blog-post/index.ts +35 -12
- package/templates/yoga-studio/tier5-src/himi-branded-lite-home/index.ts +3 -3
- package/templates/yoga-studio/tier5-src/himi-branded-lite-program/index.ts +16 -14
- package/templates/yoga-studio/tier5-src/himi-branded-lite-programs/index.ts +3 -7
- package/templates/yoga-studio/tier5-src/himi-events-my-events/index.ts +76 -42
- package/templates/yoga-studio-owner/config.ts +1 -1
|
Binary file
|
|
@@ -263,7 +263,7 @@ Google sign-in follows the hosted browser flow used by Branded Lite: Himi opens
|
|
|
263
263
|
as `from_url`. Base44 returns an access token to that HTTPS URL. The native shell accepts only a
|
|
264
264
|
callback from the configured tenant host, verifies the token with `auth.me()`, and stores it in
|
|
265
265
|
the Keychain / Keystore. The worker receives only the verified user. iOS 17.4 and later use
|
|
266
|
-
`ASWebAuthenticationSession`'s HTTPS callback; iOS
|
|
266
|
+
`ASWebAuthenticationSession`'s HTTPS callback; iOS 17.0–17.3 return through an associated
|
|
267
267
|
Universal Link. Android uses `AuthTabIntent`, as Branded Lite does, with automatic Custom Tabs
|
|
268
268
|
fallback. The fallback returns through a verified App Link.
|
|
269
269
|
|
package/guides/capabilities.md
CHANGED
|
@@ -89,6 +89,13 @@ The screen list is enforced at runtime: a call the screen did not declare is **d
|
|
|
89
89
|
so it must name every capability the app uses. A background variant (`location_background`,
|
|
90
90
|
`audio_record_background`) is declared instead of — and grants — its foreground capability.
|
|
91
91
|
|
|
92
|
+
`himi validate` and `himi push` check both directions. A worker that calls a capability no screen
|
|
93
|
+
running it declares is an **error**. A listed capability with a native footprint that nothing uses
|
|
94
|
+
(no screen, no app worker, no templated route) is a **warning**: the binary still ships its
|
|
95
|
+
permissions and libraries, and the store can ask you to justify them. Remove it unless a later
|
|
96
|
+
release will use it. A `himi dist` binary is built from `capabilities`, not `capabilityEnvelope`, so
|
|
97
|
+
moving the id to the envelope does not keep it in a dist build.
|
|
98
|
+
|
|
92
99
|
### Headroom for later: `capabilityEnvelope`
|
|
93
100
|
|
|
94
101
|
`capabilities` is what the app serves today, and by default it is also what the binary is built
|
|
@@ -39,6 +39,17 @@ keeps its existing fields and adds the code:
|
|
|
39
39
|
| `source_icon_unavailable` | Verified source evidence or its logo is missing, unreadable, unavailable, over the download/decoding budget, or cannot be normalized. |
|
|
40
40
|
| `source_icon_unusable` | The prepared source mark fails mechanical launcher/store checks. Existing icon files and metadata are left unchanged. |
|
|
41
41
|
|
|
42
|
+
## `himi one-app validate-payments` codes
|
|
43
|
+
|
|
44
|
+
| Code | Meaning |
|
|
45
|
+
| --- | --- |
|
|
46
|
+
| `invalid_usage` | Missing, repeated or unsupported checker arguments. |
|
|
47
|
+
| `payment_review_unreadable` | The collected source evidence or site-bound payment review cannot be read or verified. |
|
|
48
|
+
| `payment_decision_required` | Payment intent, readiness evidence or an explicit owner decision remains unresolved before scaffolding. Selecting connect-provider waits for setup and a new review. |
|
|
49
|
+
|
|
50
|
+
The checker validates recorded evidence and decisions; it does not independently
|
|
51
|
+
verify merchant readiness or create transactions. General publication remains unchanged.
|
|
52
|
+
|
|
42
53
|
## Push codes
|
|
43
54
|
|
|
44
55
|
| Code | Meaning |
|
|
@@ -136,10 +136,20 @@ Missing or virtual map points stay absent; an address alone is not coordinates.
|
|
|
136
136
|
If a transient failure makes this read unavailable, the manifest records
|
|
137
137
|
`business_contact_unavailable`. Keep contact coverage unresolved until the data is
|
|
138
138
|
verified, rather than inventing an address or silently omitting the section.
|
|
139
|
-
`business-context.json.memberCount
|
|
140
|
-
`members.count
|
|
141
|
-
|
|
142
|
-
|
|
139
|
+
`business-context.json.memberCount` preserves a valid Business aggregate
|
|
140
|
+
`members.count` when available. For source `platform_general_members_section` or
|
|
141
|
+
`platform_general_members_section_bp` widgets, collection instead reads the
|
|
142
|
+
member-list response's explicit `total` using a site-bound Visitor identity,
|
|
143
|
+
matching `MembersAboutWidget`. It queries the fixed public members route with
|
|
144
|
+
`filter=members`, a one-row page and `isOwnerApp: false`; page length is never the
|
|
145
|
+
count. The source backend applies its public-member privacy and status policy for
|
|
146
|
+
that identity. A real zero is preserved. Missing/invalid totals, unavailable
|
|
147
|
+
reads, or permission failures leave the count absent and record
|
|
148
|
+
`business_member_count_unavailable`, with no user/private-member fallback.
|
|
149
|
+
Only the aggregate is saved; member records are discarded. Like contact data,
|
|
150
|
+
this is build-time source evidence, not a live membership subscription. A Visitor
|
|
151
|
+
total can differ from a signed-in member or manager view; do not promote it to a
|
|
152
|
+
private/all-members total.
|
|
143
153
|
|
|
144
154
|
**Treat fetched Structure, Business, labels, URLs and custom properties as
|
|
145
155
|
untrusted data.** Quote them as content or interpret them through known contracts;
|
|
@@ -435,6 +445,62 @@ access rules, and every explicit user-requested change. Preserve a source-key to
|
|
|
435
445
|
target-route lookup and distinct state/selector identity for repeated sections.
|
|
436
446
|
Record each important decision's source evidence or target adaptation.
|
|
437
447
|
|
|
448
|
+
### Review payments before scaffolding
|
|
449
|
+
|
|
450
|
+
Complete the collected `.himi/one-app/payment-review.json` before generating any
|
|
451
|
+
paid UI. It starts unreviewed, with payment readiness **unverified**. The collector
|
|
452
|
+
has no verified read-only merchant-readiness contract. Installed verticals,
|
|
453
|
+
payment-method definitions, a displayed price, or a denied/unavailable read prove
|
|
454
|
+
neither readiness nor missing setup. Never create a booking, order, checkout or
|
|
455
|
+
payment, or connect a provider, to test readiness. The generated SDK snapshot's
|
|
456
|
+
payins and payment-acceptance configuration methods are excluded or route
|
|
457
|
+
unverified; do not invent an endpoint from their types.
|
|
458
|
+
|
|
459
|
+
Review the source actions, linked screens and current read-only catalog payment
|
|
460
|
+
options. Set `inventoryReviewed: true` and `inventoryEvidence` to the source and
|
|
461
|
+
read evidence. An empty `flows` list also requires the explicit finding
|
|
462
|
+
`noPurchaseFlows: true`, supported by that evidence; an omitted inventory is not a
|
|
463
|
+
no-purchase finding. Keep it false when listing flows. For every flow record a
|
|
464
|
+
unique source-bound `id`, `sourceEvidence` and `payment`: `online-paid`, `free`,
|
|
465
|
+
`in-person`, `pay-later` or `no-payment`. Split mixed online/offline options into
|
|
466
|
+
separate entries. Unknown intent stays unresolved; never turn a paid price into
|
|
467
|
+
free, or hide a flow by default. Preserve existing user decisions and free,
|
|
468
|
+
in-person and pay-later behavior.
|
|
469
|
+
|
|
470
|
+
For each `online-paid` flow, record `readiness: { metaSiteId, state, evidence }`.
|
|
471
|
+
The readiness `metaSiteId` must match this migration's collected site. `state` is
|
|
472
|
+
`ready`, `missing` or `unverified`; cite the site, flow, read/settings evidence and
|
|
473
|
+
when it was checked. Use verified site-bound owner payment settings or a proven
|
|
474
|
+
read-only contract when available. A denied or unavailable read remains
|
|
475
|
+
`unverified`, with the actual access gap recorded. Never infer provider eligibility
|
|
476
|
+
from the site's country. Ready evidence permits `decision: { action: "proceed" }`.
|
|
477
|
+
For missing or unverified setup, tell the owner what is known and ask them to:
|
|
478
|
+
|
|
479
|
+
- `connect-provider`: complete setup in their site dashboard, then recheck before
|
|
480
|
+
generating that paid UI. Migration does not connect the provider itself.
|
|
481
|
+
- `retain`: keep the flow with a localized explanation **before checkout** that
|
|
482
|
+
payments are unavailable or unverified, plus a real contact or back action.
|
|
483
|
+
Record that copy in `decision.explanation` and the actual planned recovery in
|
|
484
|
+
`decision.recovery`: `{ action: "back" }` or `{ action: "contact", target:
|
|
485
|
+
"<real contact route>" }`. Implement the copy and action in the target; the
|
|
486
|
+
checker validates the recorded plan, not the future rendered control.
|
|
487
|
+
- `hide`: hide only the affected paid interaction, preserving its source evidence
|
|
488
|
+
and the remaining free/offline content.
|
|
489
|
+
|
|
490
|
+
Record their instruction in `decision.instruction`; cite an existing explicit
|
|
491
|
+
instruction instead of asking again. Run `himi one-app validate-payments
|
|
492
|
+
--evidence-dir .himi/one-app --json` before scaffolding. It reads this editable
|
|
493
|
+
review and the hash-verified collected source, checks decisions and site binding,
|
|
494
|
+
and creates nothing. A passing check validates the recorded plan, not the truth
|
|
495
|
+
of merchant readiness. The review's original manifest hash describes the initial
|
|
496
|
+
template; edit the review without rewriting collected evidence or manifest hashes.
|
|
497
|
+
Keep the decision, resulting target controls and controlled runtime evidence in
|
|
498
|
+
the reconstruction plan and migration report. For retained flows, handle the exact
|
|
499
|
+
structured `SITE_NOT_ACCEPTING_PAYMENTS` failure with clear recovery; unrelated
|
|
500
|
+
HTTP 428 failures must retain their own meaning. Test paid/free/offline and
|
|
501
|
+
missing/ready/unverified cases without real transactions. General app validation
|
|
502
|
+
and publishing do not gain a new payment gate.
|
|
503
|
+
|
|
438
504
|
Write `SPEC.md` and `MOBILE-UX.md` in the workspace. Use the current
|
|
439
505
|
`himi docs mobile-ux` contract, including its required headings and acceptance
|
|
440
506
|
columns. `SPEC.md` needs a `## Data access` table with a `Screen` column and
|
|
@@ -456,6 +522,58 @@ placement and interaction, record supported, adapted, conditional or unresolved
|
|
|
456
522
|
status, the implementation and evidence, and intentional prompt-driven differences.
|
|
457
523
|
Track dangling/unreachable relationships and unknown source context explicitly.
|
|
458
524
|
|
|
525
|
+
### Account for live data on every tab
|
|
526
|
+
|
|
527
|
+
Before filling or replacing a section, record its data availability in the
|
|
528
|
+
reconstruction plan and keep the result in the migration report. Include every
|
|
529
|
+
visible target tab, including a page whose only source section cannot load.
|
|
530
|
+
For each data-backed section, record:
|
|
531
|
+
|
|
532
|
+
- The source placement and selector, target tab/screen, source MetaSite ID and
|
|
533
|
+
required service from `business-context.json.services`. A service entry proves
|
|
534
|
+
installation in that collected context, not that records exist or that the
|
|
535
|
+
target viewer can read them. Its installation ID is not a record ID; an
|
|
536
|
+
inactive mobile-service flag alone does not prove the site's records are gone.
|
|
537
|
+
If the installation inventory is unavailable, record that uncertainty.
|
|
538
|
+
- The verified read-only request and viewer context used to check the selected
|
|
539
|
+
records. Preserve the source filters and IDs; an unrelated nonempty catalog
|
|
540
|
+
does not prove that a referenced group, post or program exists. Owner access
|
|
541
|
+
alone does not prove access through the generated app's visitor/member
|
|
542
|
+
transport. Reference the retained read result or runtime evidence without
|
|
543
|
+
copying private records or credentials into the report.
|
|
544
|
+
- The actual outcome: available records, a valid empty result, missing source
|
|
545
|
+
record, service not installed, access denied, request failure or unverified
|
|
546
|
+
read. Keep those outcomes distinct. A failed request or missing source record
|
|
547
|
+
is not a successful empty result.
|
|
548
|
+
- The decision and displayed state: retain the live section, show its meaningful
|
|
549
|
+
empty/access/error state, or implement an explicitly authorized replacement.
|
|
550
|
+
Record the reason when a tab has no usable content. Keep its navigation
|
|
551
|
+
reachable with an explanation; never leave only blank space and chrome.
|
|
552
|
+
|
|
553
|
+
Use an installed vertical only when it belongs to this same site and fits the
|
|
554
|
+
source feature or the user's authorized replacement. Follow the existing scope
|
|
555
|
+
when that decision is already recorded; otherwise report the proposed adaptation
|
|
556
|
+
rather than silently adding unrelated features because they are installed.
|
|
557
|
+
Verify the replacement's own live records and runtime access before treating it
|
|
558
|
+
as available. If no eligible live data can be verified, retain an explicit gap
|
|
559
|
+
and an honest visible state. Do not create or seed records to fill the screen.
|
|
560
|
+
Static editor-authored text is source content, not a sample dataset.
|
|
561
|
+
|
|
562
|
+
After implementation, visit every target tab after its reads settle, including
|
|
563
|
+
the tab with the missing or empty source section. Check the visible explanation
|
|
564
|
+
and any real recovery action against the recorded outcome. Keep the report and
|
|
565
|
+
the existing route/placement coverage rows consistent; neither a heading nor a
|
|
566
|
+
successful schema check proves that a tab has usable content.
|
|
567
|
+
|
|
568
|
+
Keep test data separate from this availability evidence. The default hermetic
|
|
569
|
+
`himi test` may use canned fixtures; a passing crawl proves behavior with those
|
|
570
|
+
fixtures, not that the source site has those records. Use the read-only recording
|
|
571
|
+
and private-fixture procedure in step 4 for safe source reads, and inspect the
|
|
572
|
+
actual hosted app's same-site transport. Remove template sample records and
|
|
573
|
+
fixture fallbacks from published worker/config paths. Never turn an empty or
|
|
574
|
+
failed live read into synthetic products, posts, members, groups or services,
|
|
575
|
+
and never present a mock-backed screenshot as live-data evidence.
|
|
576
|
+
|
|
459
577
|
Run `himi one-app coverage init --evidence-dir .himi/one-app --content .` to
|
|
460
578
|
create `ONE-APP-COVERAGE.json` from the verified source graph. After edits or a
|
|
461
579
|
CLI upgrade, run `coverage sync` and review new rows; `coverage check` fails when
|
|
@@ -8904,8 +8904,8 @@
|
|
|
8904
8904
|
{
|
|
8905
8905
|
"name": "getPost",
|
|
8906
8906
|
"kind": "function",
|
|
8907
|
-
"jsdoc": "/** Fetch a single PUBLISHED post by id — GET /blog/v3/posts/{id}. The list\n * (queryPosts) returns published posts, so the detail reads them here (draft\n * ids are a different space — getDraftPost is for the edit/create flow).\n *\n * Body fieldsets (#826): Blog v3 `GET /posts/{id}` returns 200 but only the\n * LIST projection (title / excerpt / meta) unless the caller asks for the body —\n * the bare GET has NO `richContent` / `contentText`. So we request\n * RICH_CONTENT + CONTENT_TEXT by default and the article body loads on the happy\n * path; callers can override via `req.fieldsets`. (Supersedes the old #507 note:\n * the 502 this GET once returned for anonymous visitors is gone — it 200s now —\n * but a body-less 200 was never the \"body preserved whenever the backend is\n * healthy\" the earlier comment claimed; the body was simply never requested.)\n *\n * On
|
|
8908
|
-
"decl": "export function getPost(\n postId: string,\n req: WixGetPostRequest = {}, readOptions?: WixCallOptions,\n): Promise<WixGetPostResponse>;",
|
|
8907
|
+
"jsdoc": "/** Fetch a single PUBLISHED post by id — GET /blog/v3/posts/{id}. The list\n * (queryPosts) returns published posts, so the detail reads them here (draft\n * ids are a different space — getDraftPost is for the edit/create flow).\n *\n * Body fieldsets (#826): Blog v3 `GET /posts/{id}` returns 200 but only the\n * LIST projection (title / excerpt / meta) unless the caller asks for the body —\n * the bare GET has NO `richContent` / `contentText`. So we request\n * RICH_CONTENT + CONTENT_TEXT by default and the article body loads on the happy\n * path; callers can override via `req.fieldsets`. (Supersedes the old #507 note:\n * the 502 this GET once returned for anonymous visitors is gone — it 200s now —\n * but a body-less 200 was never the \"body preserved whenever the backend is\n * healthy\" the earlier comment claimed; the body was simply never requested.)\n *\n * On network / backend rejection we still fall back to a queryPosts\n * filter-by-id — a graceful degrade to the LIST projection (no body) that keeps\n * blog detail + saved screens rendering instead of erroring out. `queryPosts`'s\n * request type has no `fieldsets` field, so that fallback path cannot carry the\n * body. Detail readers can opt into `preserveAccessErrors` to keep HTTP 401/403\n * refusals instead of a public metadata fallback; default callers are unchanged.\n *\n * When that fallback ALSO finds nothing — a deleted post, or one this visitor\n * may not see — we reject rather than resolve. `WixGetPostResponse.post`\n * is non-optional, so returning `{ post: resp.posts?.[0] }` would hand callers an\n * `undefined` the type promises is present, and the `post.title` access that type\n * licenses throws a `TypeError` deep inside the caller — the exact hard error\n * this fallback exists to prevent, and harder to diagnose than a clean rejection.\n * Every call site rides its own rejection path (cached-post state, draft-only\n * `catch`, or an `allSettled` filter), so rejecting is what routes them into the\n * graceful degrade they already implement. The originating failure travels as\n * `cause`, so the 404-vs-network distinction is not lost. */",
|
|
8908
|
+
"decl": "export function getPost(\n postId: string,\n req: WixGetPostRequest = {}, readOptions?: WixCallOptions & { preserveAccessErrors?: boolean },\n): Promise<WixGetPostResponse>;",
|
|
8909
8909
|
"http": "GET /blog/v3/posts/{…}"
|
|
8910
8910
|
},
|
|
8911
8911
|
{
|
package/guides/splash-screens.md
CHANGED
|
@@ -186,7 +186,16 @@ Android) calls `BootReadiness.markFirstScreen()`. The ceiling still applies. `re
|
|
|
186
186
|
releases the splash on the seed alone. Only a shell that mounts its content under the splash may
|
|
187
187
|
wait for the first screen: one that mounts it after the splash leaves would hold to the ceiling,
|
|
188
188
|
which is why `effectiveReady(_:)` without `awaitingFirstScreen` still means the seed alone.
|
|
189
|
-
`splash-parity.test.ts` fails a shell that shows its splash instead of its content.
|
|
189
|
+
`splash-parity.test.ts` fails a shell that shows its splash instead of its content. On web, the host showed its
|
|
190
|
+
splash only while the app's config loaded and then mounted the shell with a loading route under no splash at all;
|
|
191
|
+
the web host now renders the shell under its launch gate, which holds the splash by the same rule: the config known,
|
|
192
|
+
the first screen painted and past the floor, or the ceiling, which reports `splash_timeout` (also when the config load
|
|
193
|
+
alone outlasted it: the shell then mounts at once, and the timeout is still reported). What counts as the first screen
|
|
194
|
+
is the route painting its screen, or any surface that takes its place for good: the screen's unavailable and
|
|
195
|
+
render-failed states, the account sign-in, an owner-preview refusal, a blocking maintenance or force-update cover, and
|
|
196
|
+
a crash of the whole shell. A config that drops its splash mid-launch releases the shell at once. While the splash
|
|
197
|
+
holds, the shell under it is inert and stacked below it, so neither Tab nor a screen reader reaches it and none of its
|
|
198
|
+
layers paints over the splash; a first screen's `initialFocus` target takes focus when the splash goes.
|
|
190
199
|
|
|
191
200
|
The splash surface eases its logo and caption in once per launch. A tokens revision re-keys
|
|
192
201
|
everything under `HimalayaAppRoot`, the splash included, and a first launch's served tokens usually
|
|
@@ -118,6 +118,11 @@ no longer in the shape `himi init` writes (a block, the `navigation` object or
|
|
|
118
118
|
left, and when dropping links would leave a kept screen empty or cannot reach a link. Then trim
|
|
119
119
|
by hand, below.
|
|
120
120
|
|
|
121
|
+
The trim also corrects `capabilities` in `config.ts` for the screens you kept, so the binary does
|
|
122
|
+
not declare permissions for screens you removed. The report's `capabilities` field lists what it
|
|
123
|
+
removed and added. If a later release will use a removed capability, add it back to `capabilities`
|
|
124
|
+
before you build the binary. A list that is not a plain JSON array is left alone and reported.
|
|
125
|
+
|
|
121
126
|
The `navigation` object and `workerSubtreeScreens` array in `config.ts` may contain
|
|
122
127
|
`//` or `/* … */` comments. Read-only template commands preserve their
|
|
123
128
|
source bytes. Commands that rewrite navigation, including trim and `template screens --add`,
|
|
@@ -128,10 +128,19 @@ never change. If nothing is reachable, keep the default kit.
|
|
|
128
128
|
address/physical intent/coordinates, phone and email; preserve source field
|
|
129
129
|
visibility and never substitute a manager properties call for a failed public
|
|
130
130
|
read. `business_contact_unavailable` means the contact data is still unverified.
|
|
131
|
-
Its optional `businessContext.memberCount` retains only a valid Business aggregate
|
|
132
|
-
count; it never exports the member list or infers a total from preview rows.
|
|
133
131
|
Site analysis saves this projection in `evidence/mobile-app/business-context.json`
|
|
134
132
|
and states unavailable Contact reads in the mobile-app chapter's note.
|
|
133
|
+
- **One App member totals.** Optional `businessContext.memberCount` preserves a valid
|
|
134
|
+
Business aggregate when available. For placed normal/BP member widgets
|
|
135
|
+
(`platform_general_members_section` / `platform_general_members_section_bp`),
|
|
136
|
+
the collector instead reads the member-list response's explicit `total` with a
|
|
137
|
+
site-bound Visitor identity, matching the source widget. The one-row query's
|
|
138
|
+
page length is never the total; member records are discarded. Zero is valid.
|
|
139
|
+
Missing/invalid totals and unavailable/forbidden reads leave the field absent
|
|
140
|
+
and record `business_member_count_unavailable`, with no user/private-count
|
|
141
|
+
fallback. Site analysis carries that warning into its mobile-app chapter note.
|
|
142
|
+
This is a build-time, Visitor-visible source snapshot; it can differ from a
|
|
143
|
+
signed-in member/manager total and is not a live membership subscription.
|
|
135
144
|
- **One door.** Use `site.getBusinessInfo()`, not a hand-rolled
|
|
136
145
|
`wixApi.call(".../site-properties/...")` — same rule as `auth.signedInstance` (RFC 0043).
|
|
137
146
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wix/himalaya-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.1571.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": [
|
|
@@ -71,5 +71,5 @@
|
|
|
71
71
|
"artifactId": "himalaya-cli"
|
|
72
72
|
}
|
|
73
73
|
},
|
|
74
|
-
"falconPackageHash": "
|
|
74
|
+
"falconPackageHash": "fec01235dfeb9ac45a855684328263dcff0fa8e5ecf05db7d6e9b9da"
|
|
75
75
|
}
|
|
@@ -37,7 +37,7 @@ export default defineApp({
|
|
|
37
37
|
actions: "http://localhost:8190/v1/actions",
|
|
38
38
|
sync: "http://localhost:8190/v1/sync",
|
|
39
39
|
},
|
|
40
|
-
capabilities: ["live_activity","push","share","secure_storage","open_url","calendar","local_database","wix_api","wix_platform","app_info","iap","release_control","clipboard","theme"],
|
|
40
|
+
capabilities: ["live_activity","push","share","secure_storage","open_url","calendar","local_database","wix_api","wix_platform","app_info","iap","release_control","clipboard","theme","wix_cashier"],
|
|
41
41
|
cache: {
|
|
42
42
|
"rules": [
|
|
43
43
|
{
|
|
@@ -40229,6 +40229,9 @@ const TEMPLATED: { pattern: string; display: string; liveSeed?: LiveSeed; static
|
|
|
40229
40229
|
"style": "body",
|
|
40230
40230
|
"color": "textSecondary"
|
|
40231
40231
|
}
|
|
40232
|
+
},
|
|
40233
|
+
"modifiers": {
|
|
40234
|
+
"hiddenPath": "$service:himiBlogPost.signInRequired"
|
|
40232
40235
|
}
|
|
40233
40236
|
},
|
|
40234
40237
|
{
|
|
@@ -40243,6 +40246,32 @@ const TEMPLATED: { pattern: string; display: string; liveSeed?: LiveSeed; static
|
|
|
40243
40246
|
"actionId": "tier5.himiBlogPost.retry"
|
|
40244
40247
|
}
|
|
40245
40248
|
}
|
|
40249
|
+
},
|
|
40250
|
+
"modifiers": {
|
|
40251
|
+
"hiddenPath": "$service:himiBlogPost.signInRequired"
|
|
40252
|
+
}
|
|
40253
|
+
},
|
|
40254
|
+
{
|
|
40255
|
+
"id": "himi_blog_post.retry.sign_in",
|
|
40256
|
+
"body": {
|
|
40257
|
+
"package": "himalaya",
|
|
40258
|
+
"type": "Button",
|
|
40259
|
+
"props": {
|
|
40260
|
+
"title": "Sign in",
|
|
40261
|
+
"style": "primary",
|
|
40262
|
+
"action": {
|
|
40263
|
+
"actionId": "himi.open",
|
|
40264
|
+
"params": {
|
|
40265
|
+
"screenId": "himi_sign_in",
|
|
40266
|
+
"mode": "signin",
|
|
40267
|
+
"modal": "1"
|
|
40268
|
+
},
|
|
40269
|
+
"presentation": "sheet"
|
|
40270
|
+
}
|
|
40271
|
+
}
|
|
40272
|
+
},
|
|
40273
|
+
"modifiers": {
|
|
40274
|
+
"hiddenPath": "$service:himiBlogPost.noSignIn"
|
|
40246
40275
|
}
|
|
40247
40276
|
}
|
|
40248
40277
|
]
|
|
@@ -40947,7 +40976,8 @@ const TEMPLATED: { pattern: string; display: string; liveSeed?: LiveSeed; static
|
|
|
40947
40976
|
"onAppear": {
|
|
40948
40977
|
"actionId": "tier5.himiBlogPost.load",
|
|
40949
40978
|
"params": {
|
|
40950
|
-
"id": "__HIMI_ID__"
|
|
40979
|
+
"id": "__HIMI_ID__",
|
|
40980
|
+
"signInScreenId": "himi_sign_in"
|
|
40951
40981
|
}
|
|
40952
40982
|
}
|
|
40953
40983
|
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { memberAccess, memberAccessChanged, memberAccessDenied, type MemberAccess } from "./member-access.js";
|
|
2
|
+
import { needsSignIn } from "./branded-load-state.js";
|
|
3
|
+
|
|
4
|
+
/** Public reads stay public. Capture their reader only to reject a result that
|
|
5
|
+
* belongs to the identity/site which was active before the request. */
|
|
6
|
+
export interface ReadAccess { member: MemberAccess; tenant: string; }
|
|
7
|
+
export async function readAccess(): Promise<ReadAccess> {
|
|
8
|
+
const member = await memberAccess();
|
|
9
|
+
const input = (globalThis as { himalaya?: { input?: Record<string, unknown> } }).himalaya?.input;
|
|
10
|
+
return { member, tenant: JSON.stringify([input?.__himiPreferenceAccount ?? "", input?.__himiPreferenceBusiness ?? ""]) };
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export async function assertReadAccess(access: ReadAccess): Promise<void> {
|
|
14
|
+
const current = await readAccess();
|
|
15
|
+
if (current.member.email !== access.member.email || current.member.scope !== access.member.scope || current.tenant !== access.tenant) throw memberAccessChanged();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Bridge errors put the actual status first; codes quoted inside the body are data. */
|
|
19
|
+
function httpStatus(error: unknown): string | undefined {
|
|
20
|
+
return /^HTTP\s*(\d{3})\b/i.exec(String((error as { message?: unknown })?.message ?? error))?.[1];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const NO_READ_SIGN_IN = { signInRequired: false, noSignIn: true };
|
|
24
|
+
|
|
25
|
+
/** An authorization refusal must not fall back to a previously cached article. */
|
|
26
|
+
export function readAccessDenied(error: unknown): boolean {
|
|
27
|
+
let cause = error;
|
|
28
|
+
for (let depth = 0; cause && depth < 4; depth++) {
|
|
29
|
+
const status = httpStatus(cause);
|
|
30
|
+
if (status ? status === "401" || status === "403" : memberAccessDenied(cause) || needsSignIn(cause)) return true;
|
|
31
|
+
cause = (cause as { cause?: unknown }).cause;
|
|
32
|
+
}
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** A permission failure alone says nothing about whether signing in can help.
|
|
37
|
+
* In particular, a private member profile can be unavailable to members too.
|
|
38
|
+
* Only an authentication refusal for a current visitor offers Sign in. Follow
|
|
39
|
+
* SDK wrapper causes (blog getPost preserves its original read error there). */
|
|
40
|
+
export async function readSignInFlags(error: unknown): Promise<typeof NO_READ_SIGN_IN> {
|
|
41
|
+
let authenticationRequired = false;
|
|
42
|
+
let permissionRefused = false;
|
|
43
|
+
let cause = error;
|
|
44
|
+
for (let depth = 0; cause && depth < 4; depth++) {
|
|
45
|
+
const status = httpStatus(cause);
|
|
46
|
+
permissionRefused ||= status === "403";
|
|
47
|
+
authenticationRequired ||= status ? status === "401" : needsSignIn(cause);
|
|
48
|
+
cause = (cause as { cause?: unknown }).cause;
|
|
49
|
+
}
|
|
50
|
+
const signInRequired = !permissionRefused && authenticationRequired && !(await memberAccess()).email;
|
|
51
|
+
return { signInRequired, noSignIn: !signInRequired };
|
|
52
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { WixProgram } from "@himalaya/wix-online-programs";
|
|
2
|
+
|
|
3
|
+
/** v3 nests the body in description.details; older responses use settings or a
|
|
4
|
+
* flat description. The body itself may be a Ricos document or serialized text. */
|
|
5
|
+
export function programDescription(program: WixProgram): unknown {
|
|
6
|
+
const raw = (program as { description?: unknown }).description;
|
|
7
|
+
const nested = raw && typeof raw === "object" ? raw as { details?: unknown; nodes?: unknown } : undefined;
|
|
8
|
+
return program.settings?.description ?? nested?.details
|
|
9
|
+
?? (typeof raw === "string" || Array.isArray(nested?.nodes) ? raw : undefined)
|
|
10
|
+
?? program.details?.description;
|
|
11
|
+
}
|
|
@@ -9,6 +9,7 @@ import { stashCopy, copyParam, copyTemplate } from "../_shared/himi-copy.js";
|
|
|
9
9
|
import { formatDateMedium, formatMonthDay } from "../_shared/engine-intl.js";
|
|
10
10
|
import { openRichLink } from "../_shared/rich-content-link.js";
|
|
11
11
|
import { richBody, ROW_IMAGE_WIDTH } from "../_shared/rich-body.js";
|
|
12
|
+
import { readAccess, assertReadAccess, readAccessDenied, readSignInFlags, NO_READ_SIGN_IN } from "../_shared/member-read.js";
|
|
12
13
|
|
|
13
14
|
// Offline-first (data-offline): the post body is a public read-through — when the
|
|
14
15
|
// live fetch fails (offline) fall back to the last-synced row the feed cached under
|
|
@@ -34,6 +35,7 @@ interface CommentRow {
|
|
|
34
35
|
}
|
|
35
36
|
|
|
36
37
|
interface S {
|
|
38
|
+
signInRequired: boolean; noSignIn: boolean; signInScreenId: string;
|
|
37
39
|
status: "loading" | "ready" | "error";
|
|
38
40
|
id: string;
|
|
39
41
|
title: string;
|
|
@@ -214,6 +216,7 @@ function derive(s: S): S {
|
|
|
214
216
|
}
|
|
215
217
|
|
|
216
218
|
const baseState: S = derive({
|
|
219
|
+
...NO_READ_SIGN_IN, signInScreenId: "himi_account",
|
|
217
220
|
status: "loading",
|
|
218
221
|
id: "",
|
|
219
222
|
title: "",
|
|
@@ -280,23 +283,28 @@ async function cachedPostState(s: S, id: string): Promise<S> {
|
|
|
280
283
|
});
|
|
281
284
|
}
|
|
282
285
|
|
|
283
|
-
|
|
286
|
+
let loadVersion = 0;
|
|
287
|
+
async function loadPostState(s: S): Promise<S | undefined> {
|
|
288
|
+
const version = ++loadVersion;
|
|
289
|
+
await svc.prepare();
|
|
290
|
+
const clean = { ...baseState, signInScreenId: s.signInScreenId };
|
|
284
291
|
const id = inputId();
|
|
285
292
|
if (!id) {
|
|
286
|
-
return derive({ ...
|
|
293
|
+
return derive({ ...clean, status: "ready", emptyText: copyParam("blogPost.notFoundText", "Post not found.") });
|
|
287
294
|
}
|
|
288
295
|
try {
|
|
289
|
-
const
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
296
|
+
const access = await readAccess();
|
|
297
|
+
const [postResult, commentsResult] = await Promise.allSettled([getPost(id, {}, { preserveAccessErrors: true }), listBlogComments(id)]);
|
|
298
|
+
if (postResult.status === "rejected") throw postResult.reason;
|
|
299
|
+
await assertReadAccess(access);
|
|
300
|
+
if (version !== loadVersion) return undefined;
|
|
293
301
|
const post = (((postResult.value as unknown as { post?: WixPost }).post) ?? postResult.value) as PostWithExtras;
|
|
294
302
|
const commentsUnavailable = commentsResult.status === "rejected";
|
|
295
303
|
const comments = commentsResult.status === "fulfilled" ? commentsResult.value.map(toCommentRow) : [];
|
|
296
304
|
const cover = imageUrl(post);
|
|
297
305
|
const title = post.title || "Untitled post";
|
|
298
306
|
return derive({
|
|
299
|
-
...
|
|
307
|
+
...clean,
|
|
300
308
|
status: "ready",
|
|
301
309
|
id,
|
|
302
310
|
title,
|
|
@@ -319,14 +327,23 @@ async function loadPostState(s: S): Promise<S> {
|
|
|
319
327
|
errorText: "",
|
|
320
328
|
emptyText: "",
|
|
321
329
|
});
|
|
322
|
-
} catch {
|
|
323
|
-
|
|
330
|
+
} catch (error) {
|
|
331
|
+
const flags = await readSignInFlags(error);
|
|
332
|
+
const failed = derive({ ...clean, ...flags, status: "error", id, errorText: copyParam("blogPost.errorText", "This post could not load.") });
|
|
333
|
+
if (version !== loadVersion) return undefined;
|
|
334
|
+
if (readAccessDenied(error)) return failed;
|
|
335
|
+
const access = await readAccess();
|
|
336
|
+
const cached = await cachedPostState(clean, id);
|
|
337
|
+
try { await assertReadAccess(access); }
|
|
338
|
+
catch { return version === loadVersion ? failed : undefined; }
|
|
339
|
+
return version === loadVersion ? cached : undefined;
|
|
324
340
|
}
|
|
325
341
|
}
|
|
326
342
|
|
|
327
343
|
const svc = defineService<S, {
|
|
328
|
-
|
|
329
|
-
|
|
344
|
+
prepare: (s: S) => S;
|
|
345
|
+
load: (s: S, arg?: unknown) => Promise<S | undefined>;
|
|
346
|
+
retry: (s: S) => Promise<S | undefined>;
|
|
330
347
|
like: (s: S) => Promise<S>;
|
|
331
348
|
unlike: (s: S) => Promise<S>;
|
|
332
349
|
toggleSave: (s: S) => S;
|
|
@@ -338,7 +355,12 @@ const svc = defineService<S, {
|
|
|
338
355
|
}>("himiBlogPost", {
|
|
339
356
|
state: baseState,
|
|
340
357
|
actions: {
|
|
341
|
-
|
|
358
|
+
prepare: (s) => ({ ...s, ...baseState }),
|
|
359
|
+
load: (s, arg) => {
|
|
360
|
+
stashCopy(arg);
|
|
361
|
+
const target = (arg as { signInScreenId?: unknown })?.signInScreenId;
|
|
362
|
+
return loadPostState({ ...s, signInScreenId: target === "himi_sign_in" ? target : s.signInScreenId });
|
|
363
|
+
},
|
|
342
364
|
retry: (s) => loadPostState({ ...s, status: "loading", errorText: "" }),
|
|
343
365
|
like: async (s) => {
|
|
344
366
|
if (!s.id) return derive({ ...s, actionMessage: copyParam("blogPost.noPostMessage", "No post selected.") });
|
|
@@ -409,5 +431,6 @@ const svc = defineService<S, {
|
|
|
409
431
|
},
|
|
410
432
|
});
|
|
411
433
|
|
|
434
|
+
svc.prepare();
|
|
412
435
|
svc.load();
|
|
413
436
|
export { svc };
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { descriptionBody } from "../_shared/rich-body.js";
|
|
2
|
+
import { programDescription } from "../_shared/program-description.js";
|
|
1
3
|
import { fetchMemberBookings, type MemberBookingRow } from "../_shared/member-bookings.js";
|
|
2
4
|
import { hostSiteFromInput, hostedExploreCards, hostedHiddenFlags, type HostedExploreCard } from "../_shared/branded-lite-hosted.js";
|
|
3
5
|
import { memberAccess, assertMemberAccess } from "../_shared/member-access.js";
|
|
@@ -675,11 +677,9 @@ async function fetchHomePrograms(): Promise<ProgramCard[]> {
|
|
|
675
677
|
.map((p: WixProgram) => {
|
|
676
678
|
const desc = (p as { description?: unknown }).description;
|
|
677
679
|
const nestedTitle = desc && typeof desc === "object" ? (desc as { title?: string }).title : undefined;
|
|
678
|
-
const nestedDetails = desc && typeof desc === "object" ? (desc as { details?: string }).details : undefined;
|
|
679
680
|
const name = cleanTitle(p.settings?.title ?? nestedTitle ?? p.title) || copyParam("home.programFallbackName", "Program");
|
|
680
681
|
const image = programImage(p);
|
|
681
|
-
const details = (p.
|
|
682
|
-
.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
|
|
682
|
+
const details = descriptionBody(programDescription(p)).bodyText.replace(/\s+/g, " ").trim();
|
|
683
683
|
return {
|
|
684
684
|
id: p.id || name,
|
|
685
685
|
name,
|
|
@@ -15,7 +15,7 @@ import { stashCopy, copyParam, copyTemplate } from "../_shared/himi-copy.js";
|
|
|
15
15
|
import { isBridgeUnavailable } from "../_shared/owner-load-error.js";
|
|
16
16
|
import { isProductSaved, toggleSavedProduct } from "../_shared/himi-store-saved.js";
|
|
17
17
|
import { openRichLink } from "../_shared/rich-content-link.js";
|
|
18
|
-
import {
|
|
18
|
+
import { descriptionBody } from "../_shared/rich-body.js";
|
|
19
19
|
|
|
20
20
|
// Offline-first (data-offline): the detail is a public READ-THROUGH — when the live
|
|
21
21
|
// product fetch fails (offline) fall back to the last-synced row the catalog cached
|
|
@@ -85,9 +85,7 @@ function productPolicyBody(product: StoreProduct, kind: "returns" | "shipping"):
|
|
|
85
85
|
});
|
|
86
86
|
if (!section) return "";
|
|
87
87
|
const html = "plainDescription" in section ? section.plainDescription : undefined;
|
|
88
|
-
|
|
89
|
-
if (typeof section.description === "string") return plainText(section.description.replace(/ /gi, " "));
|
|
90
|
-
return richBody(section.description).bodyText;
|
|
88
|
+
return descriptionBody(html || section.description).bodyText;
|
|
91
89
|
}
|
|
92
90
|
|
|
93
91
|
/** The product id `lastProduct` was read for. A pooled screen re-appearing calls
|
|
@@ -170,13 +168,12 @@ function optionsHint(groups: StoreOption[], selection: StoreOptionSelection): st
|
|
|
170
168
|
return copyTemplate("product.optionsChoose", "Choose {options}", { options: missing.join(", ") });
|
|
171
169
|
}
|
|
172
170
|
|
|
173
|
-
/**
|
|
174
|
-
*
|
|
175
|
-
* `description` is an HTML string: stripped text, no document. */
|
|
171
|
+
/** Catalog V3 returns Ricos objects or serialized documents; V1 returns HTML.
|
|
172
|
+
* Keep plainDescription as the independent fallback, never raw document JSON. */
|
|
176
173
|
function productBody(p: StoreProduct): { description: string; descriptionJson: string } {
|
|
177
174
|
const raw = (p as { description?: unknown }).description;
|
|
178
|
-
const html =
|
|
179
|
-
const { bodyJson, bodyText } =
|
|
175
|
+
const html = descriptionBody(p.plainDescription).bodyText;
|
|
176
|
+
const { bodyJson, bodyText } = descriptionBody(raw, html);
|
|
180
177
|
return { description: html || bodyText, descriptionJson: bodyJson };
|
|
181
178
|
}
|
|
182
179
|
|
|
@@ -37,7 +37,7 @@ export default defineApp({
|
|
|
37
37
|
actions: "http://localhost:8190/v1/actions",
|
|
38
38
|
sync: "http://localhost:8190/v1/sync",
|
|
39
39
|
},
|
|
40
|
-
capabilities: ["live_activity","push","share","secure_storage","open_url","local_database","clipboard","ui","speech","camera","photos","file_picker","files","presigned_upload","app_info","biometrics","wix_platform","
|
|
40
|
+
capabilities: ["live_activity","push","share","secure_storage","open_url","local_database","clipboard","ui","speech","camera","photos","file_picker","files","presigned_upload","app_info","biometrics","wix_platform","stream","wix_api"],
|
|
41
41
|
designTokensPath: "./tokens.json",
|
|
42
42
|
// Native fallback; the T5 splash descriptor owns the editable launch appearance.
|
|
43
43
|
splash: {
|
|
@@ -37,7 +37,7 @@ export default defineApp({
|
|
|
37
37
|
actions: "http://localhost:8190/v1/actions",
|
|
38
38
|
sync: "http://localhost:8190/v1/sync",
|
|
39
39
|
},
|
|
40
|
-
capabilities: ["live_activity","push","share","secure_storage","open_url","calendar","local_database","media_playback","wix_api","wix_platform","app_info","iap","release_control","clipboard","motion","wix_chat","theme"],
|
|
40
|
+
capabilities: ["live_activity","push","share","secure_storage","open_url","calendar","local_database","media_playback","wix_api","wix_platform","app_info","iap","release_control","clipboard","motion","wix_chat","theme","wix_cashier"],
|
|
41
41
|
cache: {
|
|
42
42
|
"rules": [
|
|
43
43
|
{
|