@wix/himalaya-cli 0.903.0 → 0.905.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 (27) hide show
  1. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/events/src/index.d.ts +39 -21
  2. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-events.mjs +1 -9
  3. package/dist/cli.mjs +74 -20
  4. package/guides/sdk-reference.json +5 -5
  5. package/package.json +2 -2
  6. package/templates/beauty-salon/dev/index.ts +6 -3
  7. package/templates/beauty-salon/tier5-src/himi-branded-lite-home/index.ts +45 -3
  8. package/templates/branded-lite/dev/index.ts +8 -4
  9. package/templates/branded-lite/tier5-src/himi-branded-lite-home/index.ts +45 -3
  10. package/templates/community-club/dev/index.ts +4 -2
  11. package/templates/community-club/tier5-src/himi-branded-lite-home/index.ts +45 -3
  12. package/templates/events-meetups/dev/index.ts +4 -2
  13. package/templates/events-meetups/tier5-src/himi-branded-lite-home/index.ts +45 -3
  14. package/templates/gym-fitness/dev/index.ts +8 -4
  15. package/templates/gym-fitness/tier5-src/himi-branded-lite-home/index.ts +45 -3
  16. package/templates/nonprofit-cause/dev/index.ts +4 -2
  17. package/templates/nonprofit-cause/tier5-src/himi-branded-lite-home/index.ts +45 -3
  18. package/templates/podcast-creator/dev/index.ts +4 -2
  19. package/templates/podcast-creator/tier5-src/himi-branded-lite-home/index.ts +45 -3
  20. package/templates/portfolio-creative/dev/index.ts +6 -3
  21. package/templates/portfolio-creative/tier5-src/himi-branded-lite-home/index.ts +45 -3
  22. package/templates/restaurant/dev/index.ts +4 -2
  23. package/templates/restaurant/tier5-src/himi-branded-lite-home/index.ts +45 -3
  24. package/templates/retail-store/dev/index.ts +4 -2
  25. package/templates/retail-store/tier5-src/himi-branded-lite-home/index.ts +45 -3
  26. package/templates/yoga-studio/dev/index.ts +6 -3
  27. package/templates/yoga-studio/tier5-src/himi-branded-lite-home/index.ts +45 -3
@@ -20,6 +20,39 @@ import { readSavedProducts, type SavedProduct } from "../_shared/himi-store-save
20
20
  import { readSavedIds } from "../_shared/himi-blog-saved.js";
21
21
  import { wixMediaUrl } from "@himalaya/wix-media";
22
22
 
23
+ /** Latched once the member feed is refused because of WHERE the app is running rather
24
+ * than because of the request — see the catch in `loadExtras`. Module scope, so it
25
+ * survives every refresh of this worker and is reset only by a fresh boot. */
26
+ let feedSurfaceRefused = false;
27
+
28
+ /** True for the one refusal that can never change while the app keeps running: the web
29
+ * bridge reporting that this host is not reachable from a browser at all. Deliberately
30
+ * narrower than `isBridgeUnavailable` — a 502/503 is an outage worth retrying, and only
31
+ * this typed refusal is permanent. */
32
+ function isFeedSurfaceRefusal(error: unknown): boolean {
33
+ return /webwix_owner_only/.test(String((error as Error)?.message ?? error));
34
+ }
35
+
36
+ /** In-flight feed reads, keyed by the container they are for.
37
+ *
38
+ * The latch alone still let ONE duplicate through: two Home refreshes that overlap both read
39
+ * `feedSurfaceRefused === false` before the first rejection reaches the catch. Sharing the
40
+ * request closes that window — the second refresh awaits the first instead of sending its own,
41
+ * and both see the same rejection, so the latch is set exactly once.
42
+ *
43
+ * Keyed by container, not a single slot: `active` is the most recently active group, recomputed
44
+ * per refresh from a live `queryGroups`, so two overlapping refreshes can legitimately want
45
+ * DIFFERENT containers. A single shared promise would hand the second one the first one's feed. */
46
+ const feedInFlight = new Map<string, Promise<Awaited<ReturnType<typeof listFeedItems>>>>();
47
+
48
+ function feedFor(containerId: string, limit: number): Promise<Awaited<ReturnType<typeof listFeedItems>>> {
49
+ const pending = feedInFlight.get(containerId);
50
+ if (pending) return pending;
51
+ const started = listFeedItems(containerId, limit).finally(() => { feedInFlight.delete(containerId); });
52
+ feedInFlight.set(containerId, started);
53
+ return started;
54
+ }
55
+
23
56
  interface ProductFeature {
24
57
  id: string;
25
58
  name: string;
@@ -714,10 +747,10 @@ async function fetchExtrasSignal(arg: unknown): Promise<ExtrasSignal> {
714
747
  const response = await queryGroups({ paging: { limit: 12 } });
715
748
  const groups = (response.groups ?? []).filter((group) => group.id);
716
749
  if (verticals.includes("groups")) extras.groupCards = groups.slice(0, 6).map(groupCard);
717
- if (verticals.includes("feed") && groups.length) {
750
+ if (verticals.includes("feed") && groups.length && !feedSurfaceRefused) {
718
751
  const active = groups.slice().sort((a, b) =>
719
752
  new Date(b.lastActivityDate ?? 0).getTime() - new Date(a.lastActivityDate ?? 0).getTime())[0];
720
- const feed = await listFeedItems(active.id, 8);
753
+ const feed = await feedFor(active.id, 8);
721
754
  const profiles = feed.profiles ?? [];
722
755
  const items = (feed.feedItems ?? []).filter((item) => item.feedItemId);
723
756
  extras.feedHighlights = items.slice(0, 5).map((item) => {
@@ -742,7 +775,15 @@ async function fetchExtrasSignal(arg: unknown): Promise<ExtrasSignal> {
742
775
  }))
743
776
  .slice(0, 12);
744
777
  }
745
- } catch { /* collapse silently */ }
778
+ } catch (error) {
779
+ // This particular refusal is PERMANENT, not a blip: in a browser the member feed's
780
+ // host cannot be reached at all, so asking again can only fail again. Retrying on
781
+ // every Home visit spent one doomed round trip each time and filled the error log
782
+ // with something nobody can act on. Latch it once and stop asking; on a phone,
783
+ // where the call does work, this never trips.
784
+ if (isFeedSurfaceRefusal(error)) feedSurfaceRefused = true;
785
+ /* otherwise collapse silently, as before */
786
+ }
746
787
  })());
747
788
  }
748
789
  await Promise.all(jobs);
@@ -893,6 +934,7 @@ function baseState(): State {
893
934
  * let a stale failure clobber a newer success (returning `undefined` keeps the
894
935
  * current service state; see JSRuntimeBootstrap.applyNext). */
895
936
  let refreshGen = 0;
937
+
896
938
  /** Last successful featured list — catch paths read this when the action's
897
939
  * `state` snapshot was captured before a racing refresh painted products. */
898
940
  let lastFeaturedProducts: ProductFeature[] = [];
@@ -18287,7 +18287,8 @@ const TEMPLATED: { pattern: string; display: string; liveSeed?: LiveSeed; static
18287
18287
  "open_url",
18288
18288
  "calendar",
18289
18289
  "share",
18290
- "wix_cashier"
18290
+ "wix_cashier",
18291
+ "local_database"
18291
18292
  ],
18292
18293
  "minBinaryVersion": "0.1.0",
18293
18294
  "bundleVersion": "0.0.1"
@@ -19829,7 +19830,8 @@ const TEMPLATED: { pattern: string; display: string; liveSeed?: LiveSeed; static
19829
19830
  "contentType": "application/javascript+ts-bundle",
19830
19831
  "declaredCapabilities": [
19831
19832
  "wix_api",
19832
- "share"
19833
+ "share",
19834
+ "local_database"
19833
19835
  ],
19834
19836
  "minBinaryVersion": "0.1.0",
19835
19837
  "bundleVersion": "0.0.1"
@@ -20,6 +20,39 @@ import { readSavedProducts, type SavedProduct } from "../_shared/himi-store-save
20
20
  import { readSavedIds } from "../_shared/himi-blog-saved.js";
21
21
  import { wixMediaUrl } from "@himalaya/wix-media";
22
22
 
23
+ /** Latched once the member feed is refused because of WHERE the app is running rather
24
+ * than because of the request — see the catch in `loadExtras`. Module scope, so it
25
+ * survives every refresh of this worker and is reset only by a fresh boot. */
26
+ let feedSurfaceRefused = false;
27
+
28
+ /** True for the one refusal that can never change while the app keeps running: the web
29
+ * bridge reporting that this host is not reachable from a browser at all. Deliberately
30
+ * narrower than `isBridgeUnavailable` — a 502/503 is an outage worth retrying, and only
31
+ * this typed refusal is permanent. */
32
+ function isFeedSurfaceRefusal(error: unknown): boolean {
33
+ return /webwix_owner_only/.test(String((error as Error)?.message ?? error));
34
+ }
35
+
36
+ /** In-flight feed reads, keyed by the container they are for.
37
+ *
38
+ * The latch alone still let ONE duplicate through: two Home refreshes that overlap both read
39
+ * `feedSurfaceRefused === false` before the first rejection reaches the catch. Sharing the
40
+ * request closes that window — the second refresh awaits the first instead of sending its own,
41
+ * and both see the same rejection, so the latch is set exactly once.
42
+ *
43
+ * Keyed by container, not a single slot: `active` is the most recently active group, recomputed
44
+ * per refresh from a live `queryGroups`, so two overlapping refreshes can legitimately want
45
+ * DIFFERENT containers. A single shared promise would hand the second one the first one's feed. */
46
+ const feedInFlight = new Map<string, Promise<Awaited<ReturnType<typeof listFeedItems>>>>();
47
+
48
+ function feedFor(containerId: string, limit: number): Promise<Awaited<ReturnType<typeof listFeedItems>>> {
49
+ const pending = feedInFlight.get(containerId);
50
+ if (pending) return pending;
51
+ const started = listFeedItems(containerId, limit).finally(() => { feedInFlight.delete(containerId); });
52
+ feedInFlight.set(containerId, started);
53
+ return started;
54
+ }
55
+
23
56
  interface ProductFeature {
24
57
  id: string;
25
58
  name: string;
@@ -714,10 +747,10 @@ async function fetchExtrasSignal(arg: unknown): Promise<ExtrasSignal> {
714
747
  const response = await queryGroups({ paging: { limit: 12 } });
715
748
  const groups = (response.groups ?? []).filter((group) => group.id);
716
749
  if (verticals.includes("groups")) extras.groupCards = groups.slice(0, 6).map(groupCard);
717
- if (verticals.includes("feed") && groups.length) {
750
+ if (verticals.includes("feed") && groups.length && !feedSurfaceRefused) {
718
751
  const active = groups.slice().sort((a, b) =>
719
752
  new Date(b.lastActivityDate ?? 0).getTime() - new Date(a.lastActivityDate ?? 0).getTime())[0];
720
- const feed = await listFeedItems(active.id, 8);
753
+ const feed = await feedFor(active.id, 8);
721
754
  const profiles = feed.profiles ?? [];
722
755
  const items = (feed.feedItems ?? []).filter((item) => item.feedItemId);
723
756
  extras.feedHighlights = items.slice(0, 5).map((item) => {
@@ -742,7 +775,15 @@ async function fetchExtrasSignal(arg: unknown): Promise<ExtrasSignal> {
742
775
  }))
743
776
  .slice(0, 12);
744
777
  }
745
- } catch { /* collapse silently */ }
778
+ } catch (error) {
779
+ // This particular refusal is PERMANENT, not a blip: in a browser the member feed's
780
+ // host cannot be reached at all, so asking again can only fail again. Retrying on
781
+ // every Home visit spent one doomed round trip each time and filled the error log
782
+ // with something nobody can act on. Latch it once and stop asking; on a phone,
783
+ // where the call does work, this never trips.
784
+ if (isFeedSurfaceRefusal(error)) feedSurfaceRefused = true;
785
+ /* otherwise collapse silently, as before */
786
+ }
746
787
  })());
747
788
  }
748
789
  await Promise.all(jobs);
@@ -893,6 +934,7 @@ function baseState(): State {
893
934
  * let a stale failure clobber a newer success (returning `undefined` keeps the
894
935
  * current service state; see JSRuntimeBootstrap.applyNext). */
895
936
  let refreshGen = 0;
937
+
896
938
  /** Last successful featured list — catch paths read this when the action's
897
939
  * `state` snapshot was captured before a racing refresh painted products. */
898
940
  let lastFeaturedProducts: ProductFeature[] = [];
@@ -21549,7 +21549,8 @@ const TEMPLATED: { pattern: string; display: string; liveSeed?: LiveSeed; static
21549
21549
  "contentType": "application/javascript+ts-bundle",
21550
21550
  "declaredCapabilities": [
21551
21551
  "wix_api",
21552
- "share"
21552
+ "share",
21553
+ "local_database"
21553
21554
  ],
21554
21555
  "minBinaryVersion": "0.1.0",
21555
21556
  "bundleVersion": "0.0.1"
@@ -22479,7 +22480,8 @@ const TEMPLATED: { pattern: string; display: string; liveSeed?: LiveSeed; static
22479
22480
  "open_url",
22480
22481
  "calendar",
22481
22482
  "share",
22482
- "wix_cashier"
22483
+ "wix_cashier",
22484
+ "local_database"
22483
22485
  ],
22484
22486
  "minBinaryVersion": "0.1.0",
22485
22487
  "bundleVersion": "0.0.1"
@@ -24021,7 +24023,8 @@ const TEMPLATED: { pattern: string; display: string; liveSeed?: LiveSeed; static
24021
24023
  "contentType": "application/javascript+ts-bundle",
24022
24024
  "declaredCapabilities": [
24023
24025
  "wix_api",
24024
- "share"
24026
+ "share",
24027
+ "local_database"
24025
24028
  ],
24026
24029
  "minBinaryVersion": "0.1.0",
24027
24030
  "bundleVersion": "0.0.1"
@@ -20,6 +20,39 @@ import { readSavedProducts, type SavedProduct } from "../_shared/himi-store-save
20
20
  import { readSavedIds } from "../_shared/himi-blog-saved.js";
21
21
  import { wixMediaUrl } from "@himalaya/wix-media";
22
22
 
23
+ /** Latched once the member feed is refused because of WHERE the app is running rather
24
+ * than because of the request — see the catch in `loadExtras`. Module scope, so it
25
+ * survives every refresh of this worker and is reset only by a fresh boot. */
26
+ let feedSurfaceRefused = false;
27
+
28
+ /** True for the one refusal that can never change while the app keeps running: the web
29
+ * bridge reporting that this host is not reachable from a browser at all. Deliberately
30
+ * narrower than `isBridgeUnavailable` — a 502/503 is an outage worth retrying, and only
31
+ * this typed refusal is permanent. */
32
+ function isFeedSurfaceRefusal(error: unknown): boolean {
33
+ return /webwix_owner_only/.test(String((error as Error)?.message ?? error));
34
+ }
35
+
36
+ /** In-flight feed reads, keyed by the container they are for.
37
+ *
38
+ * The latch alone still let ONE duplicate through: two Home refreshes that overlap both read
39
+ * `feedSurfaceRefused === false` before the first rejection reaches the catch. Sharing the
40
+ * request closes that window — the second refresh awaits the first instead of sending its own,
41
+ * and both see the same rejection, so the latch is set exactly once.
42
+ *
43
+ * Keyed by container, not a single slot: `active` is the most recently active group, recomputed
44
+ * per refresh from a live `queryGroups`, so two overlapping refreshes can legitimately want
45
+ * DIFFERENT containers. A single shared promise would hand the second one the first one's feed. */
46
+ const feedInFlight = new Map<string, Promise<Awaited<ReturnType<typeof listFeedItems>>>>();
47
+
48
+ function feedFor(containerId: string, limit: number): Promise<Awaited<ReturnType<typeof listFeedItems>>> {
49
+ const pending = feedInFlight.get(containerId);
50
+ if (pending) return pending;
51
+ const started = listFeedItems(containerId, limit).finally(() => { feedInFlight.delete(containerId); });
52
+ feedInFlight.set(containerId, started);
53
+ return started;
54
+ }
55
+
23
56
  interface ProductFeature {
24
57
  id: string;
25
58
  name: string;
@@ -714,10 +747,10 @@ async function fetchExtrasSignal(arg: unknown): Promise<ExtrasSignal> {
714
747
  const response = await queryGroups({ paging: { limit: 12 } });
715
748
  const groups = (response.groups ?? []).filter((group) => group.id);
716
749
  if (verticals.includes("groups")) extras.groupCards = groups.slice(0, 6).map(groupCard);
717
- if (verticals.includes("feed") && groups.length) {
750
+ if (verticals.includes("feed") && groups.length && !feedSurfaceRefused) {
718
751
  const active = groups.slice().sort((a, b) =>
719
752
  new Date(b.lastActivityDate ?? 0).getTime() - new Date(a.lastActivityDate ?? 0).getTime())[0];
720
- const feed = await listFeedItems(active.id, 8);
753
+ const feed = await feedFor(active.id, 8);
721
754
  const profiles = feed.profiles ?? [];
722
755
  const items = (feed.feedItems ?? []).filter((item) => item.feedItemId);
723
756
  extras.feedHighlights = items.slice(0, 5).map((item) => {
@@ -742,7 +775,15 @@ async function fetchExtrasSignal(arg: unknown): Promise<ExtrasSignal> {
742
775
  }))
743
776
  .slice(0, 12);
744
777
  }
745
- } catch { /* collapse silently */ }
778
+ } catch (error) {
779
+ // This particular refusal is PERMANENT, not a blip: in a browser the member feed's
780
+ // host cannot be reached at all, so asking again can only fail again. Retrying on
781
+ // every Home visit spent one doomed round trip each time and filled the error log
782
+ // with something nobody can act on. Latch it once and stop asking; on a phone,
783
+ // where the call does work, this never trips.
784
+ if (isFeedSurfaceRefusal(error)) feedSurfaceRefused = true;
785
+ /* otherwise collapse silently, as before */
786
+ }
746
787
  })());
747
788
  }
748
789
  await Promise.all(jobs);
@@ -893,6 +934,7 @@ function baseState(): State {
893
934
  * let a stale failure clobber a newer success (returning `undefined` keeps the
894
935
  * current service state; see JSRuntimeBootstrap.applyNext). */
895
936
  let refreshGen = 0;
937
+
896
938
  /** Last successful featured list — catch paths read this when the action's
897
939
  * `state` snapshot was captured before a racing refresh painted products. */
898
940
  let lastFeaturedProducts: ProductFeature[] = [];
@@ -16005,7 +16005,8 @@ const TEMPLATED: { pattern: string; display: string; liveSeed?: LiveSeed; static
16005
16005
  "open_url",
16006
16006
  "calendar",
16007
16007
  "share",
16008
- "wix_cashier"
16008
+ "wix_cashier",
16009
+ "local_database"
16009
16010
  ],
16010
16011
  "minBinaryVersion": "0.1.0",
16011
16012
  "bundleVersion": "0.0.1"
@@ -17547,7 +17548,8 @@ const TEMPLATED: { pattern: string; display: string; liveSeed?: LiveSeed; static
17547
17548
  "contentType": "application/javascript+ts-bundle",
17548
17549
  "declaredCapabilities": [
17549
17550
  "wix_api",
17550
- "share"
17551
+ "share",
17552
+ "local_database"
17551
17553
  ],
17552
17554
  "minBinaryVersion": "0.1.0",
17553
17555
  "bundleVersion": "0.0.1"
@@ -20,6 +20,39 @@ import { readSavedProducts, type SavedProduct } from "../_shared/himi-store-save
20
20
  import { readSavedIds } from "../_shared/himi-blog-saved.js";
21
21
  import { wixMediaUrl } from "@himalaya/wix-media";
22
22
 
23
+ /** Latched once the member feed is refused because of WHERE the app is running rather
24
+ * than because of the request — see the catch in `loadExtras`. Module scope, so it
25
+ * survives every refresh of this worker and is reset only by a fresh boot. */
26
+ let feedSurfaceRefused = false;
27
+
28
+ /** True for the one refusal that can never change while the app keeps running: the web
29
+ * bridge reporting that this host is not reachable from a browser at all. Deliberately
30
+ * narrower than `isBridgeUnavailable` — a 502/503 is an outage worth retrying, and only
31
+ * this typed refusal is permanent. */
32
+ function isFeedSurfaceRefusal(error: unknown): boolean {
33
+ return /webwix_owner_only/.test(String((error as Error)?.message ?? error));
34
+ }
35
+
36
+ /** In-flight feed reads, keyed by the container they are for.
37
+ *
38
+ * The latch alone still let ONE duplicate through: two Home refreshes that overlap both read
39
+ * `feedSurfaceRefused === false` before the first rejection reaches the catch. Sharing the
40
+ * request closes that window — the second refresh awaits the first instead of sending its own,
41
+ * and both see the same rejection, so the latch is set exactly once.
42
+ *
43
+ * Keyed by container, not a single slot: `active` is the most recently active group, recomputed
44
+ * per refresh from a live `queryGroups`, so two overlapping refreshes can legitimately want
45
+ * DIFFERENT containers. A single shared promise would hand the second one the first one's feed. */
46
+ const feedInFlight = new Map<string, Promise<Awaited<ReturnType<typeof listFeedItems>>>>();
47
+
48
+ function feedFor(containerId: string, limit: number): Promise<Awaited<ReturnType<typeof listFeedItems>>> {
49
+ const pending = feedInFlight.get(containerId);
50
+ if (pending) return pending;
51
+ const started = listFeedItems(containerId, limit).finally(() => { feedInFlight.delete(containerId); });
52
+ feedInFlight.set(containerId, started);
53
+ return started;
54
+ }
55
+
23
56
  interface ProductFeature {
24
57
  id: string;
25
58
  name: string;
@@ -714,10 +747,10 @@ async function fetchExtrasSignal(arg: unknown): Promise<ExtrasSignal> {
714
747
  const response = await queryGroups({ paging: { limit: 12 } });
715
748
  const groups = (response.groups ?? []).filter((group) => group.id);
716
749
  if (verticals.includes("groups")) extras.groupCards = groups.slice(0, 6).map(groupCard);
717
- if (verticals.includes("feed") && groups.length) {
750
+ if (verticals.includes("feed") && groups.length && !feedSurfaceRefused) {
718
751
  const active = groups.slice().sort((a, b) =>
719
752
  new Date(b.lastActivityDate ?? 0).getTime() - new Date(a.lastActivityDate ?? 0).getTime())[0];
720
- const feed = await listFeedItems(active.id, 8);
753
+ const feed = await feedFor(active.id, 8);
721
754
  const profiles = feed.profiles ?? [];
722
755
  const items = (feed.feedItems ?? []).filter((item) => item.feedItemId);
723
756
  extras.feedHighlights = items.slice(0, 5).map((item) => {
@@ -742,7 +775,15 @@ async function fetchExtrasSignal(arg: unknown): Promise<ExtrasSignal> {
742
775
  }))
743
776
  .slice(0, 12);
744
777
  }
745
- } catch { /* collapse silently */ }
778
+ } catch (error) {
779
+ // This particular refusal is PERMANENT, not a blip: in a browser the member feed's
780
+ // host cannot be reached at all, so asking again can only fail again. Retrying on
781
+ // every Home visit spent one doomed round trip each time and filled the error log
782
+ // with something nobody can act on. Latch it once and stop asking; on a phone,
783
+ // where the call does work, this never trips.
784
+ if (isFeedSurfaceRefusal(error)) feedSurfaceRefused = true;
785
+ /* otherwise collapse silently, as before */
786
+ }
746
787
  })());
747
788
  }
748
789
  await Promise.all(jobs);
@@ -893,6 +934,7 @@ function baseState(): State {
893
934
  * let a stale failure clobber a newer success (returning `undefined` keeps the
894
935
  * current service state; see JSRuntimeBootstrap.applyNext). */
895
936
  let refreshGen = 0;
937
+
896
938
  /** Last successful featured list — catch paths read this when the action's
897
939
  * `state` snapshot was captured before a racing refresh painted products. */
898
940
  let lastFeaturedProducts: ProductFeature[] = [];
@@ -15098,7 +15098,8 @@ const TEMPLATED: { pattern: string; display: string; liveSeed?: LiveSeed; static
15098
15098
  "contentType": "application/javascript+ts-bundle",
15099
15099
  "declaredCapabilities": [
15100
15100
  "wix_api",
15101
- "share"
15101
+ "share",
15102
+ "local_database"
15102
15103
  ],
15103
15104
  "minBinaryVersion": "0.1.0",
15104
15105
  "bundleVersion": "0.0.1"
@@ -15988,7 +15989,8 @@ const TEMPLATED: { pattern: string; display: string; liveSeed?: LiveSeed; static
15988
15989
  "contentType": "application/javascript+ts-bundle",
15989
15990
  "declaredCapabilities": [
15990
15991
  "wix_api",
15991
- "share"
15992
+ "share",
15993
+ "local_database"
15992
15994
  ],
15993
15995
  "minBinaryVersion": "0.1.0",
15994
15996
  "bundleVersion": "0.0.1"
@@ -20,6 +20,39 @@ import { readSavedProducts, type SavedProduct } from "../_shared/himi-store-save
20
20
  import { readSavedIds } from "../_shared/himi-blog-saved.js";
21
21
  import { wixMediaUrl } from "@himalaya/wix-media";
22
22
 
23
+ /** Latched once the member feed is refused because of WHERE the app is running rather
24
+ * than because of the request — see the catch in `loadExtras`. Module scope, so it
25
+ * survives every refresh of this worker and is reset only by a fresh boot. */
26
+ let feedSurfaceRefused = false;
27
+
28
+ /** True for the one refusal that can never change while the app keeps running: the web
29
+ * bridge reporting that this host is not reachable from a browser at all. Deliberately
30
+ * narrower than `isBridgeUnavailable` — a 502/503 is an outage worth retrying, and only
31
+ * this typed refusal is permanent. */
32
+ function isFeedSurfaceRefusal(error: unknown): boolean {
33
+ return /webwix_owner_only/.test(String((error as Error)?.message ?? error));
34
+ }
35
+
36
+ /** In-flight feed reads, keyed by the container they are for.
37
+ *
38
+ * The latch alone still let ONE duplicate through: two Home refreshes that overlap both read
39
+ * `feedSurfaceRefused === false` before the first rejection reaches the catch. Sharing the
40
+ * request closes that window — the second refresh awaits the first instead of sending its own,
41
+ * and both see the same rejection, so the latch is set exactly once.
42
+ *
43
+ * Keyed by container, not a single slot: `active` is the most recently active group, recomputed
44
+ * per refresh from a live `queryGroups`, so two overlapping refreshes can legitimately want
45
+ * DIFFERENT containers. A single shared promise would hand the second one the first one's feed. */
46
+ const feedInFlight = new Map<string, Promise<Awaited<ReturnType<typeof listFeedItems>>>>();
47
+
48
+ function feedFor(containerId: string, limit: number): Promise<Awaited<ReturnType<typeof listFeedItems>>> {
49
+ const pending = feedInFlight.get(containerId);
50
+ if (pending) return pending;
51
+ const started = listFeedItems(containerId, limit).finally(() => { feedInFlight.delete(containerId); });
52
+ feedInFlight.set(containerId, started);
53
+ return started;
54
+ }
55
+
23
56
  interface ProductFeature {
24
57
  id: string;
25
58
  name: string;
@@ -714,10 +747,10 @@ async function fetchExtrasSignal(arg: unknown): Promise<ExtrasSignal> {
714
747
  const response = await queryGroups({ paging: { limit: 12 } });
715
748
  const groups = (response.groups ?? []).filter((group) => group.id);
716
749
  if (verticals.includes("groups")) extras.groupCards = groups.slice(0, 6).map(groupCard);
717
- if (verticals.includes("feed") && groups.length) {
750
+ if (verticals.includes("feed") && groups.length && !feedSurfaceRefused) {
718
751
  const active = groups.slice().sort((a, b) =>
719
752
  new Date(b.lastActivityDate ?? 0).getTime() - new Date(a.lastActivityDate ?? 0).getTime())[0];
720
- const feed = await listFeedItems(active.id, 8);
753
+ const feed = await feedFor(active.id, 8);
721
754
  const profiles = feed.profiles ?? [];
722
755
  const items = (feed.feedItems ?? []).filter((item) => item.feedItemId);
723
756
  extras.feedHighlights = items.slice(0, 5).map((item) => {
@@ -742,7 +775,15 @@ async function fetchExtrasSignal(arg: unknown): Promise<ExtrasSignal> {
742
775
  }))
743
776
  .slice(0, 12);
744
777
  }
745
- } catch { /* collapse silently */ }
778
+ } catch (error) {
779
+ // This particular refusal is PERMANENT, not a blip: in a browser the member feed's
780
+ // host cannot be reached at all, so asking again can only fail again. Retrying on
781
+ // every Home visit spent one doomed round trip each time and filled the error log
782
+ // with something nobody can act on. Latch it once and stop asking; on a phone,
783
+ // where the call does work, this never trips.
784
+ if (isFeedSurfaceRefusal(error)) feedSurfaceRefused = true;
785
+ /* otherwise collapse silently, as before */
786
+ }
746
787
  })());
747
788
  }
748
789
  await Promise.all(jobs);
@@ -893,6 +934,7 @@ function baseState(): State {
893
934
  * let a stale failure clobber a newer success (returning `undefined` keeps the
894
935
  * current service state; see JSRuntimeBootstrap.applyNext). */
895
936
  let refreshGen = 0;
937
+
896
938
  /** Last successful featured list — catch paths read this when the action's
897
939
  * `state` snapshot was captured before a racing refresh painted products. */
898
940
  let lastFeaturedProducts: ProductFeature[] = [];
@@ -22840,7 +22840,8 @@ const TEMPLATED: { pattern: string; display: string; liveSeed?: LiveSeed; static
22840
22840
  "contentType": "application/javascript+ts-bundle",
22841
22841
  "declaredCapabilities": [
22842
22842
  "wix_api",
22843
- "share"
22843
+ "share",
22844
+ "local_database"
22844
22845
  ],
22845
22846
  "minBinaryVersion": "0.1.0",
22846
22847
  "bundleVersion": "0.0.1"
@@ -25234,7 +25235,8 @@ const TEMPLATED: { pattern: string; display: string; liveSeed?: LiveSeed; static
25234
25235
  "open_url",
25235
25236
  "calendar",
25236
25237
  "share",
25237
- "wix_cashier"
25238
+ "wix_cashier",
25239
+ "local_database"
25238
25240
  ],
25239
25241
  "minBinaryVersion": "0.1.0",
25240
25242
  "bundleVersion": "0.0.1"
@@ -26776,7 +26778,8 @@ const TEMPLATED: { pattern: string; display: string; liveSeed?: LiveSeed; static
26776
26778
  "contentType": "application/javascript+ts-bundle",
26777
26779
  "declaredCapabilities": [
26778
26780
  "wix_api",
26779
- "share"
26781
+ "share",
26782
+ "local_database"
26780
26783
  ],
26781
26784
  "minBinaryVersion": "0.1.0",
26782
26785
  "bundleVersion": "0.0.1"
@@ -20,6 +20,39 @@ import { readSavedProducts, type SavedProduct } from "../_shared/himi-store-save
20
20
  import { readSavedIds } from "../_shared/himi-blog-saved.js";
21
21
  import { wixMediaUrl } from "@himalaya/wix-media";
22
22
 
23
+ /** Latched once the member feed is refused because of WHERE the app is running rather
24
+ * than because of the request — see the catch in `loadExtras`. Module scope, so it
25
+ * survives every refresh of this worker and is reset only by a fresh boot. */
26
+ let feedSurfaceRefused = false;
27
+
28
+ /** True for the one refusal that can never change while the app keeps running: the web
29
+ * bridge reporting that this host is not reachable from a browser at all. Deliberately
30
+ * narrower than `isBridgeUnavailable` — a 502/503 is an outage worth retrying, and only
31
+ * this typed refusal is permanent. */
32
+ function isFeedSurfaceRefusal(error: unknown): boolean {
33
+ return /webwix_owner_only/.test(String((error as Error)?.message ?? error));
34
+ }
35
+
36
+ /** In-flight feed reads, keyed by the container they are for.
37
+ *
38
+ * The latch alone still let ONE duplicate through: two Home refreshes that overlap both read
39
+ * `feedSurfaceRefused === false` before the first rejection reaches the catch. Sharing the
40
+ * request closes that window — the second refresh awaits the first instead of sending its own,
41
+ * and both see the same rejection, so the latch is set exactly once.
42
+ *
43
+ * Keyed by container, not a single slot: `active` is the most recently active group, recomputed
44
+ * per refresh from a live `queryGroups`, so two overlapping refreshes can legitimately want
45
+ * DIFFERENT containers. A single shared promise would hand the second one the first one's feed. */
46
+ const feedInFlight = new Map<string, Promise<Awaited<ReturnType<typeof listFeedItems>>>>();
47
+
48
+ function feedFor(containerId: string, limit: number): Promise<Awaited<ReturnType<typeof listFeedItems>>> {
49
+ const pending = feedInFlight.get(containerId);
50
+ if (pending) return pending;
51
+ const started = listFeedItems(containerId, limit).finally(() => { feedInFlight.delete(containerId); });
52
+ feedInFlight.set(containerId, started);
53
+ return started;
54
+ }
55
+
23
56
  interface ProductFeature {
24
57
  id: string;
25
58
  name: string;
@@ -714,10 +747,10 @@ async function fetchExtrasSignal(arg: unknown): Promise<ExtrasSignal> {
714
747
  const response = await queryGroups({ paging: { limit: 12 } });
715
748
  const groups = (response.groups ?? []).filter((group) => group.id);
716
749
  if (verticals.includes("groups")) extras.groupCards = groups.slice(0, 6).map(groupCard);
717
- if (verticals.includes("feed") && groups.length) {
750
+ if (verticals.includes("feed") && groups.length && !feedSurfaceRefused) {
718
751
  const active = groups.slice().sort((a, b) =>
719
752
  new Date(b.lastActivityDate ?? 0).getTime() - new Date(a.lastActivityDate ?? 0).getTime())[0];
720
- const feed = await listFeedItems(active.id, 8);
753
+ const feed = await feedFor(active.id, 8);
721
754
  const profiles = feed.profiles ?? [];
722
755
  const items = (feed.feedItems ?? []).filter((item) => item.feedItemId);
723
756
  extras.feedHighlights = items.slice(0, 5).map((item) => {
@@ -742,7 +775,15 @@ async function fetchExtrasSignal(arg: unknown): Promise<ExtrasSignal> {
742
775
  }))
743
776
  .slice(0, 12);
744
777
  }
745
- } catch { /* collapse silently */ }
778
+ } catch (error) {
779
+ // This particular refusal is PERMANENT, not a blip: in a browser the member feed's
780
+ // host cannot be reached at all, so asking again can only fail again. Retrying on
781
+ // every Home visit spent one doomed round trip each time and filled the error log
782
+ // with something nobody can act on. Latch it once and stop asking; on a phone,
783
+ // where the call does work, this never trips.
784
+ if (isFeedSurfaceRefusal(error)) feedSurfaceRefused = true;
785
+ /* otherwise collapse silently, as before */
786
+ }
746
787
  })());
747
788
  }
748
789
  await Promise.all(jobs);
@@ -893,6 +934,7 @@ function baseState(): State {
893
934
  * let a stale failure clobber a newer success (returning `undefined` keeps the
894
935
  * current service state; see JSRuntimeBootstrap.applyNext). */
895
936
  let refreshGen = 0;
937
+
896
938
  /** Last successful featured list — catch paths read this when the action's
897
939
  * `state` snapshot was captured before a racing refresh painted products. */
898
940
  let lastFeaturedProducts: ProductFeature[] = [];