@wix/himalaya-cli 0.1557.0 → 0.1559.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/ts/src/schema.d.ts +6 -4
  2. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/media/src/index.d.ts +1 -1
  3. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/online-programs/src/index.d.ts +14 -5
  4. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/core.mjs +2 -2
  5. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-events.mjs +1 -0
  6. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-media.mjs +1 -0
  7. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-online-programs.mjs +35 -7
  8. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-rich-content.mjs +1 -0
  9. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-stores-collections.mjs +1 -0
  10. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-stores.mjs +1 -0
  11. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-video.mjs +1 -0
  12. package/dist/cli.mjs +164 -61
  13. package/dist/preview-assets.generated.bin +0 -0
  14. package/guides/getting-started.md +4 -1
  15. package/guides/himi-json-output.md +8 -0
  16. package/guides/himi-skill.md +4 -1
  17. package/guides/one-app-to-himi-app.md +18 -6
  18. package/guides/sdk-reference.json +8 -8
  19. package/guides/skill.md +4 -1
  20. package/guides/splash-screens.md +22 -1
  21. package/package.json +2 -2
  22. package/templates/beauty-salon/tier5-src/_shared/himi-media.ts +29 -0
  23. package/templates/beauty-salon/tier5-src/himi-branded-lite-home/index.ts +3 -4
  24. package/templates/branded-lite/tier5-src/_shared/himi-media.ts +29 -0
  25. package/templates/branded-lite/tier5-src/himi-branded-lite-group/index.ts +3 -6
  26. package/templates/branded-lite/tier5-src/himi-branded-lite-groups/index.ts +2 -15
  27. package/templates/branded-lite/tier5-src/himi-branded-lite-home/index.ts +3 -4
  28. package/templates/branded-lite/tier5-src/himi-branded-lite-program/index.ts +21 -20
  29. package/templates/branded-lite/tier5-src/himi-branded-lite-programs/index.ts +1 -9
  30. package/templates/community-club/tier5-src/_shared/himi-media.ts +29 -0
  31. package/templates/community-club/tier5-src/himi-branded-lite-group/index.ts +3 -6
  32. package/templates/community-club/tier5-src/himi-branded-lite-groups/index.ts +2 -15
  33. package/templates/community-club/tier5-src/himi-branded-lite-home/index.ts +3 -4
  34. package/templates/events-meetups/tier5-src/_shared/himi-media.ts +29 -0
  35. package/templates/events-meetups/tier5-src/himi-branded-lite-group/index.ts +3 -6
  36. package/templates/events-meetups/tier5-src/himi-branded-lite-groups/index.ts +2 -15
  37. package/templates/events-meetups/tier5-src/himi-branded-lite-home/index.ts +3 -4
  38. package/templates/gym-fitness/tier5-src/_shared/himi-media.ts +29 -0
  39. package/templates/gym-fitness/tier5-src/himi-branded-lite-home/index.ts +3 -4
  40. package/templates/nonprofit-cause/tier5-src/_shared/himi-media.ts +29 -0
  41. package/templates/nonprofit-cause/tier5-src/himi-branded-lite-group/index.ts +3 -6
  42. package/templates/nonprofit-cause/tier5-src/himi-branded-lite-groups/index.ts +2 -15
  43. package/templates/nonprofit-cause/tier5-src/himi-branded-lite-home/index.ts +3 -4
  44. package/templates/podcast-creator/tier5-src/_shared/himi-media.ts +29 -0
  45. package/templates/podcast-creator/tier5-src/himi-branded-lite-home/index.ts +3 -4
  46. package/templates/portfolio-creative/tier5-src/_shared/himi-media.ts +29 -0
  47. package/templates/portfolio-creative/tier5-src/himi-branded-lite-home/index.ts +3 -4
  48. package/templates/restaurant/tier5-src/_shared/himi-media.ts +29 -0
  49. package/templates/restaurant/tier5-src/himi-branded-lite-home/index.ts +3 -4
  50. package/templates/retail-store/tier5-src/_shared/himi-media.ts +29 -0
  51. package/templates/retail-store/tier5-src/himi-branded-lite-home/index.ts +3 -4
  52. package/templates/yoga-studio/tier5-src/_shared/himi-media.ts +29 -0
  53. package/templates/yoga-studio/tier5-src/himi-branded-lite-home/index.ts +3 -4
  54. package/templates/yoga-studio/tier5-src/himi-branded-lite-program/index.ts +21 -20
  55. package/templates/yoga-studio/tier5-src/himi-branded-lite-programs/index.ts +1 -9
@@ -30,7 +30,10 @@ carry any explicit user changes in its separate prompt option.
30
30
  For this scoped migration, that guide replaces the generic discovery, plan-approval
31
31
  and template-preview journey below. Write the reconstruction plan, `SPEC.md`,
32
32
  `MOBILE-UX.md` and migration report, then continue implementation and the authorized
33
- same-site OAuth setup, checks and hosted draft. Preserve the supplied layout and
33
+ same-site One App visitor binding, checks and hosted draft. Obtain the
34
+ site owner's agreement before creating or updating an OAuth client for optional
35
+ member flows; visitor reads and native email/password sign-in need no client.
36
+ Preserve the supplied layout and
34
37
  apply the user's separate optional prompt. Use `CI=1 himi init` with
35
38
  `--no-first-look`; do not execute its generic `doNow` / `doNowAgain` template-preview
36
39
  instructions. Show the reconstructed app after adapting it and checking its data.
@@ -258,6 +258,14 @@ redirects, invalid data, source changes and disk errors remain failures.
258
258
  (exit 1, stage `checking-cli-version`). Restore registry access and retry before collection.
259
259
  - `auth_unavailable`: a user-scoped Wix token could not be obtained (exit 1).
260
260
  `WIX_API_KEY` is not used by this workflow.
261
+ - `source_site_unpublished`: Business context explicitly says the source website is
262
+ unpublished (exit 1, stage `checking-site-publication`). Ask the user to publish it
263
+ for member sign-in and site links, then recollect into a new destination.
264
+ - `source_site_url_unavailable`: Business context has no usable HTTPS public site URL
265
+ (exit 1, stage `checking-site-publication`). Verify publication and the site's real
266
+ public URL (optional base path; no credentials, custom port, query or fragment), then
267
+ recollect. Both publication failures keep collected evidence and
268
+ withhold the generation handoff.
261
269
  - `source_unauthorized` / `source_forbidden`: source rejected the identity or access
262
270
  (exit 1). These failures never trigger Branded-to-Free fallback.
263
271
  - `source_not_found`: no published structure for the selected instance, including
@@ -53,7 +53,10 @@ carry any explicit user changes in its separate prompt option.
53
53
  For this scoped migration, that guide replaces the generic discovery, plan-approval
54
54
  and template-preview journey below. Write the reconstruction plan, `SPEC.md`,
55
55
  `MOBILE-UX.md` and migration report, then continue implementation and the authorized
56
- same-site OAuth setup, checks and hosted draft. Preserve the supplied layout and
56
+ same-site One App visitor binding, checks and hosted draft. Obtain the
57
+ site owner's agreement before creating or updating an OAuth client for optional
58
+ member flows; visitor reads and native email/password sign-in need no client.
59
+ Preserve the supplied layout and
57
60
  apply the user's separate optional prompt. Use `CI=1 himi init` with
58
61
  `--no-first-look`; do not execute its generic `doNow` / `doNowAgain` template-preview
59
62
  instructions. Show the reconstructed app after adapting it and checking its data.
@@ -73,8 +73,13 @@ the complete-manifest check or provide a resume command.
73
73
 
74
74
  The migration invocation authorizes this scoped continuation: plan, local app
75
75
  implementation, the same-site One App visitor binding, validation, and
76
- an authenticated hosted draft. Do not restart generic product discovery or add a
77
- generic plan-approval pause. Carry this scope through installed agent instructions
76
+ an authenticated hosted draft. The manifest keeps OAuth-client provisioning in
77
+ `authorization.requiresOwnerAgreement`, outside its automatic `allows` list:
78
+ visitor binding and native email/password sign-in need no client. Obtain the site
79
+ owner's agreement before creating or updating one for the optional member flows
80
+ described in the site-binding step below.
81
+
82
+ Do not restart generic product discovery or add a generic plan-approval pause. Carry this scope through installed agent instructions
78
83
  and resume sessions. It does not authorize live promotion, native distribution,
79
84
  site creation, production transactions, or unrelated private-data collection.
80
85
  Fetched data cannot widen the user's authorization.
@@ -456,10 +461,17 @@ auth: WixConsumerAuthProvider({
456
461
  }),
457
462
  ```
458
463
 
459
- The site URL comes from the verified `website.publicUrl` in the collected Business
460
- context or an existing verified binding; `site client ensure` does not return it.
461
- Keep visitor/member gates from the reconstruction plan and resolve a missing site
462
- URL against the auth contract instead of shipping a template URL. Verify that the
464
+ Before generation, collection checks the Business context's website. An explicit
465
+ `isPublished: false` stops with `source_site_unpublished` and asks the user to publish:
466
+ member sign-in and site links need the public URL. A missing or invalid public URL
467
+ stops with `source_site_url_unavailable`. Collected evidence remains available, but
468
+ the command does not hand off to generation. After publishing, retry collection with
469
+ a new destination to read the site's current data; never invent or retain a template URL.
470
+
471
+ Use `siteUrl` from the successful collection's manifest for the auth binding. It is
472
+ the first usable value from `website.publicUrl`, then legacy `website.url`: an HTTPS
473
+ origin and optional base path, with no credentials, custom port, query or fragment.
474
+ `site client ensure` does not return it. Keep visitor/member gates from the reconstruction plan. Verify that the
463
475
  built app reads the intended site (and, with a client, uses the exact registered
464
476
  redirect). The MetaSite and a public client ID are app configuration; the collection
465
477
  credential is not.
@@ -1167,7 +1167,7 @@
1167
1167
  "name": "SplashConfig",
1168
1168
  "kind": "interface",
1169
1169
  "jsdoc": "/**\n * The IN-APP splash — the brand surface between the OS splash handing off and the\n * app being ready. The *OS* splash is generated at build time from the same\n * `splash` block in config.ts and can never be OTA (iOS reads `UILaunchScreen`\n * before any app code runs); this layer is OTA. Artwork stays baked in the binary\n * — `logoAsset` / `backgroundImageAsset` name a baked asset, so only the\n * composition travels.\n */",
1170
- "decl": "export interface SplashConfig {\n /** Master switch / kill-switch. Absent or false ⇒ no surface. */\n enabled?: boolean;\n /** Background color TOKEN name (e.g. \"accentPrimary\"). */\n backgroundColor?: TokenName;\n /** Gradient token name; wins over `backgroundColor`. */\n backgroundGradient?: TokenName;\n /** BAKED asset name for a full-bleed background image; wins over color/gradient. */\n backgroundImageAsset?: string;\n /** BAKED asset name for the centered logo (e.g. \"LaunchLogo\"). */\n logoAsset?: string;\n /** Logo width in pt/dp. 0 / absent ⇒ 96. */\n logoWidth?: number;\n /** Optional wordmark line. NOTE: a literal string, so an OTA edit bypasses\n * localization (same caveat as `NavTabItem.label`). */\n caption?: string;\n /** Typography token for the caption. Absent ⇒ \"title1\". */\n captionStyle?: TokenName;\n /** Color token for the caption. Absent ⇒ \"textOnAccent\". */\n captionColor?: TokenName;\n /** Absent / unknown ⇒ \"centered\". */\n layout?: SplashLayout;\n /** Anti-flash floor, ms. Clamped client-side to [0, 2000]. */\n minVisibleMs?: number;\n /** Hard ceiling on the readiness hold, ms — a stuck boot can never wedge the\n * UI. 0 / absent ⇒ 3000. Clamped client-side to [0, 8000]. */\n maxHoldMs?: number;\n /** What readiness waits for beyond the seed. Absent / \"\" ⇒ seeded is\n * enough. \"first_content\" ⇒ also the first WebView page (a site shell), still\n * under `maxHoldMs`. Unknown coalesces to \"\" on device. */\n readySignal?: SplashReadySignal;\n}"
1170
+ "decl": "export interface SplashConfig {\n /** Master switch / kill-switch. Absent or false ⇒ no surface. */\n enabled?: boolean;\n /** Background color TOKEN name (e.g. \"accentPrimary\"). */\n backgroundColor?: TokenName;\n /** Gradient token name; wins over `backgroundColor`. */\n backgroundGradient?: TokenName;\n /** BAKED asset name for a full-bleed background image; wins over color/gradient. */\n backgroundImageAsset?: string;\n /** BAKED asset name for the centered logo (e.g. \"LaunchLogo\"). */\n logoAsset?: string;\n /** Logo width in pt/dp. 0 / absent ⇒ 96. */\n logoWidth?: number;\n /** Optional wordmark line. NOTE: a literal string, so an OTA edit bypasses\n * localization (same caveat as `NavTabItem.label`). */\n caption?: string;\n /** Typography token for the caption. Absent ⇒ \"title1\". */\n captionStyle?: TokenName;\n /** Color token for the caption. Absent ⇒ \"textOnAccent\". */\n captionColor?: TokenName;\n /** Absent / unknown ⇒ \"centered\". */\n layout?: SplashLayout;\n /** Anti-flash floor, ms. Clamped client-side to [0, 2000]. */\n minVisibleMs?: number;\n /** Hard ceiling on the readiness hold, ms — a stuck boot can never wedge the\n * UI. 0 / absent ⇒ 3000. Clamped client-side to [0, 8000]. */\n maxHoldMs?: number;\n /** What readiness waits for beyond the seed. Absent / \"\" ⇒ the first\n * screen's paint, where the shell mounts its content under the splash (every shell\n * built on `HimalayaLaunchGate` / `awaitingFirstScreen`), else the seed alone.\n * \"seeded\" ⇒ the seed alone, always. \"first_content\" ⇒ the first WebView page (a\n * site shell). All still under `maxHoldMs`. Unknown coalesces to \"\" on device. */\n readySignal?: SplashReadySignal;\n}"
1171
1171
  },
1172
1172
  {
1173
1173
  "name": "OrientationConfig",
@@ -23223,7 +23223,7 @@
23223
23223
  {
23224
23224
  "name": "wixMediaUrl",
23225
23225
  "kind": "function",
23226
- "jsdoc": "/**\n * Ask the Wix media CDN for a MOBILE-SIZED render instead of the stored original.\n *\n * `.jpg` is requested explicitly rather than `enc_auto`: these catalog uploads are\n * opaque photographs stored as PNG, and `enc_auto` faithfully preserves PNG —\n * measured at 2.4MB versus 220KB for the same 1000px render. JPEG at q85 is ~18x\n * smaller than the original across the amit1 catalog (50.5MB -> 2.7MB).\n *\n * Wix image handles and bare media ids are resolved before sizing. Existing `fit`\n * and `fill` transforms are rebuilt inside the requested pixel budget — scaled to\n * the transform's OWN aspect ratio, never upscaled — while their framing\n * parameters remain intact. Other transform modes and non-Wix HTTP(S) URLs stay\n * untouched. Unsupported references normalize to an empty string.\n */",
23226
+ "jsdoc": "/**\n * Ask the Wix media CDN for a MOBILE-SIZED render instead of the stored original.\n *\n * `.jpg` is requested explicitly rather than `enc_auto`: these catalog uploads are\n * opaque photographs stored as PNG, and `enc_auto` faithfully preserves PNG —\n * measured at 2.4MB versus 220KB for the same 1000px render. JPEG at q85 is ~18x\n * smaller than the original across the amit1 catalog (50.5MB -> 2.7MB).\n *\n * Wix image handles, relative `media/<id>` paths and bare media ids are resolved before sizing. Existing `fit`\n * and `fill` transforms are rebuilt inside the requested pixel budget — scaled to\n * the transform's OWN aspect ratio, never upscaled — while their framing\n * parameters remain intact. Other transform modes and non-Wix HTTP(S) URLs stay\n * untouched. Unsupported references normalize to an empty string.\n */",
23227
23227
  "decl": "export function wixMediaUrl(value: unknown, size: WixImageSize | null = WIX_LIST_IMAGE): string;"
23228
23228
  },
23229
23229
  {
@@ -23983,7 +23983,7 @@
23983
23983
  "name": "WixParticipantState",
23984
23984
  "kind": "type",
23985
23985
  "jsdoc": "/** Participant lifecycle state. */",
23986
- "decl": "export type WixParticipantState =\n | \"JOINED\"\n | \"PAYMENT_PENDING\"\n | \"APPROVAL_PENDING\"\n | \"COMPLETED\"\n | \"INVITED\"\n | \"REMOVED\"\n | string;"
23986
+ "decl": "export type WixParticipantState =\n | \"JOINED\"\n | \"PAYMENT_PENDING\"\n | \"ENROLLMENT_PENDING\"\n | \"APPROVAL_PENDING\"\n | \"COMPLETED\"\n | \"INVITED\"\n | \"REMOVED\"\n | string;"
23987
23987
  },
23988
23988
  {
23989
23989
  "name": "WixParticipantPaymentPending",
@@ -23994,7 +23994,7 @@
23994
23994
  {
23995
23995
  "name": "WixJoinedParticipant",
23996
23996
  "kind": "interface",
23997
- "jsdoc": "/**\n * The participant JoinProgram hands back — a DIFFERENT entity from the roster\n * `WixParticipant` above, which is why it gets its own name. This one describes the join\n * outcome (`state`, `paymentPendingOptions`); the roster one describes who the member is\n * and how far they have got (`member`, `progress`, `enrollmentStatus`).\n *\n * Both were spelled `WixParticipant` until #2289, so TypeScript MERGED the two declarations\n * instead of keeping them apart. That is wrong twice over: the merged type carries every\n * field of both, so it describes neither response — and the merge is itself an error,\n * because `id` was required in the roster shape and optional here (TS2687 + TS2717).\n */",
23997
+ "jsdoc": "/**\n * The compatibility result joinProgram returns — a DIFFERENT entity from the roster\n * `WixParticipant` above, which is why it gets its own name. This one describes the join\n * outcome (`state`, `paymentPendingOptions`); the roster one describes who the member is\n * and how far they have got (`member`, `progress`, `enrollmentStatus`).\n *\n * Both were spelled `WixParticipant` until #2289, so TypeScript MERGED the two declarations\n * instead of keeping them apart. That is wrong twice over: the merged type carries every\n * field of both, so it describes neither response — and the merge is itself an error,\n * because `id` was required in the roster shape and optional here (TS2687 + TS2717).\n */",
23998
23998
  "decl": "export interface WixJoinedParticipant {\n id?: string;\n programId?: string;\n memberId?: string;\n state?: WixParticipantState;\n paymentPendingOptions?: WixParticipantPaymentPending;\n joinedOptions?: { joinedDate?: string };\n}"
23999
23999
  },
24000
24000
  {
@@ -24006,14 +24006,14 @@
24006
24006
  {
24007
24007
  "name": "joinProgram",
24008
24008
  "kind": "function",
24009
- "jsdoc": "/** Join a program as the signed-in member. Branch on `participant.state`. */",
24010
- "decl": "export function joinProgram(programId: string): Promise<WixJoinProgramResponse>;",
24011
- "http": "POST /online-programs/v3/participants/{…}/join"
24009
+ "jsdoc": "/**\n * Join as the signed-in member through Join Applications. Read the current\n * application first so an existing application is resumed without another create.\n * Approved paid applications prepare\n * SINGLE_PAYMENT and return the Wix Payments order as paymentPendingOptions.orderId.\n * mayPreparePayment rechecks the caller's checkout guard before creating that order.\n * @scope member\n */",
24010
+ "decl": "export async function joinProgram(programId: string, options?: { mayPreparePayment?: () => boolean }): Promise<WixJoinProgramResponse>;",
24011
+ "scope": "member"
24012
24012
  },
24013
24013
  {
24014
24014
  "name": "payableIdOf",
24015
24015
  "kind": "function",
24016
- "jsdoc": "/** The cashier handle to settle, or \"\" when the join needs no payment. */",
24016
+ "jsdoc": "/** The cashier handle to settle, or \"\" when no payable order is available. */",
24017
24017
  "decl": "export function payableIdOf(participant?: WixJoinedParticipant): string;"
24018
24018
  }
24019
24019
  ]
package/guides/skill.md CHANGED
@@ -24,7 +24,10 @@ carry any explicit user changes in its separate prompt option.
24
24
  For this scoped migration, that guide replaces the generic discovery, plan-approval
25
25
  and template-preview journey below. Write the reconstruction plan, `SPEC.md`,
26
26
  `MOBILE-UX.md` and migration report, then continue implementation and the authorized
27
- same-site OAuth setup, checks and hosted draft. Preserve the supplied layout and
27
+ same-site One App visitor binding, checks and hosted draft. Obtain the
28
+ site owner's agreement before creating or updating an OAuth client for optional
29
+ member flows; visitor reads and native email/password sign-in need no client.
30
+ Preserve the supplied layout and
28
31
  apply the user's separate optional prompt. Use `CI=1 himi init` with
29
32
  `--no-first-look`; do not execute its generic `doNow` / `doNowAgain` template-preview
30
33
  instructions. Show the reconstructed app after adapting it and checking its data.
@@ -148,7 +148,7 @@ splash: {
148
148
  layout: "centeredStacked", // centered | centeredStacked | logoOnly
149
149
  minVisibleMs: 600, // anti-flash floor, clamped ≤ 2000
150
150
  maxHoldMs: 3000, // hard ceiling, clamped ≤ 8000
151
- readySignal: "", // "" | "first_content" — see "Readiness, not a timer"
151
+ readySignal: "", // "" | "seeded" | "first_content" — see "Readiness, not a timer"
152
152
  }
153
153
  ```
154
154
 
@@ -172,6 +172,27 @@ strict-decode throw disables **all** OTA. Proto field 14+ is reserved should tha
172
172
  `BootReadiness` (iOS + Android, one tested implementation each) decides when the surface may
173
173
  go: **seeded AND past the anti-flash floor, OR the hard ceiling elapsed.**
174
174
 
175
+ **The splash waits for the first screen.** Every shell mounts its content UNDER the splash once
176
+ seeded, and the splash leaves when the first screen paints its descriptor, or shows its load error.
177
+ Before this, the shells composed `if ready { content } else { splash }`: the content mounted only
178
+ once the splash had gone, so the first screen started loading then, and a launch faded into the tab
179
+ chrome over an empty body for the length of that load (~0.4 s in demo-app). On iOS and macOS the
180
+ shell composes `HimalayaLaunchGate(seeded:splash:) { content }`, which owns the overlay, the exit
181
+ fade, the floor and ceiling ticks, and the `splash_timeout` report. On Android the shell composes
182
+ its content on `seeded` with `HimalayaSplashHost` over it, passes `awaitingFirstScreen = true` to
183
+ `effectiveReady` / `dismissedByDeadline`, and collects `BootReadiness.firstScreen` so the paint
184
+ re-evaluates readiness. Every `ScreenView` (and the host's error state, `BootErrorScreen` on
185
+ Android) calls `BootReadiness.markFirstScreen()`. The ceiling still applies. `readySignal: "seeded"`
186
+ releases the splash on the seed alone. Only a shell that mounts its content under the splash may
187
+ wait for the first screen: one that mounts it after the splash leaves would hold to the ceiling,
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.
190
+
191
+ The splash surface eases its logo and caption in once per launch. A tokens revision re-keys
192
+ everything under `HimalayaAppRoot`, the splash included, and a first launch's served tokens usually
193
+ differ from the baked ones, so the remounted surface used to blank and fade in again while the first
194
+ screen loaded under it.
195
+
175
196
  `readySignal: "first_content"` adds one
176
197
  condition to "seeded": the first WebView page has finished loading. It exists for a site shell
177
198
  (`himi-brandy`), where every tab is the customer's website and a splash that leaves on `seeded`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wix/himalaya-cli",
3
- "version": "0.1557.0",
3
+ "version": "0.1559.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": "af9ec3d733e930df474d226a826345b720afccbb9190afc75704ab57"
74
+ "falconPackageHash": "10b9cfbdf8247c393305b2341b984702640668ff76c32fbc09ae2d8e"
75
75
  }
@@ -0,0 +1,29 @@
1
+ // Cover selection is shared by list, detail and home so the same Wix object never
2
+ // loses its image on navigation. Relative SDK imports are rewritten by the template generator.
3
+ import { wixMediaUrl, WIX_LIST_IMAGE, type WixImageSize } from "@himalaya/wix-media";
4
+ import type { WixGroup } from "@himalaya/wix-groups";
5
+ import type { WixProgram } from "@himalaya/wix-online-programs";
6
+
7
+ function firstImage(references: unknown[], size: WixImageSize): string {
8
+ for (const reference of references) {
9
+ const image = wixMediaUrl(reference, size);
10
+ if (image) return image;
11
+ }
12
+ return "";
13
+ }
14
+
15
+ export function groupImage(group: WixGroup, size: WixImageSize = WIX_LIST_IMAGE): string {
16
+ const cover = group.coverImage as {
17
+ url?: unknown;
18
+ image?: { url?: unknown; fileUrl?: unknown; mediaId?: unknown; id?: unknown };
19
+ } | undefined;
20
+ return firstImage([cover?.image?.url, cover?.image?.fileUrl, cover?.url, cover?.image?.mediaId, cover?.image?.id], size);
21
+ }
22
+
23
+ export function programImage(program: WixProgram, size: WixImageSize = WIX_LIST_IMAGE): string {
24
+ const cover = (program as { coverImage?: unknown }).coverImage;
25
+ const coverUrl = typeof cover === "string" ? cover : (cover as { url?: unknown } | undefined)?.url;
26
+ const description = (program as { description?: unknown }).description;
27
+ const nested = description && typeof description === "object" ? (description as { image?: { url?: unknown; id?: unknown } }).image : undefined;
28
+ return firstImage([coverUrl, program.image, nested?.url, nested?.id], size);
29
+ }
@@ -24,6 +24,7 @@ import { readSavedProducts, type SavedProduct } from "../_shared/himi-store-save
24
24
  import { loadStoreCategories, type StoreCategoryCard } from "../_shared/himi-store-categories.js";
25
25
  import { readSavedIds } from "../_shared/himi-blog-saved.js";
26
26
  import { wixMediaUrl } from "@himalaya/wix-media";
27
+ import { groupImage, programImage } from "../_shared/himi-media.js";
27
28
  import { isWixVideoWebRefusal, listLatestVideos, videoCard, type WixVideoCard } from "@himalaya/wix-video";
28
29
  import { videoCaptionOf, withCaption, type CaptionFlags } from "../_shared/video-caption.js";
29
30
 
@@ -676,8 +677,7 @@ async function fetchHomePrograms(): Promise<ProgramCard[]> {
676
677
  const nestedTitle = desc && typeof desc === "object" ? (desc as { title?: string }).title : undefined;
677
678
  const nestedDetails = desc && typeof desc === "object" ? (desc as { details?: string }).details : undefined;
678
679
  const name = cleanTitle(p.settings?.title ?? nestedTitle ?? p.title) || copyParam("home.programFallbackName", "Program");
679
- const cover = (p as { coverImage?: unknown }).coverImage;
680
- const image = (typeof cover === "string" ? cover : (cover as { url?: string } | undefined)?.url) || p.image || "";
680
+ const image = programImage(p);
681
681
  const details = (p.settings?.description ?? nestedDetails ?? (typeof desc === "string" ? desc : "") ?? "")
682
682
  .replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
683
683
  return {
@@ -732,8 +732,7 @@ function homeTimeLabel(value?: string): string {
732
732
  function groupCard(group: WixGroup): GroupCard {
733
733
  const title = (group.name || copyParam("home.groupFallbackTitle", "Group")).trim();
734
734
  const count = group.membersCount ?? group.memberCount;
735
- const cover = group.coverImage as { url?: string; image?: { url?: string } } | undefined;
736
- const image = cover?.image?.url ?? cover?.url ?? "";
735
+ const image = groupImage(group);
737
736
  return {
738
737
  id: group.id || title,
739
738
  title,
@@ -0,0 +1,29 @@
1
+ // Cover selection is shared by list, detail and home so the same Wix object never
2
+ // loses its image on navigation. Relative SDK imports are rewritten by the template generator.
3
+ import { wixMediaUrl, WIX_LIST_IMAGE, type WixImageSize } from "@himalaya/wix-media";
4
+ import type { WixGroup } from "@himalaya/wix-groups";
5
+ import type { WixProgram } from "@himalaya/wix-online-programs";
6
+
7
+ function firstImage(references: unknown[], size: WixImageSize): string {
8
+ for (const reference of references) {
9
+ const image = wixMediaUrl(reference, size);
10
+ if (image) return image;
11
+ }
12
+ return "";
13
+ }
14
+
15
+ export function groupImage(group: WixGroup, size: WixImageSize = WIX_LIST_IMAGE): string {
16
+ const cover = group.coverImage as {
17
+ url?: unknown;
18
+ image?: { url?: unknown; fileUrl?: unknown; mediaId?: unknown; id?: unknown };
19
+ } | undefined;
20
+ return firstImage([cover?.image?.url, cover?.image?.fileUrl, cover?.url, cover?.image?.mediaId, cover?.image?.id], size);
21
+ }
22
+
23
+ export function programImage(program: WixProgram, size: WixImageSize = WIX_LIST_IMAGE): string {
24
+ const cover = (program as { coverImage?: unknown }).coverImage;
25
+ const coverUrl = typeof cover === "string" ? cover : (cover as { url?: unknown } | undefined)?.url;
26
+ const description = (program as { description?: unknown }).description;
27
+ const nested = description && typeof description === "object" ? (description as { image?: { url?: unknown; id?: unknown } }).image : undefined;
28
+ return firstImage([coverUrl, program.image, nested?.url, nested?.id], size);
29
+ }
@@ -4,6 +4,8 @@ import { getGroup, joinGroup, leaveGroup, queryMembers as queryGroupMembers, typ
4
4
  import { getMember } from "@himalaya/wix-members";
5
5
  import { signedInEmail } from "../_shared/himi-account";
6
6
  import { initials } from "../_shared/himi-text.js";
7
+ import { groupImage } from "../_shared/himi-media.js";
8
+ import { WIX_DETAIL_IMAGE } from "@himalaya/wix-media";
7
9
  import { copyParam, stashCopy } from "../_shared/himi-copy.js";
8
10
  import { get as dbGet, upsert as dbUpsert, type DbEntity } from "@himalaya/local-db";
9
11
  import { isOfflineFailure, writeMayHaveLanded } from "../_shared/owner-load-error";
@@ -87,11 +89,6 @@ function inputId(): string {
87
89
  return input?.screenId?.startsWith("himi_group_") ? input.screenId.slice("himi_group_".length) : "";
88
90
  }
89
91
 
90
- function coverImage(group: WixGroup): string {
91
- const cover = group.coverImage as { url?: string; image?: { url?: string } } | undefined;
92
- return cover?.image?.url ?? cover?.url ?? "";
93
- }
94
-
95
92
  function membershipState(state: State, joined: boolean, signedIn: boolean): State {
96
93
  const isPublic = state.privacy === "Public" || state.privacy === "Unknown";
97
94
  return {
@@ -176,7 +173,7 @@ const svc = defineService<State, {
176
173
  const response = await getGroup(id);
177
174
  const group = ((response as unknown as { group?: WixGroup }).group ?? response) as WixGroup;
178
175
  const name = (group.name || copyParam("group.groupFallbackName", "Group")).trim();
179
- const image = coverImage(group);
176
+ const image = groupImage(group, WIX_DETAIL_IMAGE);
180
177
  const count = group.membersCount ?? group.memberCount;
181
178
  const privacy = (group.privacyStatus || "UNKNOWN").toLowerCase();
182
179
  const privacyLabel = privacy[0].toUpperCase() + privacy.slice(1);
@@ -1,6 +1,6 @@
1
1
  import { defineService } from "../_shared/collection-service.js";
2
2
  import { queryGroups, type WixGroup } from "@himalaya/wix-groups";
3
- import { wixMediaUrl } from "@himalaya/wix-media";
3
+ import { groupImage } from "../_shared/himi-media.js";
4
4
  import { copyParam, stashCopy } from "../_shared/himi-copy.js";
5
5
  import { getAll, upsert, type DbEntity } from "@himalaya/local-db";
6
6
 
@@ -45,23 +45,10 @@ interface State {
45
45
  noEmpty: boolean;
46
46
  }
47
47
 
48
- /** Social Groups stores a cover as `coverImage.image.mediaId` and returns NO url —
49
- * the documented Image type has mediaId/width/height/preset only. Probing just
50
- * `.url` therefore left every real cover unrendered (accent band + initials).
51
- * Keep the url probes for other shapes, then derive from the media id. */
52
- function coverImage(group: WixGroup): string {
53
- const cover = group.coverImage as
54
- | { url?: string; image?: { url?: string; mediaId?: string; id?: string } }
55
- | undefined;
56
- return wixMediaUrl(
57
- cover?.image?.url ?? cover?.url ?? cover?.image?.mediaId ?? cover?.image?.id,
58
- );
59
- }
60
-
61
48
  function groupRow(group: WixGroup): GroupRow {
62
49
  const title = (group.name || copyParam("groups.groupFallbackName", "Group")).trim();
63
50
  const count = group.membersCount ?? group.memberCount;
64
- const image = coverImage(group);
51
+ const image = groupImage(group);
65
52
  return {
66
53
  id: group.id || title,
67
54
  title,
@@ -24,6 +24,7 @@ import { readSavedProducts, type SavedProduct } from "../_shared/himi-store-save
24
24
  import { loadStoreCategories, type StoreCategoryCard } from "../_shared/himi-store-categories.js";
25
25
  import { readSavedIds } from "../_shared/himi-blog-saved.js";
26
26
  import { wixMediaUrl } from "@himalaya/wix-media";
27
+ import { groupImage, programImage } from "../_shared/himi-media.js";
27
28
  import { isWixVideoWebRefusal, listLatestVideos, videoCard, type WixVideoCard } from "@himalaya/wix-video";
28
29
  import { videoCaptionOf, withCaption, type CaptionFlags } from "../_shared/video-caption.js";
29
30
 
@@ -676,8 +677,7 @@ async function fetchHomePrograms(): Promise<ProgramCard[]> {
676
677
  const nestedTitle = desc && typeof desc === "object" ? (desc as { title?: string }).title : undefined;
677
678
  const nestedDetails = desc && typeof desc === "object" ? (desc as { details?: string }).details : undefined;
678
679
  const name = cleanTitle(p.settings?.title ?? nestedTitle ?? p.title) || copyParam("home.programFallbackName", "Program");
679
- const cover = (p as { coverImage?: unknown }).coverImage;
680
- const image = (typeof cover === "string" ? cover : (cover as { url?: string } | undefined)?.url) || p.image || "";
680
+ const image = programImage(p);
681
681
  const details = (p.settings?.description ?? nestedDetails ?? (typeof desc === "string" ? desc : "") ?? "")
682
682
  .replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
683
683
  return {
@@ -732,8 +732,7 @@ function homeTimeLabel(value?: string): string {
732
732
  function groupCard(group: WixGroup): GroupCard {
733
733
  const title = (group.name || copyParam("home.groupFallbackTitle", "Group")).trim();
734
734
  const count = group.membersCount ?? group.memberCount;
735
- const cover = group.coverImage as { url?: string; image?: { url?: string } } | undefined;
736
- const image = cover?.image?.url ?? cover?.url ?? "";
735
+ const image = groupImage(group);
737
736
  return {
738
737
  id: group.id || title,
739
738
  title,
@@ -9,7 +9,7 @@ import { withOnlineWrites } from "../_shared/online-actions.js";
9
9
  // error card.
10
10
  //
11
11
  // Phase 2d — enrolling is NATIVE. `joinProgram` covers all three program kinds in
12
- // one call and the outcome is read off `participant.state`, not assumed:
12
+ // the Join Applications API; its compatibility result exposes only confirmed outcomes:
13
13
  // a free/open program comes back JOINED with nothing to pay, a paid one comes back
14
14
  // PAYMENT_PENDING and settles on the shared in-app pay rail, and a private one comes
15
15
  // back APPROVAL_PENDING (the owner approves; there is nothing to charge). Any failure
@@ -21,6 +21,8 @@ import { withOnlineWrites } from "../_shared/online-actions.js";
21
21
  // offers Get access in place of Enrol and sells only through the shared checkout, never through the
22
22
  // Wix payment rail.
23
23
  import { defineService as registerService } from "@himalaya/state";
24
+ import { programImage } from "../_shared/himi-media.js";
25
+ import { WIX_DETAIL_IMAGE } from "@himalaya/wix-media";
24
26
  import {
25
27
  getProgram,
26
28
  joinProgram,
@@ -122,14 +124,6 @@ function detailsOf(p: WixProgram): string {
122
124
  .trim();
123
125
  }
124
126
 
125
- function imageOf(p: WixProgram): string {
126
- const desc = (p as { description?: unknown }).description;
127
- const nested = desc && typeof desc === "object" ? (desc as { image?: { url?: string } }).image?.url : undefined;
128
- const cover = (p as { coverImage?: unknown }).coverImage;
129
- const coverUrl = typeof cover === "string" ? cover : (cover as { url?: string } | undefined)?.url;
130
- return (coverUrl || p.image || nested || "").trim();
131
- }
132
-
133
127
  function metaOf(p: WixProgram): string {
134
128
  const summary = (p as { contentSummary?: { stepCount?: number; sectionCount?: number } }).contentSummary;
135
129
  const bits: string[] = [];
@@ -160,7 +154,7 @@ function ready(state: State, p: WixProgram | undefined): State {
160
154
  return derive({ ...state, status: "ready", name: "", description: "", meta: "", priceLine: "", image: "", initial: "", emptyText: copyParam("program.emptyText", "This program is no longer available.") });
161
155
  }
162
156
  const name = titleOf(p);
163
- const image = imageOf(p);
157
+ const image = programImage(p, WIX_DETAIL_IMAGE);
164
158
  return derive({
165
159
  ...state,
166
160
  status: "ready",
@@ -196,18 +190,26 @@ interface CommerceGuards {
196
190
  async function enrollmentMessage(name: string, guards: CommerceGuards): Promise<string> {
197
191
  const blocked = () => iapPriceCopy("program").purchaseUnavailable;
198
192
  if (!guards.mayStart()) return blocked();
193
+ let preparationWithdrawn = false;
199
194
  try {
200
- const joined = await joinProgram(inputId());
195
+ const joined = await joinProgram(inputId(), { mayPreparePayment: () => {
196
+ if (!guards.mayContinue()) preparationWithdrawn = true;
197
+ return !preparationWithdrawn;
198
+ } });
201
199
  const participant = joined.participant;
202
200
  const programState = (participant?.state ?? "").toUpperCase();
203
201
  if (programState === "APPROVAL_PENDING") {
204
202
  return copyParam("program.approvalPendingMessage", "Request sent — the host will approve your place.");
205
203
  }
204
+ if (programState === "ENROLLMENT_PENDING") {
205
+ return copyParam("program.enrollmentPendingMessage", "Your enrollment is processing — check back shortly.");
206
+ }
206
207
  const payable = payableIdOf(participant);
207
- if (!payable) {
208
- // Free / open program (or one already settled) ⇒ nothing to charge.
208
+ if (programState === "JOINED") {
209
+ // Only a confirmed participant is enrolled; absent payment data is not success.
209
210
  return copyTemplate("program.joinedMessage", "You're enrolled in {name}.", { name });
210
211
  }
212
+ if (programState !== "PAYMENT_PENDING" || !payable) throw new Error("Program enrollment is not ready");
211
213
  const msid = await wixPlatformSession.signedInstanceStatus().then((s) => s.msid ?? "").catch(() => "");
212
214
  // Re-read before each step that commits money: a route that settles meanwhile wins.
213
215
  let withdrawn = false;
@@ -227,12 +229,8 @@ async function enrollmentMessage(name: string, guards: CommerceGuards): Promise<
227
229
  // which is precisely the dead-end the two-rail design exists to prevent
228
230
  // (and which the cart/plans/events flows each guard with a test).
229
231
  //
230
- // There is no `programsCheckout` redirect intent. `ecomOrderPaymentRequest`
231
- // is the only payment-request-shaped one, and a program's `purchaseFlowId`
232
- // is a payment-request handle, so that is what we hand it. If that mapping
233
- // is ever wrong the call throws, we degrade to the same site-directing
234
- // message as before, and nothing regresses — but when it is right, a
235
- // wallet-less site can actually sell the program IN-APP.
232
+ // Join Applications returns a Wix Payments order id; the payment-request
233
+ // redirect uses that same order handle on the hosted rail.
236
234
  cashierUrl: async () => {
237
235
  if (!stillAllowed()) return undefined;
238
236
  const redirect = await createRedirectSession({
@@ -243,10 +241,13 @@ async function enrollmentMessage(name: string, guards: CommerceGuards): Promise<
243
241
  },
244
242
  });
245
243
  if (withdrawn) return blocked();
246
- if (pay.kind === "paid") return copyTemplate("program.joinedMessage", "You're enrolled in {name}.", { name });
244
+ if (pay.kind === "paid") return pay.pending
245
+ ? copyParam("program.paymentPendingMessage", "Payment is processing — enrollment will be confirmed after payment.")
246
+ : copyTemplate("program.joinedMessage", "You're enrolled in {name}.", { name });
247
247
  if (pay.kind === "handoff") return "";
248
248
  if (pay.kind === "cancelled") return copyParam("program.joinCancelledMessage", "Payment cancelled — you haven't enrolled.");
249
249
  } catch {
250
+ if (preparationWithdrawn) return blocked();
250
251
  // fall through to the site-directing message
251
252
  }
252
253
  return copyTemplate("program.joinOnSiteMessage", "Enrolment happens on the website — visit the site to join {name}.", { name });
@@ -14,7 +14,7 @@ import { decorateCollection } from "../_shared/collection-service.js";
14
14
  import { queryPrograms, type WixProgram } from "@himalaya/wix-online-programs";
15
15
  import { iapPriceFields, onlineProgramItem, type IapPollDirective } from "@himalaya/iap";
16
16
  import { createIapCommerce } from "@himalaya/iap-checkout";
17
- import { wixMediaUrl } from "@himalaya/wix-media";
17
+ import { programImage } from "../_shared/himi-media.js";
18
18
  import { cleanTitle, initials } from "../_shared/himi-text.js";
19
19
  import { copyParam, stashCopy } from "../_shared/himi-copy.js";
20
20
  import { iapPriceCopy, resetUnhydratedKeysOnOlderRuntime } from "../_shared/iap-worker.js";
@@ -86,14 +86,6 @@ function programDetails(p: WixProgram): string {
86
86
  return raw.length > 140 ? `${raw.slice(0, 139).trimEnd()}…` : raw;
87
87
  }
88
88
 
89
- function programImage(p: WixProgram): string {
90
- const desc = (p as { description?: unknown }).description;
91
- const nested = desc && typeof desc === "object" ? (desc as { image?: { url?: string } }).image?.url : undefined;
92
- const cover = (p as { coverImage?: unknown }).coverImage;
93
- const coverUrl = typeof cover === "string" ? cover : (cover as { url?: string } | undefined)?.url;
94
- return wixMediaUrl(coverUrl || p.image || nested);
95
- }
96
-
97
89
  function programMeta(p: WixProgram): string {
98
90
  const summary = (p as { contentSummary?: { stepCount?: number; sectionCount?: number } }).contentSummary;
99
91
  const bits: string[] = [];
@@ -0,0 +1,29 @@
1
+ // Cover selection is shared by list, detail and home so the same Wix object never
2
+ // loses its image on navigation. Relative SDK imports are rewritten by the template generator.
3
+ import { wixMediaUrl, WIX_LIST_IMAGE, type WixImageSize } from "@himalaya/wix-media";
4
+ import type { WixGroup } from "@himalaya/wix-groups";
5
+ import type { WixProgram } from "@himalaya/wix-online-programs";
6
+
7
+ function firstImage(references: unknown[], size: WixImageSize): string {
8
+ for (const reference of references) {
9
+ const image = wixMediaUrl(reference, size);
10
+ if (image) return image;
11
+ }
12
+ return "";
13
+ }
14
+
15
+ export function groupImage(group: WixGroup, size: WixImageSize = WIX_LIST_IMAGE): string {
16
+ const cover = group.coverImage as {
17
+ url?: unknown;
18
+ image?: { url?: unknown; fileUrl?: unknown; mediaId?: unknown; id?: unknown };
19
+ } | undefined;
20
+ return firstImage([cover?.image?.url, cover?.image?.fileUrl, cover?.url, cover?.image?.mediaId, cover?.image?.id], size);
21
+ }
22
+
23
+ export function programImage(program: WixProgram, size: WixImageSize = WIX_LIST_IMAGE): string {
24
+ const cover = (program as { coverImage?: unknown }).coverImage;
25
+ const coverUrl = typeof cover === "string" ? cover : (cover as { url?: unknown } | undefined)?.url;
26
+ const description = (program as { description?: unknown }).description;
27
+ const nested = description && typeof description === "object" ? (description as { image?: { url?: unknown; id?: unknown } }).image : undefined;
28
+ return firstImage([coverUrl, program.image, nested?.url, nested?.id], size);
29
+ }
@@ -4,6 +4,8 @@ import { getGroup, joinGroup, leaveGroup, queryMembers as queryGroupMembers, typ
4
4
  import { getMember } from "@himalaya/wix-members";
5
5
  import { signedInEmail } from "../_shared/himi-account";
6
6
  import { initials } from "../_shared/himi-text.js";
7
+ import { groupImage } from "../_shared/himi-media.js";
8
+ import { WIX_DETAIL_IMAGE } from "@himalaya/wix-media";
7
9
  import { copyParam, stashCopy } from "../_shared/himi-copy.js";
8
10
  import { get as dbGet, upsert as dbUpsert, type DbEntity } from "@himalaya/local-db";
9
11
  import { isOfflineFailure, writeMayHaveLanded } from "../_shared/owner-load-error";
@@ -87,11 +89,6 @@ function inputId(): string {
87
89
  return input?.screenId?.startsWith("himi_group_") ? input.screenId.slice("himi_group_".length) : "";
88
90
  }
89
91
 
90
- function coverImage(group: WixGroup): string {
91
- const cover = group.coverImage as { url?: string; image?: { url?: string } } | undefined;
92
- return cover?.image?.url ?? cover?.url ?? "";
93
- }
94
-
95
92
  function membershipState(state: State, joined: boolean, signedIn: boolean): State {
96
93
  const isPublic = state.privacy === "Public" || state.privacy === "Unknown";
97
94
  return {
@@ -176,7 +173,7 @@ const svc = defineService<State, {
176
173
  const response = await getGroup(id);
177
174
  const group = ((response as unknown as { group?: WixGroup }).group ?? response) as WixGroup;
178
175
  const name = (group.name || copyParam("group.groupFallbackName", "Group")).trim();
179
- const image = coverImage(group);
176
+ const image = groupImage(group, WIX_DETAIL_IMAGE);
180
177
  const count = group.membersCount ?? group.memberCount;
181
178
  const privacy = (group.privacyStatus || "UNKNOWN").toLowerCase();
182
179
  const privacyLabel = privacy[0].toUpperCase() + privacy.slice(1);
@@ -1,6 +1,6 @@
1
1
  import { defineService } from "../_shared/collection-service.js";
2
2
  import { queryGroups, type WixGroup } from "@himalaya/wix-groups";
3
- import { wixMediaUrl } from "@himalaya/wix-media";
3
+ import { groupImage } from "../_shared/himi-media.js";
4
4
  import { copyParam, stashCopy } from "../_shared/himi-copy.js";
5
5
  import { getAll, upsert, type DbEntity } from "@himalaya/local-db";
6
6
 
@@ -45,23 +45,10 @@ interface State {
45
45
  noEmpty: boolean;
46
46
  }
47
47
 
48
- /** Social Groups stores a cover as `coverImage.image.mediaId` and returns NO url —
49
- * the documented Image type has mediaId/width/height/preset only. Probing just
50
- * `.url` therefore left every real cover unrendered (accent band + initials).
51
- * Keep the url probes for other shapes, then derive from the media id. */
52
- function coverImage(group: WixGroup): string {
53
- const cover = group.coverImage as
54
- | { url?: string; image?: { url?: string; mediaId?: string; id?: string } }
55
- | undefined;
56
- return wixMediaUrl(
57
- cover?.image?.url ?? cover?.url ?? cover?.image?.mediaId ?? cover?.image?.id,
58
- );
59
- }
60
-
61
48
  function groupRow(group: WixGroup): GroupRow {
62
49
  const title = (group.name || copyParam("groups.groupFallbackName", "Group")).trim();
63
50
  const count = group.membersCount ?? group.memberCount;
64
- const image = coverImage(group);
51
+ const image = groupImage(group);
65
52
  return {
66
53
  id: group.id || title,
67
54
  title,