@wix/himalaya-cli 0.1571.0 → 0.1572.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.
Binary file
package/guides/index.json CHANGED
@@ -296,6 +296,12 @@
296
296
  "category": "Wix SDK",
297
297
  "file": "one-app-to-himi-app.md"
298
298
  },
299
+ {
300
+ "id": "recipe-one-app-coverage-mapping",
301
+ "title": "Recipe: map One App coverage decisions",
302
+ "category": "Recipes",
303
+ "file": "recipe-one-app-coverage-mapping.md"
304
+ },
299
305
  {
300
306
  "id": "one-app-reference",
301
307
  "title": "One App reconstruction references and provenance",
@@ -441,6 +447,7 @@
441
447
  "showcaseFile": "stocks-dashboard.descriptor.json"
442
448
  },
443
449
  "examples": [
450
+ "map-one-app-coverage.js",
444
451
  "counter.service.ts",
445
452
  "counter-t5.descriptor.json",
446
453
  "stocks-dashboard.descriptor.json",
@@ -0,0 +1,117 @@
1
+ // Local reference recipe, not a readiness check. See himi docs recipe-one-app-coverage-mapping.
2
+ import { execFileSync } from "node:child_process";
3
+ import { lstatSync, readFileSync, realpathSync, writeFileSync } from "node:fs";
4
+ import { dirname, resolve } from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+
7
+ const object = value => value !== null && typeof value === "object" && !Array.isArray(value);
8
+ const sameTarget = (a, b) => a?.screenId === b?.screenId && a?.controlId === b?.controlId;
9
+ const needsControl = row => row.kind === "interaction" || row.kind === "top-bar";
10
+
11
+ function idsIn(value, ids = new Set()) {
12
+ if (Array.isArray(value)) value.forEach(item => idsIn(item, ids));
13
+ else if (object(value)) {
14
+ const component = typeof value.body?.type === "string" || (typeof value.type === "string" && object(value.props));
15
+ if (component && typeof value.id === "string" && !value.id.includes("$")) ids.add(value.id);
16
+ for (const [key, item] of Object.entries(value)) if (key !== "params" && key !== "metadata") idsIn(item, ids);
17
+ }
18
+ return ids;
19
+ }
20
+
21
+ function controlIds(screen) {
22
+ const ids = idsIn([screen.payload, screen.stickyHeader, screen.stickyFooter]);
23
+ for (const action of screen.topBar?.actions ?? []) {
24
+ if (typeof action.id === "string" && !action.id.includes("$")) ids.add(action.id);
25
+ }
26
+ return ids;
27
+ }
28
+
29
+ /** Inputs are explicit decisions and the full authored descriptor map, with effective top bars. */
30
+ export function mapCoverage(ledger, decisions, screens) {
31
+ if (ledger?.version !== 1 || ledger.workflow !== "one-app-to-himi-app" || !Array.isArray(ledger.requirements)
32
+ || !Array.isArray(decisions) || !object(screens)) throw new Error("Expected a coverage ledger, decision array and authored screen map.");
33
+ const coverage = structuredClone(ledger);
34
+ const rows = coverage.requirements;
35
+ if (new Set(rows.map(row => row.id)).size !== rows.length) throw new Error("Duplicate ledger row IDs; run coverage check.");
36
+ const issues = [];
37
+ const selected = new Map();
38
+ const invalidate = row => {
39
+ row.status = "unresolved";
40
+ delete row.target;
41
+ delete row.verification;
42
+ delete row.adaptation;
43
+ };
44
+ const targetProblem = (row, target) => {
45
+ if (!object(target) || Object.keys(target).some(key => !["screenId", "controlId"].includes(key))) return "Expected only screenId and optional controlId.";
46
+ if (typeof target.screenId !== "string" || !target.screenId.trim()) return "An explicit target screenId is required.";
47
+ const screen = Object.hasOwn(screens, target.screenId) && screens[target.screenId];
48
+ if (!object(screen) || screen.screenId !== target.screenId) return "Target screen is absent from the authored descriptor map.";
49
+ if (needsControl(row) && (typeof target.controlId !== "string" || !target.controlId.trim())) return "This row requires an explicit controlId.";
50
+ if (target.controlId !== undefined && !controlIds(screen).has(target.controlId)) return "Target control ID is absent from that screen, including its effective top bar.";
51
+ };
52
+ // A removed target cannot retain a passing record merely because no decision mentions it.
53
+ for (const row of rows) if (row.kind !== "design" && targetProblem(row, row.target)) invalidate(row);
54
+ for (const [index, decision] of decisions.entries()) {
55
+ if (!object(decision)) { issues.push({ decision: index, reason: "Expected an explicit row/source decision." }); continue; }
56
+ const matches = rows.filter(row => typeof decision.id === "string" ? row.id === decision.id
57
+ : typeof decision.source === "string" && row.source === decision.source && row.kind === decision.kind && row.platform === decision.platform);
58
+ if (matches.length !== 1) { issues.push({ decision: index, reason: "Unknown or ambiguous row/source; use the exact row ID or kind/source/platform." }); continue; }
59
+ const row = matches[0];
60
+ if (row.kind === "design") { issues.push({ decision: index, id: row.id, reason: "Design rows belong to validate-design, not screen mapping." }); continue; }
61
+ const allowed = typeof decision.id === "string" ? ["id", "target"] : ["source", "kind", "platform", "target"];
62
+ const problem = Object.keys(decision).some(key => !allowed.includes(key)) ? "Decisions cannot set status, verification, adaptation or other row fields."
63
+ : targetProblem(row, decision.target);
64
+ if (selected.has(row.id)) {
65
+ selected.set(row.id, null);
66
+ invalidate(row);
67
+ issues.push({ decision: index, id: row.id, reason: "Duplicate decisions for this row; left unresolved." });
68
+ } else {
69
+ selected.set(row.id, problem ? null : decision.target);
70
+ if (problem) { invalidate(row); issues.push({ decision: index, id: row.id, reason: problem }); }
71
+ }
72
+ }
73
+ for (const [id, target] of selected) {
74
+ if (!target) continue;
75
+ const row = rows.find(row => row.id === id);
76
+ if (!sameTarget(row.target, target)) {
77
+ invalidate(row);
78
+ row.target = { ...target };
79
+ }
80
+ }
81
+ return {
82
+ coverage,
83
+ issues,
84
+ unmapped: rows.filter(row => row.kind !== "design" && !row.target).map(({ id, kind, source, platform }) => ({ id, kind, source, ...(platform ? { platform } : {}) })),
85
+ // Inventory checks cannot validate retained proof against the author's current built release.
86
+ needsVerification: rows.map(row => row.id),
87
+ };
88
+ }
89
+
90
+ export function main(args) {
91
+ const [content, evidence, decisionsFile, screensFile, ...flags] = args;
92
+ if (!screensFile || flags.some(flag => flag !== "--write") || flags.length > 1) throw new Error("Usage: node map-one-app-coverage.js <content> <evidence-dir> <decisions.json> <screens.json> [--write]");
93
+ // Reuse the installed CLI's actual source hashes, derived row IDs and schema checks. No sync/write.
94
+ execFileSync(process.env.HIMI_BIN ?? "himi", ["one-app", "coverage", "check", "--content", content, "--evidence-dir", evidence, "--json", "--skip-version-check"], { stdio: ["ignore", "pipe", "pipe"] });
95
+ const ledgerFile = resolve(content, "ONE-APP-COVERAGE.json");
96
+ if (!lstatSync(ledgerFile).isFile() || lstatSync(ledgerFile).isSymbolicLink()) throw new Error("The ledger must be a regular file.");
97
+ const read = file => JSON.parse(readFileSync(file, "utf8"));
98
+ const result = mapCoverage(read(ledgerFile), read(decisionsFile), read(screensFile));
99
+ if (flags.includes("--write")) {
100
+ for (let directory = resolve(content);; directory = dirname(directory)) {
101
+ const stat = lstatSync(directory);
102
+ if (stat.isSymbolicLink() || !stat.isDirectory()) throw new Error("Use a physical content directory without symlinked parents for --write.");
103
+ if (directory === dirname(directory)) break;
104
+ }
105
+ writeFileSync(ledgerFile, JSON.stringify(result.coverage, null, 2) + "\n");
106
+ }
107
+ console.log(JSON.stringify({ ...result, written: flags.includes("--write") }, null, 2));
108
+ return result.issues.length || result.unmapped.length ? 1 : 0;
109
+ }
110
+
111
+ if (process.argv[1] && realpathSync(fileURLToPath(import.meta.url)) === realpathSync(process.argv[1])) {
112
+ try { process.exitCode = main(process.argv.slice(2)); }
113
+ catch (error) {
114
+ console.error(JSON.stringify({ ok: false, code: "coverage_mapping_input_invalid", error: error.message }));
115
+ process.exitCode = 1;
116
+ }
117
+ }
@@ -136,6 +136,50 @@ 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
+
140
+ ### Native widget defaults
141
+
142
+ Empty settings do not mean an empty native widget. Coverage derives separate
143
+ `native-widget` obligations for each placement from the runtime checklist below,
144
+ including repeated sections, detached pages and Business Profile placements.
145
+ These are behaviors to compare, not claims that the target implements them.
146
+ The CLI's versioned native-widget inventory supplies these rows automatically.
147
+
148
+ | Source section type | Effective behavior to verify |
149
+ | --- | --- |
150
+ | `bookings_services_list_section` | Search, category and location filters. |
151
+ | `single_group_feed_section`, `groups_feed_section` | Post actions, attachments, comments/post destination, topic filter and the group's Members / Library / Rules / Media destinations. |
152
+ | `platform_general_contact_section`, `platform_general_contact_section_bp` | Business address and map, respecting authored visibility and available coordinates. |
153
+ | `achievements_list_section` | Category filter, including All Categories with no categories; actual visitor/member login, access/join/payment/Continue states; participants line; default limit 2 and See All when applicable. |
154
+ | `achievements_carousel_section` | The same Program state/display rules, default limit 5 and page dots. |
155
+ | `newBlog_postlist_vertical_section` | See All Posts when a limited list has more results, preserving its scope. |
156
+ | `events_list_section` | Date badge, Share and the appropriate RSVP/ticket action. |
157
+ | `platform_general_members_section`, `platform_general_members_section_bp` | Effective member strip and directory journey: totals/search, alphabetical sections, Follow, activity/privacy. Distinguish badge chips from actual viewed-member program progress. |
158
+
159
+ This initial checklist comes from reported native source-device observations
160
+ on 2026-11-02 across two migrations, not editor demo records.
161
+ Program settings are also backed by the pinned
162
+ [Program section implementation](https://github.com/wix-private/wix-one-app-achievements/tree/f844eee1fe7cc7732531c3deaa7dca777e53c548/sections "audience-lint-ok: optional pinned upstream evidence, not a local authoring dependency").
163
+ Its absent category selection means All/future categories; explicit empty selection
164
+ means none. MANUAL order prioritizes selected IDs without excluding other records.
165
+ Use current collected settings and source-device behavior to resolve each instance.
166
+ An explicit disable/override is source evidence: faithfully preserving it requires
167
+ no adaptation waiver, and this checklist must not force the disabled control on.
168
+ Data/access-dependent controls need the relevant source state; never fabricate
169
+ participants or derive program completion from a badge title.
170
+
171
+ The table is not exhaustive. Other section types get an explicit unverified-defaults
172
+ obligation; inventory their effective source behavior rather than assuming there
173
+ are no defaults. Changing the behavior contract creates new requirement identities.
174
+ Recollection invalidates status/check evidence while preserving unchanged mappings.
175
+
176
+ Compare every tab in the actual Player or Branded Lite host with the source app
177
+ on device and live data under the same site and visitor/member identity. The editor
178
+ can show demo members/badges or blank verticals; editor-only observations remain
179
+ unverified. Until missing behavior is implemented, retain an honest unresolved,
180
+ conditional or explicitly authorized adapted row. Do not mark it supported merely
181
+ because its placement exists.
182
+
139
183
  `business-context.json.memberCount` preserves a valid Business aggregate
140
184
  `members.count` when available. For source `platform_general_members_section` or
141
185
  `platform_general_members_section_bp` widgets, collection instead reads the
@@ -235,10 +279,30 @@ Apply these interpretation rules:
235
279
  because a missing scheme falls back to the light value. Rebuild after `--fix`.
236
280
  For Branded apps, known source typography IDs (for example `lato`) become family
237
281
  names (`Lato`); tokens whose names contain title/heading/headline/display/hero use the heading family; other
238
- typography tokens use the body family. Numeric metrics stay unchanged. Unknown IDs,
239
- families outside the Himi font catalog, and missing target typography roles remain unresolved; resolve them before recording
240
- a pass, even when the coverage ledger cites an adaptation. A deliberate difference
241
- between resolved font roles can still be adapted. Build/font validation still has
282
+ typography tokens use the body family. Numeric metrics stay unchanged. Read each
283
+ source heading/body family and check `himi fonts list`; use `himi fonts add <Family>`
284
+ when it is available. A known source family that Himi does not ship (for example
285
+ `spaceGrotesk` → Space Grotesk) is still compared with the actual target family.
286
+ Choose an available replacement, such as `$system`, only with the owner's explicit
287
+ instruction; this is an adaptation, never a font match. Sync the coverage ledger
288
+ against the current source evidence, then mark the corresponding `design` requirement
289
+ `adapted` with `adaptation.instruction` citing that instruction and
290
+ `adaptation.explanation` naming the source, replacement and reason. Bind a base font decision
291
+ to the exact reported `target` string in `adaptation.target` (for example,
292
+ `"body: $system, caption: $system"`); a later replacement requires a new decision.
293
+ When an active idiom changes the font, name it in `adaptation.idioms` and copy its
294
+ reported target into `adaptation.idiomTargets[idiom]`. An overlay that keeps the base
295
+ fonts needs no separate font approval. When the base already matches, an idiom-only
296
+ decision needs only `idioms` and `idiomTargets`, without a base `target`.
297
+ Rerun `himi one-app validate-design` to produce
298
+ the source-to-target report; without that adaptation the difference fails the check.
299
+ `--fix` imports supported source families but never chooses a fallback or writes
300
+ an unavailable family. Unknown source IDs, unsupported target families and missing
301
+ or invalid target typography metrics remain unresolved even with an adaptation.
302
+ Omitted families also remain unresolved: renderer defaults differ across surfaces;
303
+ author `$system` explicitly when that is the approved replacement. `--fix` may fill
304
+ omitted families from a supported source while preserving valid numeric metrics.
305
+ Build/font validation still has
242
306
  to prove the font bytes are available. Unresolved fonts and contrast also fail
243
307
  the plain check used by push, not only `--record`.
244
308
  Branded accent text must reach 4.5:1: `textOnAccent` against the fill, and `accentText` against the target page and declared neutral
@@ -578,9 +642,10 @@ Run `himi one-app coverage init --evidence-dir .himi/one-app --content .` to
578
642
  create `ONE-APP-COVERAGE.json` from the verified source graph. After edits or a
579
643
  CLI upgrade, run `coverage sync` and review new rows; `coverage check` fails when
580
644
  the file is absent or source requirements have appeared, disappeared or changed.
581
- Rows cover source graph warnings and app-wide design values as well as routes and
645
+ Rows cover native widget behavior, source graph warnings and app-wide design values as well as routes and
582
646
  controls. This is an inventory of migration obligations, not proof that their target
583
- actions work.
647
+ actions work. For explicit batch target decisions without inventing verification, use the
648
+ [executable ledger mapping recipe](https://apps.wix.com/_api/himi-serve/docs/recipe-one-app-coverage-mapping.md).
584
649
 
585
650
  Preserve the existing app mark after scaffolding: run `himi icon from-one-app
586
651
  --content . --evidence-dir .himi/one-app`. This reads the hash-verified collected
@@ -602,7 +667,7 @@ satisfied by the design check instead. Set its `status` to `supported`, `adapted
602
667
  `unresolved`. For an adaptation, cite the user's instruction and explain the
603
668
  change. Each resolved row needs a `verification` record using the required check
604
669
  ID for its kind (`route-walkthrough`, `visual-comparison`, `behavior-crawl`,
605
- `topbar-control`, `access-state`, `design-token`, or `source-resolution`), a safe relative path to
670
+ `topbar-control`, `access-state`, `design-token`, `source-resolution`, or `native-widget-behavior`), a safe relative path to
606
671
  a JSON check record, and the exact release payload SHA-256 in `releaseHash`.
607
672
  The JSON file's `checks[]` must contain a passed record with that requirement ID,
608
673
  check ID, release hash, target mapping, and a relative `artifact` path to the
@@ -638,6 +703,21 @@ For example, one entry in the referenced JSON file is:
638
703
  The artifact must exist. Review the actual route and state shown in it before
639
704
  recording a pass; a valid record is an index to the check, not a substitute for it.
640
705
 
706
+ For a `native-widget` row use `check: "native-widget-behavior"` and include
707
+ `sourceBaseline` and `targetContext` in that check record. For example:
708
+
709
+ ```json
710
+ {"checks":[{"requirementId":"<widget-row-id>","check":"native-widget-behavior","releaseHash":"<64-hex-sha256>","status":"passed","target":{"screenId":"home"},"artifact":".himi/one-app/device/target.png","sourceBaseline":{"kind":"native-device","artifact":".himi/one-app/device/source.png","siteId":"<same-site-id>","identityContext":"visitor-session-1"},"targetContext":{"siteId":"<same-site-id>","identityContext":"visitor-session-1"}}]}
711
+ ```
712
+
713
+ `identityContext` is a private, opaque label for the compared identity, never a
714
+ credential, email address or session token. Both artifacts must be local safe
715
+ relative paths. The check rejects editor baselines and mismatched site/identity
716
+ labels. It checks record consistency; it cannot establish that screenshots are
717
+ authentic or that the observed behavior matches them. Review the artifacts and
718
+ exercise each effective control, including authored overrides and access states.
719
+ This comparison does not replace the QA APK/native proof below.
720
+
641
721
  After publishing a draft, build a QA APK pinned to that draft release with
642
722
  `himi dist --platform android --release <draft-id> --allow-draft`. Install
643
723
  and walk through every requirement on the device. Save
@@ -0,0 +1,74 @@
1
+ # Recipe: map a One App coverage ledger
2
+
3
+ Use explicit reconstruction decisions to map a large `ONE-APP-COVERAGE.json` without
4
+ rewriting its source-derived IDs. This recipe only assigns targets. It never records a
5
+ passed check, chooses a customer route, or makes an unresolved requirement supported.
6
+
7
+ Run the existing coverage commands first:
8
+
9
+ ```sh
10
+ himi one-app coverage init --evidence-dir .himi/one-app --content .
11
+ # After source recollection or a CLI upgrade, use coverage sync and review its changes.
12
+ himi one-app coverage check --evidence-dir .himi/one-app --content .
13
+ ```
14
+
15
+ The runnable example is installed by `himi init` and refreshed by `himi agent-init` at
16
+ `.agents/skills/himalaya/examples/map-one-app-coverage.js`. It uses the installed
17
+ `himi one-app coverage check` before reading the ledger, so stale hashes, invented rows
18
+ and changed source requirements refuse the operation. It does not execute app workers
19
+ or call Wix. Keep decisions, descriptors and reports under `.himi/`.
20
+
21
+ Write `.himi/one-app/mapping-decisions.json` as an array. Select each row by its exact
22
+ `id`, or by exact `kind`, JSON-pointer `source`, and `platform` when present:
23
+
24
+ ```json
25
+ [
26
+ { "kind": "route", "source": "/screens/source_home", "target": { "screenId": "home" } },
27
+ { "id": "<interaction-row-id-from-the-ledger>", "target": { "screenId": "home", "controlId": "view_products" } }
28
+ ]
29
+ ```
30
+
31
+ Copy source pointers and IDs from the ledger; do not derive them from labels, row
32
+ positions, or guessed routes. An ambiguous source selector requires the row ID. Each
33
+ decision may set only its target, never status, adaptation or verification. A `design`
34
+ row is app-wide and belongs to `validate-design`; leave it out of mapping decisions.
35
+
36
+ Generate `.himi/one-app/mapping-screens.json` from the **same authored descriptor data**
37
+ used by the app generator: `{ "home": { "screenId": "home", "payload": { … } }, … }`.
38
+ Include every authored screen and each screen's effective top bar (its override or the
39
+ actual `navigation.topBarDefault`). Serialize builders at generation time. Do not
40
+ invent a screen/control inventory to make a decision pass. Missing or dynamically
41
+ unresolved IDs stay unmapped. Control-ID presence is only an identity check; a real
42
+ interaction or top-bar check must still prove the behavior.
43
+
44
+ Inspect a dry run first, then write the reviewed mappings:
45
+
46
+ ```sh
47
+ node .agents/skills/himalaya/examples/map-one-app-coverage.js \
48
+ . .himi/one-app .himi/one-app/mapping-decisions.json .himi/one-app/mapping-screens.json \
49
+ > .himi/one-app/mapping-review.json
50
+ # Review coverage, issues, unmapped and needsVerification in that JSON.
51
+ node .agents/skills/himalaya/examples/map-one-app-coverage.js \
52
+ . .himi/one-app .himi/one-app/mapping-decisions.json .himi/one-app/mapping-screens.json --write
53
+ himi one-app coverage check --evidence-dir .himi/one-app --content .
54
+ himi one-app validate-topbar --evidence-dir .himi/one-app --content .
55
+ ```
56
+
57
+ For `--write`, use the physical app directory, without symlinked parent directories;
58
+ the ledger itself must also be a regular file. Running the installed script through a
59
+ symlink is supported. Without `--write` the ledger is unchanged. Exit 1 means there are invalid decisions or
60
+ unmapped rows; the JSON lists them. With `--write`, valid decisions are retained while
61
+ unknown targets, missing controls and duplicate decisions leave their rows unresolved.
62
+ An unknown source/row creates no requirement and is reported in `issues`.
63
+
64
+ A changed mapping loses its old verification and adaptation and becomes `unresolved`.
65
+ An unchanged mapping retains its existing evidence; unrelated valid rows, IDs and source
66
+ hashes are preserved. A target removed from the authored map also loses its old evidence.
67
+ `needsVerification` lists every row: this recipe cannot validate retained proof against
68
+ the current built release. Use `coverage verify --release-hash <sha256>` with the real
69
+ release hash to check it, including unchanged mappings and previously saved records.
70
+ Mapping output is not a readiness verdict. Decide supported/adapted/conditional status
71
+ only after implementing and testing the corresponding behavior, with the required
72
+ artifact and exact release hash. Run `validate-design --fix`, rebuild, then record the
73
+ final design/UX/coverage evidence before pushing; repeat affected records after content
74
+ changes. The main migration guide defines those checks and their limitations.
@@ -23606,6 +23606,12 @@
23606
23606
  "jsdoc": "/** List the signed-in member's notifications, newest first. MOBILE channel. */",
23607
23607
  "decl": "export async function queryNotifications(limit = 25, options?: WixCallOptions): Promise<MfvNotification[]>;"
23608
23608
  },
23609
+ {
23610
+ "name": "countUnseenNotifications",
23611
+ "kind": "function",
23612
+ "jsdoc": "/** Source member-app MOBILE/UNSEEN aggregate, capped at 100. Requires a bound\n * single-site Member transport; old native binaries and web reject. A missing,\n * malformed or failed result is unavailable, never an authoritative zero. */",
23613
+ "decl": "export async function countUnseenNotifications(options: { site: string }): Promise<number>;"
23614
+ },
23609
23615
  {
23610
23616
  "name": "markRead",
23611
23617
  "kind": "function",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wix/himalaya-cli",
3
- "version": "0.1571.0",
3
+ "version": "0.1572.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": "fec01235dfeb9ac45a855684328263dcff0fa8e5ecf05db7d6e9b9da"
74
+ "falconPackageHash": "20229381643935ec0f9390addc404f459d7040e7dfd5b634ab41385c"
75
75
  }
@@ -52829,8 +52829,7 @@ const SCREENS = runtimeSetting({
52829
52829
  "params": {
52830
52830
  "screenId": "himi_owner_lite_search",
52831
52831
  "title": "Search records",
52832
- "subtitle": "Across your business areas",
52833
- "search": "drawer"
52832
+ "subtitle": "Across your business areas"
52834
52833
  }
52835
52834
  },
52836
52835
  "pressFeedback": "scale",
@@ -54349,7 +54348,7 @@ const SCREENS = runtimeSetting({
54349
54348
  "actionId": "tier5.himiOwnerLiteSearch.search"
54350
54349
  },
54351
54350
  "debounceMs": 200,
54352
- "placement": "drawer"
54351
+ "loadingPath": "$service:himiOwnerLiteSearch.anyLoading"
54353
54352
  }
54354
54353
  }
54355
54354
  },
@@ -112,13 +112,10 @@ function programFields(p: WixProgram): Omit<ProgramRow, "priceLine"> {
112
112
  initial: initials(name),
113
113
  hasImage: !!image,
114
114
  noImage: !image,
115
- // The row also binds `id` as a nav param, which `currentInputId` prefers — but no
116
- // shell threads that param into worker `input.id` yet, so a param-less target left
117
- // the detail worker with no id and its Enrol CTA dead on arrival. Same fix (and same
118
- // reasoning) as the events row: bake the id, which the app's own
119
- // liveSeedRoute({ prefix: "himi_program_" }) already seeds and `currentInputId`
120
- // resolves through its prefix path.
121
- detailScreenId: `himi_program_${id}`,
115
+ // The canonical route is baked even offline. Hosts pass the row's `id` as
116
+ // worker input (WS-C); suffixed routes only exist for the preview seed's
117
+ // first 50 records and cannot open a complete live catalog in production.
118
+ detailScreenId: "himi_program",
122
119
  };
123
120
  }
124
121
 
@@ -157,9 +154,29 @@ function errored(state: State): State {
157
154
  };
158
155
  }
159
156
 
160
- async function fetchPrograms(): Promise<WixProgram[]> {
161
- const response = await queryPrograms({ cursorPaging: { limit: 50 } });
162
- return (response.programs ?? []).filter((p) => !isDraft(p));
157
+ async function fetchPrograms(isCurrent: () => boolean): Promise<WixProgram[] | undefined> {
158
+ const programs = new Map<string, WixProgram>();
159
+ const seen = new Set<string>();
160
+ let cursor = "";
161
+ do {
162
+ // Wix cursors carry the initial sort/filter. Repeating either on a continuation
163
+ // is rejected with INVALID_CURSOR, even when their values have not changed.
164
+ const response = await queryPrograms(cursor
165
+ ? { cursorPaging: { limit: 50, cursor } }
166
+ : { cursorPaging: { limit: 50 }, sort: [{ fieldName: "createdDate", order: "DESC" }] });
167
+ if (!isCurrent()) return undefined;
168
+ for (const program of response.programs ?? []) {
169
+ if (program.id && !isDraft(program)) programs.set(program.id, program);
170
+ }
171
+ const paging = response.pagingMetadata;
172
+ const next = paging?.hasNext === false ? "" : paging?.cursors?.next ?? "";
173
+ if ((paging?.hasNext && !next) || (next && seen.has(next))) {
174
+ throw new Error("Invalid program catalog cursor");
175
+ }
176
+ if (next) seen.add(next);
177
+ cursor = next;
178
+ } while (cursor);
179
+ return [...programs.values()];
163
180
  }
164
181
 
165
182
  // ── Content and App Store prices ─────
@@ -180,7 +197,7 @@ let loadSeq = 0;
180
197
  async function load(_state: State, arg?: unknown): Promise<undefined> {
181
198
  stashCopy(arg);
182
199
  const seq = ++loadSeq;
183
- const programs = await fetchPrograms().catch(() => undefined);
200
+ const programs = await fetchPrograms(() => seq === loadSeq).catch(() => undefined);
184
201
  if (seq !== loadSeq) return undefined;
185
202
  content = programs ? { status: "ready", programs: programs.map(programContent) } : { ...content, status: "error" };
186
203
  void svc.pricingPublish();
@@ -52829,8 +52829,7 @@ const SCREENS = runtimeSetting({
52829
52829
  "params": {
52830
52830
  "screenId": "himi_owner_lite_search",
52831
52831
  "title": "Search records",
52832
- "subtitle": "Across your business areas",
52833
- "search": "drawer"
52832
+ "subtitle": "Across your business areas"
52834
52833
  }
52835
52834
  },
52836
52835
  "pressFeedback": "scale",
@@ -54349,7 +54348,7 @@ const SCREENS = runtimeSetting({
54349
54348
  "actionId": "tier5.himiOwnerLiteSearch.search"
54350
54349
  },
54351
54350
  "debounceMs": 200,
54352
- "placement": "drawer"
54351
+ "loadingPath": "$service:himiOwnerLiteSearch.anyLoading"
54353
54352
  }
54354
54353
  }
54355
54354
  },
@@ -52446,8 +52446,7 @@ const SCREENS = runtimeSetting({
52446
52446
  "params": {
52447
52447
  "screenId": "himi_owner_lite_search",
52448
52448
  "title": "Search records",
52449
- "subtitle": "Across your business areas",
52450
- "search": "drawer"
52449
+ "subtitle": "Across your business areas"
52451
52450
  }
52452
52451
  },
52453
52452
  "pressFeedback": "scale",
@@ -53966,7 +53965,7 @@ const SCREENS = runtimeSetting({
53966
53965
  "actionId": "tier5.himiOwnerLiteSearch.search"
53967
53966
  },
53968
53967
  "debounceMs": 200,
53969
- "placement": "drawer"
53968
+ "loadingPath": "$service:himiOwnerLiteSearch.anyLoading"
53970
53969
  }
53971
53970
  }
53972
53971
  },
@@ -52905,8 +52905,7 @@ const SCREENS = runtimeSetting({
52905
52905
  "params": {
52906
52906
  "screenId": "himi_owner_lite_search",
52907
52907
  "title": "Search records",
52908
- "subtitle": "Across your business areas",
52909
- "search": "drawer"
52908
+ "subtitle": "Across your business areas"
52910
52909
  }
52911
52910
  },
52912
52911
  "pressFeedback": "scale",
@@ -54425,7 +54424,7 @@ const SCREENS = runtimeSetting({
54425
54424
  "actionId": "tier5.himiOwnerLiteSearch.search"
54426
54425
  },
54427
54426
  "debounceMs": 200,
54428
- "placement": "drawer"
54427
+ "loadingPath": "$service:himiOwnerLiteSearch.anyLoading"
54429
54428
  }
54430
54429
  }
54431
54430
  },
@@ -52522,8 +52522,7 @@ const SCREENS = runtimeSetting({
52522
52522
  "params": {
52523
52523
  "screenId": "himi_owner_lite_search",
52524
52524
  "title": "Search records",
52525
- "subtitle": "Across your business areas",
52526
- "search": "drawer"
52525
+ "subtitle": "Across your business areas"
52527
52526
  }
52528
52527
  },
52529
52528
  "pressFeedback": "scale",
@@ -54042,7 +54041,7 @@ const SCREENS = runtimeSetting({
54042
54041
  "actionId": "tier5.himiOwnerLiteSearch.search"
54043
54042
  },
54044
54043
  "debounceMs": 200,
54045
- "placement": "drawer"
54044
+ "loadingPath": "$service:himiOwnerLiteSearch.anyLoading"
54046
54045
  }
54047
54046
  }
54048
54047
  },
@@ -52904,8 +52904,7 @@ const SCREENS = runtimeSetting({
52904
52904
  "params": {
52905
52905
  "screenId": "himi_owner_lite_search",
52906
52906
  "title": "Search records",
52907
- "subtitle": "Across your business areas",
52908
- "search": "drawer"
52907
+ "subtitle": "Across your business areas"
52909
52908
  }
52910
52909
  },
52911
52910
  "pressFeedback": "scale",
@@ -54422,7 +54421,7 @@ const SCREENS = runtimeSetting({
54422
54421
  "actionId": "tier5.himiOwnerLiteSearch.search"
54423
54422
  },
54424
54423
  "debounceMs": 200,
54425
- "placement": "drawer"
54424
+ "loadingPath": "$service:himiOwnerLiteSearch.anyLoading"
54426
54425
  }
54427
54426
  }
54428
54427
  },
@@ -52829,8 +52829,7 @@ const SCREENS = runtimeSetting({
52829
52829
  "params": {
52830
52830
  "screenId": "himi_owner_lite_search",
52831
52831
  "title": "Search records",
52832
- "subtitle": "Across your business areas",
52833
- "search": "drawer"
52832
+ "subtitle": "Across your business areas"
52834
52833
  }
52835
52834
  },
52836
52835
  "pressFeedback": "scale",
@@ -54349,7 +54348,7 @@ const SCREENS = runtimeSetting({
54349
54348
  "actionId": "tier5.himiOwnerLiteSearch.search"
54350
54349
  },
54351
54350
  "debounceMs": 200,
54352
- "placement": "drawer"
54351
+ "loadingPath": "$service:himiOwnerLiteSearch.anyLoading"
54353
54352
  }
54354
54353
  }
54355
54354
  },
@@ -52829,8 +52829,7 @@ const SCREENS = runtimeSetting({
52829
52829
  "params": {
52830
52830
  "screenId": "himi_owner_lite_search",
52831
52831
  "title": "Search records",
52832
- "subtitle": "Across your business areas",
52833
- "search": "drawer"
52832
+ "subtitle": "Across your business areas"
52834
52833
  }
52835
52834
  },
52836
52835
  "pressFeedback": "scale",
@@ -54349,7 +54348,7 @@ const SCREENS = runtimeSetting({
54349
54348
  "actionId": "tier5.himiOwnerLiteSearch.search"
54350
54349
  },
54351
54350
  "debounceMs": 200,
54352
- "placement": "drawer"
54351
+ "loadingPath": "$service:himiOwnerLiteSearch.anyLoading"
54353
54352
  }
54354
54353
  }
54355
54354
  },
@@ -52905,8 +52905,7 @@ const SCREENS = runtimeSetting({
52905
52905
  "params": {
52906
52906
  "screenId": "himi_owner_lite_search",
52907
52907
  "title": "Search records",
52908
- "subtitle": "Across your business areas",
52909
- "search": "drawer"
52908
+ "subtitle": "Across your business areas"
52910
52909
  }
52911
52910
  },
52912
52911
  "pressFeedback": "scale",
@@ -54425,7 +54424,7 @@ const SCREENS = runtimeSetting({
54425
54424
  "actionId": "tier5.himiOwnerLiteSearch.search"
54426
54425
  },
54427
54426
  "debounceMs": 200,
54428
- "placement": "drawer"
54427
+ "loadingPath": "$service:himiOwnerLiteSearch.anyLoading"
54429
54428
  }
54430
54429
  }
54431
54430
  },
@@ -52741,8 +52741,7 @@ const SCREENS = runtimeSetting({
52741
52741
  "params": {
52742
52742
  "screenId": "himi_owner_lite_search",
52743
52743
  "title": "Search records",
52744
- "subtitle": "Across your business areas",
52745
- "search": "drawer"
52744
+ "subtitle": "Across your business areas"
52746
52745
  }
52747
52746
  },
52748
52747
  "pressFeedback": "scale",
@@ -54261,7 +54260,7 @@ const SCREENS = runtimeSetting({
54261
54260
  "actionId": "tier5.himiOwnerLiteSearch.search"
54262
54261
  },
54263
54262
  "debounceMs": 200,
54264
- "placement": "drawer"
54263
+ "loadingPath": "$service:himiOwnerLiteSearch.anyLoading"
54265
54264
  }
54266
54265
  }
54267
54266
  },