@wix/himalaya-cli 0.1571.0 → 0.1573.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/dev-server/src/app-module.d.ts +2 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/flows/checkout/src/index.d.ts +4 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/flows/iap-checkout/src/apple-iap-contract.d.ts +1 -1
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/flows/iap-checkout/src/commerce-contract.d.ts +2 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/flows/screens/src/index.d.ts +1 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/flows/screens/src/one-app-page.d.ts +61 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/members-feed-viewer/src/index.d.ts +6 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/social-feed/src/index.d.ts +34 -9
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/checkout.mjs +28 -3
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/iap-checkout.mjs +2 -1
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/screens.mjs +573 -73
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-members-feed-viewer.mjs +18 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-social-feed.mjs +24 -1
- package/dist/cli.mjs +466 -90
- package/dist/preview-assets.generated.bin +0 -0
- package/guides/index.json +7 -0
- package/guides/map-one-app-coverage.js +117 -0
- package/guides/one-app-to-himi-app.md +141 -7
- package/guides/recipe-one-app-coverage-mapping.md +74 -0
- package/guides/sdk-reference.json +34 -4
- package/guides/webview-page-sdk.md +68 -3
- package/package.json +2 -2
- package/templates/beauty-salon/tier5-src/_shared/iap-worker.ts +2 -0
- package/templates/beauty-salon/tier5-src/_shared/payment-setup.ts +6 -0
- package/templates/beauty-salon/tier5-src/himi-booking-time/index.ts +5 -2
- package/templates/beauty-salon/tier5-src/himi-branded-lite-cart/index.ts +5 -2
- package/templates/beauty-salon/tier5-src/himi-branded-lite-plans/index.ts +5 -2
- package/templates/beauty-salon-owner/dev/index.ts +2 -3
- package/templates/branded-lite/tier5-src/_shared/iap-worker.ts +2 -0
- package/templates/branded-lite/tier5-src/_shared/payment-setup.ts +6 -0
- package/templates/branded-lite/tier5-src/himi-booking-time/index.ts +5 -2
- package/templates/branded-lite/tier5-src/himi-branded-lite-cart/index.ts +5 -2
- package/templates/branded-lite/tier5-src/himi-branded-lite-plans/index.ts +5 -2
- package/templates/branded-lite/tier5-src/himi-branded-lite-program/index.ts +5 -2
- package/templates/branded-lite/tier5-src/himi-branded-lite-programs/index.ts +28 -11
- package/templates/branded-lite/tier5-src/himi-events-detail/index.ts +9 -4
- package/templates/community-club/tier5-src/_shared/iap-worker.ts +2 -0
- package/templates/community-club/tier5-src/_shared/payment-setup.ts +6 -0
- package/templates/community-club/tier5-src/himi-events-detail/index.ts +9 -4
- package/templates/community-club-owner/dev/index.ts +2 -3
- package/templates/events-meetups/tier5-src/_shared/iap-worker.ts +2 -0
- package/templates/events-meetups/tier5-src/_shared/payment-setup.ts +6 -0
- package/templates/events-meetups/tier5-src/himi-events-detail/index.ts +9 -4
- package/templates/events-meetups-owner/dev/index.ts +2 -3
- package/templates/gym-fitness/tier5-src/_shared/iap-worker.ts +2 -0
- package/templates/gym-fitness/tier5-src/_shared/payment-setup.ts +6 -0
- package/templates/gym-fitness/tier5-src/himi-booking-time/index.ts +5 -2
- package/templates/gym-fitness/tier5-src/himi-branded-lite-cart/index.ts +5 -2
- package/templates/gym-fitness/tier5-src/himi-branded-lite-plans/index.ts +5 -2
- package/templates/gym-fitness/tier5-src/himi-events-detail/index.ts +9 -4
- package/templates/gym-fitness-owner/dev/index.ts +2 -3
- package/templates/nonprofit-cause/tier5-src/_shared/iap-worker.ts +2 -0
- package/templates/nonprofit-cause/tier5-src/_shared/payment-setup.ts +6 -0
- package/templates/nonprofit-cause/tier5-src/himi-branded-lite-plans/index.ts +5 -2
- package/templates/nonprofit-cause/tier5-src/himi-events-detail/index.ts +9 -4
- package/templates/nonprofit-cause-owner/dev/index.ts +2 -3
- package/templates/owner-lite/dev/index.ts +2 -3
- package/templates/podcast-creator/tier5-src/_shared/iap-worker.ts +2 -0
- package/templates/podcast-creator/tier5-src/_shared/payment-setup.ts +6 -0
- package/templates/podcast-creator/tier5-src/himi-branded-lite-plans/index.ts +5 -2
- package/templates/podcast-creator/tier5-src/himi-events-detail/index.ts +9 -4
- package/templates/podcast-creator-owner/dev/index.ts +2 -3
- package/templates/portfolio-creative/tier5-src/_shared/iap-worker.ts +2 -0
- package/templates/portfolio-creative/tier5-src/_shared/payment-setup.ts +6 -0
- package/templates/portfolio-creative/tier5-src/himi-branded-lite-cart/index.ts +5 -2
- package/templates/portfolio-creative/tier5-src/himi-events-detail/index.ts +9 -4
- package/templates/portfolio-creative-owner/dev/index.ts +2 -3
- package/templates/restaurant/tier5-src/_shared/iap-worker.ts +2 -0
- package/templates/restaurant/tier5-src/_shared/payment-setup.ts +6 -0
- package/templates/restaurant/tier5-src/himi-events-detail/index.ts +9 -4
- package/templates/restaurant-owner/dev/index.ts +2 -3
- package/templates/retail-store/tier5-src/_shared/iap-worker.ts +2 -0
- package/templates/retail-store/tier5-src/_shared/payment-setup.ts +6 -0
- package/templates/retail-store/tier5-src/himi-branded-lite-cart/index.ts +5 -2
- package/templates/retail-store-owner/dev/index.ts +2 -3
- package/templates/yoga-studio/tier5-src/_shared/iap-worker.ts +2 -0
- package/templates/yoga-studio/tier5-src/_shared/payment-setup.ts +6 -0
- package/templates/yoga-studio/tier5-src/himi-booking-time/index.ts +5 -2
- package/templates/yoga-studio/tier5-src/himi-branded-lite-plans/index.ts +5 -2
- package/templates/yoga-studio/tier5-src/himi-branded-lite-program/index.ts +5 -2
- package/templates/yoga-studio/tier5-src/himi-branded-lite-programs/index.ts +28 -11
- package/templates/yoga-studio/tier5-src/himi-events-detail/index.ts +9 -4
- package/templates/yoga-studio-owner/dev/index.ts +2 -3
|
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.
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
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
|
|
@@ -437,6 +501,53 @@ mapping rows are research leads: inspect the current CLI's component, SDK and
|
|
|
437
501
|
template contracts before using them. A previously empty row does not prove the
|
|
438
502
|
feature is impossible today.
|
|
439
503
|
|
|
504
|
+
### Standalone launch tab
|
|
505
|
+
|
|
506
|
+
When the reconstructed navigation has a tab bar, preserve the app's actual tab
|
|
507
|
+
IDs, source order, labels, icons and content. Do not keep the starter template's
|
|
508
|
+
`home` ID or rename the first tab to `home` merely to match a shell default.
|
|
509
|
+
An actual source tab named `home` is valid; the name is not reserved or required.
|
|
510
|
+
A tab ID is not a `screenId`: a Community tab can use `id: "community"` with
|
|
511
|
+
`screenId: "community_home"` and its visible Community label.
|
|
512
|
+
For navigation with no tab bar, do not add a synthetic Home tab.
|
|
513
|
+
|
|
514
|
+
Validate that tab IDs are non-empty and unique. Keep the source-to-target lookup,
|
|
515
|
+
every `tabId` in
|
|
516
|
+
`navigation.linkableScreens`, tab-selection actions and worker navigation requests
|
|
517
|
+
consistent with the actual destination IDs, including detail screens that belong
|
|
518
|
+
to the first tab. Remove stale template references instead of renaming the app's
|
|
519
|
+
tabs to make them match. `himi validate --strict` rejects empty or duplicate tab
|
|
520
|
+
IDs and linkable `tabId` references outside the selectable primary navigation
|
|
521
|
+
on enabled surfaces when chrome lists are configured. An auxiliary drawer row is
|
|
522
|
+
not a compact responsive tab: use an empty/default-stack `tabId` for a pushed
|
|
523
|
+
screen without an owning tab. Explicit primary drawer/sidebar modes and declared
|
|
524
|
+
surface overrides use their own destinations. This config-only membership check
|
|
525
|
+
does not prove source fidelity, dynamic navigation requests, or deep-link behavior
|
|
526
|
+
on every surface; verify those independently. For example,
|
|
527
|
+
the navigation fragment for a Community-first app is:
|
|
528
|
+
|
|
529
|
+
```json
|
|
530
|
+
{
|
|
531
|
+
"tabBar": {
|
|
532
|
+
"items": [
|
|
533
|
+
{ "id": "community", "screenId": "community_home", "label": "Community", "icon": "people" },
|
|
534
|
+
{ "id": "profile", "screenId": "member_profile", "label": "Profile", "icon": "person" }
|
|
535
|
+
]
|
|
536
|
+
},
|
|
537
|
+
"linkableScreens": [
|
|
538
|
+
{ "screenId": "community_home", "isPublic": true, "paramKeys": [], "parentScreenId": "", "tabId": "community" },
|
|
539
|
+
{ "screenId": "community_post", "isPublic": true, "paramKeys": ["id"], "parentScreenId": "community_home", "tabId": "community" }
|
|
540
|
+
]
|
|
541
|
+
}
|
|
542
|
+
```
|
|
543
|
+
|
|
544
|
+
Current standalone bottom-tab chrome selects the first visible configured tab
|
|
545
|
+
when its stored selection is unknown. Do not infer this fallback for sidebar or
|
|
546
|
+
responsive-wide chrome: verify its launch selection independently. A stored
|
|
547
|
+
template default such as `home` must not override the app's navigation or leave
|
|
548
|
+
it blank. If a binary does not handle the app's actual IDs, fix or update that
|
|
549
|
+
shell and report the incompatibility; do not disguise it by renaming the first tab.
|
|
550
|
+
|
|
440
551
|
## 2. Write the reconstruction plan and resolve gaps
|
|
441
552
|
|
|
442
553
|
Write `.himi/one-app/reconstruction-plan.md` before scaffolding. Describe the app
|
|
@@ -578,9 +689,10 @@ Run `himi one-app coverage init --evidence-dir .himi/one-app --content .` to
|
|
|
578
689
|
create `ONE-APP-COVERAGE.json` from the verified source graph. After edits or a
|
|
579
690
|
CLI upgrade, run `coverage sync` and review new rows; `coverage check` fails when
|
|
580
691
|
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
|
|
692
|
+
Rows cover native widget behavior, source graph warnings and app-wide design values as well as routes and
|
|
582
693
|
controls. This is an inventory of migration obligations, not proof that their target
|
|
583
|
-
actions work.
|
|
694
|
+
actions work. For explicit batch target decisions without inventing verification, use the
|
|
695
|
+
[executable ledger mapping recipe](https://apps.wix.com/_api/himi-serve/docs/recipe-one-app-coverage-mapping.md).
|
|
584
696
|
|
|
585
697
|
Preserve the existing app mark after scaffolding: run `himi icon from-one-app
|
|
586
698
|
--content . --evidence-dir .himi/one-app`. This reads the hash-verified collected
|
|
@@ -602,7 +714,7 @@ satisfied by the design check instead. Set its `status` to `supported`, `adapted
|
|
|
602
714
|
`unresolved`. For an adaptation, cite the user's instruction and explain the
|
|
603
715
|
change. Each resolved row needs a `verification` record using the required check
|
|
604
716
|
ID for its kind (`route-walkthrough`, `visual-comparison`, `behavior-crawl`,
|
|
605
|
-
`topbar-control`, `access-state`, `design-token`,
|
|
717
|
+
`topbar-control`, `access-state`, `design-token`, `source-resolution`, or `native-widget-behavior`), a safe relative path to
|
|
606
718
|
a JSON check record, and the exact release payload SHA-256 in `releaseHash`.
|
|
607
719
|
The JSON file's `checks[]` must contain a passed record with that requirement ID,
|
|
608
720
|
check ID, release hash, target mapping, and a relative `artifact` path to the
|
|
@@ -638,6 +750,21 @@ For example, one entry in the referenced JSON file is:
|
|
|
638
750
|
The artifact must exist. Review the actual route and state shown in it before
|
|
639
751
|
recording a pass; a valid record is an index to the check, not a substitute for it.
|
|
640
752
|
|
|
753
|
+
For a `native-widget` row use `check: "native-widget-behavior"` and include
|
|
754
|
+
`sourceBaseline` and `targetContext` in that check record. For example:
|
|
755
|
+
|
|
756
|
+
```json
|
|
757
|
+
{"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"}}]}
|
|
758
|
+
```
|
|
759
|
+
|
|
760
|
+
`identityContext` is a private, opaque label for the compared identity, never a
|
|
761
|
+
credential, email address or session token. Both artifacts must be local safe
|
|
762
|
+
relative paths. The check rejects editor baselines and mismatched site/identity
|
|
763
|
+
labels. It checks record consistency; it cannot establish that screenshots are
|
|
764
|
+
authentic or that the observed behavior matches them. Review the artifacts and
|
|
765
|
+
exercise each effective control, including authored overrides and access states.
|
|
766
|
+
This comparison does not replace the QA APK/native proof below.
|
|
767
|
+
|
|
641
768
|
After publishing a draft, build a QA APK pinned to that draft release with
|
|
642
769
|
`himi dist --platform android --release <draft-id> --allow-draft`. Install
|
|
643
770
|
and walk through every requirement on the device. Save
|
|
@@ -851,6 +978,13 @@ transactions against fixtures only. Read-only live checks verify the correct sit
|
|
|
851
978
|
entity selection and runtime identity; they do not authorize placing orders,
|
|
852
979
|
bookings, payments, form submissions or destructive updates during validation.
|
|
853
980
|
|
|
981
|
+
For a tabbed migration, cold-launch the standalone app without a launch-screen
|
|
982
|
+
override or incoming deep link. Confirm the first tab is selected and displays
|
|
983
|
+
content. Then switch tabs and open a detail link belonging to the first tab;
|
|
984
|
+
confirm it selects that tab and opens the intended screen. A hosted preview alone
|
|
985
|
+
does not establish standalone launch behavior. Record an unavailable native check
|
|
986
|
+
as unverified in the migration report.
|
|
987
|
+
|
|
854
988
|
The UX audit requires app-specific network fixtures. `himi test --record` records
|
|
855
989
|
only boot-time reads with the app's visitor identity, then crawls hermetically.
|
|
856
990
|
Review its recorded/refused requests; do not run a source getter known to mutate
|
|
@@ -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.
|
|
@@ -2771,7 +2771,7 @@
|
|
|
2771
2771
|
"name": "OrderFailureCode",
|
|
2772
2772
|
"kind": "type",
|
|
2773
2773
|
"jsdoc": "",
|
|
2774
|
-
"decl": "export type OrderFailureCode =\n | \"notEnabled\" | \"notConfigured\" | \"signInRequired\" | \"unsupportedItem\" | \"notAppleRoute\"\n | \"storageUnavailable\" | \"approvalRequired\" | \"orderClosed\" | \"orderRefused\" | \"chargeFailed\"\n | \"paymentRefused\" | \"paymentHeld\" | \"alreadyOwned\" | \"paymentInProgress\" | \"productChanged\"\n | \"invalidAssociation\" | \"needsSupport\" | \"unavailable\" | \"sessionChanged\" | \"outcomeUnknown\";"
|
|
2774
|
+
"decl": "export type OrderFailureCode =\n | \"notEnabled\" | \"notConfigured\" | \"signInRequired\" | \"unsupportedItem\" | \"notAppleRoute\"\n | \"storageUnavailable\" | \"approvalRequired\" | \"orderClosed\" | \"orderRefused\" | \"chargeFailed\"\n | \"paymentRefused\" | \"paymentHeld\" | \"alreadyOwned\" | \"paymentInProgress\" | \"productChanged\"\n | \"sitePaymentUnavailable\" | \"invalidAssociation\" | \"needsSupport\" | \"unavailable\" | \"sessionChanged\" | \"outcomeUnknown\";"
|
|
2775
2775
|
},
|
|
2776
2776
|
{
|
|
2777
2777
|
"name": "PaymentAccess",
|
|
@@ -2849,7 +2849,7 @@
|
|
|
2849
2849
|
"name": "IapPurchaseCopy",
|
|
2850
2850
|
"kind": "interface",
|
|
2851
2851
|
"jsdoc": "/** Labels from the screen's copy pack; the shared layer only chooses between them. */",
|
|
2852
|
-
"decl": "export interface IapPurchaseCopy {\n buy: string;\n buyAnother?: string;\n processing: string;\n awaitingApproval: string;\n unavailable: string;\n signInRequired: string;\n retry: string;\n priceChanged: string;\n support: string;\n}"
|
|
2852
|
+
"decl": "export interface IapPurchaseCopy {\n buy: string;\n buyAnother?: string;\n processing: string;\n awaitingApproval: string;\n unavailable: string;\n signInRequired: string;\n retry: string;\n /** Optional for existing SDK consumers; the app supplies a localized recovery message. */\n sitePaymentUnavailable?: string;\n priceChanged: string;\n support: string;\n}"
|
|
2853
2853
|
},
|
|
2854
2854
|
{
|
|
2855
2855
|
"name": "IapPurchaseFields",
|
|
@@ -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",
|
|
@@ -25554,13 +25560,37 @@
|
|
|
25554
25560
|
"jsdoc": "",
|
|
25555
25561
|
"decl": "export interface WixListFeedItemsResponse {\n feedItems: WixFeedItem[];\n profiles?: WixFeedProfile[];\n nextPageCursor?: string;\n}"
|
|
25556
25562
|
},
|
|
25563
|
+
{
|
|
25564
|
+
"name": "WixFeedPageOptions",
|
|
25565
|
+
"kind": "interface",
|
|
25566
|
+
"jsdoc": "/** Read controls shared by the source's single-group and central feeds. */",
|
|
25567
|
+
"decl": "export interface WixFeedPageOptions extends WixCallOptions {\n /** Opaque server cursor; pass it back unchanged when loading the next page. */\n cursor?: string;\n invalidatePermissionsCache?: boolean;\n}"
|
|
25568
|
+
},
|
|
25569
|
+
{
|
|
25570
|
+
"name": "WixGroupFeedOptions",
|
|
25571
|
+
"kind": "interface",
|
|
25572
|
+
"jsdoc": "",
|
|
25573
|
+
"decl": "export interface WixGroupFeedOptions extends WixFeedPageOptions {\n topicId?: string;\n}"
|
|
25574
|
+
},
|
|
25575
|
+
{
|
|
25576
|
+
"name": "WixCentralFeedOptions",
|
|
25577
|
+
"kind": "interface",
|
|
25578
|
+
"jsdoc": "",
|
|
25579
|
+
"decl": "export interface WixCentralFeedOptions extends WixFeedPageOptions {\n withSuggestedPosts?: boolean;\n /** Omit to use the central-feed service's default representation. */\n contentType?: \"DRAFTJS\";\n}"
|
|
25580
|
+
},
|
|
25557
25581
|
{
|
|
25558
25582
|
"name": "listFeedItems",
|
|
25559
25583
|
"kind": "function",
|
|
25560
|
-
"jsdoc": "/**\n * List
|
|
25561
|
-
"decl": "export function listFeedItems(groupId: string, limit = 20, options?:
|
|
25584
|
+
"jsdoc": "/**\n * List one group's activity feed. Mirrors wix-one-app-feed getFeedPosts:\n * containerId + Groups appDefId + feedType=all + contentType=DRAFTJS, with optional\n * topic and opaque paging cursor. Existing site/cache options remain per-call.\n * Reactions lists must not be treated as authoritative totals.\n *\n * @scope member — verified live with a signed-in member session; visitor readability\n * depends on group permissions and is not established by the central-feed probe.\n */",
|
|
25585
|
+
"decl": "export function listFeedItems(groupId: string, limit = 20, options?: WixGroupFeedOptions): Promise<WixListFeedItemsResponse>;",
|
|
25562
25586
|
"scope": "member"
|
|
25563
25587
|
},
|
|
25588
|
+
{
|
|
25589
|
+
"name": "listCentralFeedItems",
|
|
25590
|
+
"kind": "function",
|
|
25591
|
+
"jsdoc": "/**\n * List the site's combined feed, rather than substituting a selected group's feed.\n * Mirrors wix-one-app-feed getCentralFeedPosts: there is no containerId or appDefId.\n * Authentication and business selection belong to the bridge (or per-call `site`).\n *\n * Source: wix-one-app-feed@6ef53170e94e27349d2a6c1cf08038b078d86228,\n * src/services/feed-api.ts. A real Visitor read returned 200 with posts and a cursor.\n * That establishes the server contract only: native consumers need a core release with the exact read-route guard, and\n * production web still refuses social.wix.com. This SDK method does\n * not change those policies or establish membership/write permission. Consumers must\n * surface a transport refusal; they must not substitute a group directory or empty feed.\n */",
|
|
25592
|
+
"decl": "export function listCentralFeedItems(limit = 20, options?: WixCentralFeedOptions): Promise<WixListFeedItemsResponse>;"
|
|
25593
|
+
},
|
|
25564
25594
|
{
|
|
25565
25595
|
"name": "createFeedItem",
|
|
25566
25596
|
"kind": "function",
|
|
@@ -94,7 +94,28 @@ every message none of them claims, including a handler registered directly on
|
|
|
94
94
|
`himalaya.webview.onPageMessage` after `@himalaya/webview` loaded).
|
|
95
95
|
|
|
96
96
|
```ts
|
|
97
|
-
import {
|
|
97
|
+
import { defineService } from "@himalaya/state";
|
|
98
|
+
import { emitToPage, onPageLifecycle, onPageRequest, openPageStream, type PageStream } from "@himalaya/webview";
|
|
99
|
+
|
|
100
|
+
// The worker has NO setTimeout / setInterval on any renderer. A stream that produces over time
|
|
101
|
+
// is paced by the host clock: an action returns a `_poll`, and each tick sends the next chunk.
|
|
102
|
+
const watching = new Set<PageStream>();
|
|
103
|
+
let armed = false; // re-arming a live id with `immediate` fires an extra tick at once — arm once
|
|
104
|
+
const steps = defineService("steps", {
|
|
105
|
+
state: {},
|
|
106
|
+
actions: {
|
|
107
|
+
arm: (s) => ({ ...s, _poll: { id: "steps", intervalMs: 1000, action: "steps.tick", immediate: true } }),
|
|
108
|
+
tick: (s) => {
|
|
109
|
+
for (const stream of watching) {
|
|
110
|
+
if (stream.closed) watching.delete(stream); // ended, or the page broke out of its loop
|
|
111
|
+
else stream.next({ steps: Math.random() });
|
|
112
|
+
}
|
|
113
|
+
if (watching.size) return s;
|
|
114
|
+
armed = false;
|
|
115
|
+
return { ...s, _poll: { id: "steps", stop: true } };
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
});
|
|
98
119
|
|
|
99
120
|
onPageRequest("nano", {
|
|
100
121
|
// (args, ctx) → value | promise. ctx = { instanceId, requestId, active }.
|
|
@@ -102,8 +123,8 @@ onPageRequest("nano", {
|
|
|
102
123
|
// Return a stream handle to answer with many chunks.
|
|
103
124
|
"steps.watch": (_args, ctx) => {
|
|
104
125
|
const stream = openPageStream(ctx.instanceId, ctx.requestId);
|
|
105
|
-
|
|
106
|
-
|
|
126
|
+
watching.add(stream);
|
|
127
|
+
if (!armed) { armed = true; void steps.arm(); }
|
|
107
128
|
return stream;
|
|
108
129
|
},
|
|
109
130
|
});
|
|
@@ -112,6 +133,9 @@ emitToPage("*", "nano.theme.changed", { dark: true }); // or one instance id
|
|
|
112
133
|
const off = onPageLifecycle(({ instanceId, state }) => console.log(instanceId, state)); // mounted … unmounted
|
|
113
134
|
```
|
|
114
135
|
|
|
136
|
+
A timer in a worker is not a style choice: `setInterval` here throws `Can't find variable` on the
|
|
137
|
+
device, and the page sees the request fail (`_poll`: [Tier-5 guide](tier-5-add-a-screen.md#refresh-on-a-cadence-with-_poll-the-worker-has-no-clock)).
|
|
138
|
+
|
|
115
139
|
- `ctx.instanceId` is the WebView the **host** says the request came from; a page-sent `iid` is
|
|
116
140
|
ignored. `ctx.active` is live: `false` between `inactive` and `active` lifecycle events.
|
|
117
141
|
- A namespace — and an exposed capability id — follows the capability-id pattern
|
|
@@ -255,3 +279,44 @@ The page is served at `/web/webview-sdk.html`. Its companion worker is
|
|
|
255
279
|
probes. Safe probes run on load.
|
|
256
280
|
Verbs that prompt, write, or open system UI stay allowlisted and are not called. Streaming
|
|
257
281
|
helpers that return a live object stay on the worker.
|
|
282
|
+
|
|
283
|
+
## Embedding worker-bridged WebViews
|
|
284
|
+
|
|
285
|
+
A Tier-5 SDUI screen embeds any number of `WebView` nodes that talk to the **owning screen's
|
|
286
|
+
worker** over the same channel: set `bridge: "worker"` on the node, declare `"webview"`
|
|
287
|
+
in the screen's `logic_bundle`, and give each widget an explicit `instance_id` (inside a
|
|
288
|
+
`LazyList`/`Carousel` item template it must be item-derived, e.g. `"row-$item.id"`). Untrusted or
|
|
289
|
+
LLM-generated inline HTML additionally wants `origin_isolation: "per_instance"` (its own origin +
|
|
290
|
+
storage per widget), `frame_policy: "content"` (top frame pinned, bridge main-frame-only),
|
|
291
|
+
`bridge_security: { profile: "app_content" }` (multi-line-friendly caps) and `console_forwarding`.
|
|
292
|
+
The host injects the page client at document start, so the page imports nothing; an app alias
|
|
293
|
+
(`inject_js_document_start: "window.demoSdk = himalaya.demo;"`) runs after it, and the platform
|
|
294
|
+
knows no app names. On web (phase 1), only an app-controlled **cross-origin `url`** page listed in
|
|
295
|
+
`allowed_origins` is bridged; an inline (`srcdoc`) widget stays unbridged and the worker hears
|
|
296
|
+
`mounted` with `detail: { bridged: false, reason: "web_srcdoc_unbridged" }` — show a fallback.
|
|
297
|
+
|
|
298
|
+
A complete shape to copy — the demo app's **Embedded widgets** screen (`demo_app_embedded_widgets`)
|
|
299
|
+
is built exactly this way:
|
|
300
|
+
|
|
301
|
+
- three isolated inline widgets in a `Carousel` with `live_window: 1`, so only the current widget
|
|
302
|
+
and its neighbours are mounted (swiping to widget 3 unmounts widget 1), plus one bridged
|
|
303
|
+
cross-origin `url` page — all served by **one** worker;
|
|
304
|
+
- the worker answers `onPageRequest("demo", { echo, bump, stream })`: a request, a per-instance
|
|
305
|
+
counter that answers with `emitToPage(ctx.instanceId, "demo.tick", n)`, and a 5-chunk stream the
|
|
306
|
+
page can stop (`break` out of its loop), paced by a host `_poll` as above;
|
|
307
|
+
- `muted_path`, `active_path` and one widget's `reload_key_path` bound to worker state, and an
|
|
308
|
+
`onPageLifecycle` handler that shows each widget's last state;
|
|
309
|
+
- each widget keeps its own `localStorage` counter, which survives a relaunch and is never visible
|
|
310
|
+
to its neighbours.
|
|
311
|
+
|
|
312
|
+
Test such a screen by the document, not only by lifecycle envelopes: put state in a widget's page
|
|
313
|
+
(an echo reply), swipe away and back, and check the state survived where the widget should have
|
|
314
|
+
stayed mounted. **Crash recovery:** when a widget's content process dies, the host sends `crashed`
|
|
315
|
+
and then remounts it (`reloaded`). On iOS and macOS each widget has its own content process, so the
|
|
316
|
+
page exhausting memory (the demo's "Crash widget 3" does) kills only that widget; killing the
|
|
317
|
+
`com.apple.WebKit.WebContent` process serving it by hand on a simulator triggers the same two
|
|
318
|
+
envelopes. **On Android, don't use a memory hog:** one renderer process serves every WebView of the
|
|
319
|
+
app — the screen's own worker included — so the hog takes them all down, and repeated renderer
|
|
320
|
+
crashes exhaust the worker's recovery budget. Trigger it by hand instead: kill the app's sandboxed
|
|
321
|
+
WebView process (`adb shell ps -A | grep sandboxed_process`, then `adb shell kill <pid>`) and watch
|
|
322
|
+
for `crashed` then `reloaded`.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wix/himalaya-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.1573.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "himi — the Himalaya authoring CLI. Build a Tier-5 content package on your own machine, push it to the production serve as a draft, preview it on web + Himi Player, and read worker logs back — the remote dev loop. Wraps @himalaya/serve-cli for transport.",
|
|
6
6
|
"keywords": [
|
|
@@ -71,5 +71,5 @@
|
|
|
71
71
|
"artifactId": "himalaya-cli"
|
|
72
72
|
}
|
|
73
73
|
},
|
|
74
|
-
"falconPackageHash": "
|
|
74
|
+
"falconPackageHash": "39a44e1a057f7d34899d550863397025bfdec7b4f241a6e45b677246"
|
|
75
75
|
}
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
// SDK; this only supplies the copy and the hydration reset.
|
|
5
5
|
import { hostSessionFromGlobal, type IapPriceCopy } from "@himalaya/iap";
|
|
6
6
|
import type { IapCommerce, IapPurchaseCopy, IapRestoreResult } from "@himalaya/iap-checkout";
|
|
7
|
+
import { paymentSetupMessage } from "./payment-setup.js";
|
|
7
8
|
import { copyParam, copyTemplate } from "./himi-copy.js";
|
|
8
9
|
|
|
9
10
|
/** Price labels from the screen's copy pack (`<prefix>.priceLoadingText`, …). */
|
|
@@ -25,6 +26,7 @@ export function iapPurchaseCopy(prefix: string): IapPurchaseCopy {
|
|
|
25
26
|
awaitingApproval: copyParam(`${prefix}.purchaseAwaitingApprovalText`, "Waiting for approval"),
|
|
26
27
|
unavailable: copyParam(`${prefix}.purchaseTemporarilyUnavailableText`, "Purchasing is temporarily unavailable."),
|
|
27
28
|
signInRequired: copyParam(`${prefix}.purchaseSignInText`, "Sign in to buy."),
|
|
29
|
+
sitePaymentUnavailable: paymentSetupMessage(prefix),
|
|
28
30
|
retry: copyParam(`${prefix}.purchaseRetryText`, "Something went wrong. Try again."),
|
|
29
31
|
priceChanged: copyParam(`${prefix}.purchasePriceChangedText`, "The price changed. Check it and try again."),
|
|
30
32
|
support: copyParam(`${prefix}.purchaseSupportText`, "We couldn't finish this purchase. Contact support."),
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { copyParam } from "./himi-copy.js";
|
|
2
|
+
|
|
3
|
+
/** No provider or country guess: Wix only established that online payment is unavailable. */
|
|
4
|
+
export function paymentSetupMessage(prefix: string): string {
|
|
5
|
+
return copyParam(`${prefix}.paymentSetupUnavailable`, "This site is not accepting online payments. Go back or contact the business for other payment options.");
|
|
6
|
+
}
|