@wix/himalaya-cli 0.1445.0 → 0.1447.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/sdk/state/src/index.d.ts +2 -1
- package/dist/cli.mjs +37 -27
- package/dist/preview-assets.generated.bin +0 -0
- package/fixtures/wix-canned.json +20 -0
- package/guides/sdk-reference.json +1 -1
- package/package.json +2 -2
- package/templates/beauty-salon/tier5-src/_shared/branded-lite-hosted.ts +1 -0
- package/templates/beauty-salon/tier5-src/_shared/collection-query.ts +43 -9
- package/templates/beauty-salon/tier5-src/_shared/collection-service.ts +15 -3
- package/templates/beauty-salon/tier5-src/_shared/iap-presentation.ts +3 -1
- package/templates/beauty-salon-owner/dev/index.ts +3 -14
- package/templates/beauty-salon-owner/tier5-src/_shared/collection-query.ts +43 -9
- package/templates/beauty-salon-owner/tier5-src/_shared/collection-service.ts +15 -3
- package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-manage/index.ts +17 -12
- package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-manage/logic.ts +18 -5
- package/templates/branded-lite/config.ts +7 -0
- package/templates/branded-lite/dev/index.ts +1120 -0
- package/templates/branded-lite/himalaya.content.json +3 -0
- package/templates/branded-lite/tier5-src/_shared/branded-lite-hosted.ts +1 -0
- package/templates/branded-lite/tier5-src/_shared/business-chat-message-model.ts +177 -0
- package/templates/branded-lite/tier5-src/_shared/business-chat-model.ts +22 -0
- package/templates/branded-lite/tier5-src/_shared/business-chat-presenter.ts +427 -0
- package/templates/branded-lite/tier5-src/_shared/business-chat-transport.ts +102 -0
- package/templates/branded-lite/tier5-src/_shared/collection-query.ts +43 -9
- package/templates/branded-lite/tier5-src/_shared/collection-service.ts +15 -3
- package/templates/branded-lite/tier5-src/_shared/iap-presentation.ts +3 -1
- package/templates/branded-lite/tier5-src/business-chat/index.ts +304 -0
- package/templates/community-club/tier5-src/_shared/branded-lite-hosted.ts +1 -0
- package/templates/community-club/tier5-src/_shared/collection-query.ts +43 -9
- package/templates/community-club/tier5-src/_shared/collection-service.ts +15 -3
- package/templates/community-club/tier5-src/_shared/iap-presentation.ts +3 -1
- package/templates/community-club-owner/dev/index.ts +3 -14
- package/templates/community-club-owner/tier5-src/_shared/collection-query.ts +43 -9
- package/templates/community-club-owner/tier5-src/_shared/collection-service.ts +15 -3
- package/templates/community-club-owner/tier5-src/himi-owner-lite-manage/index.ts +17 -12
- package/templates/community-club-owner/tier5-src/himi-owner-lite-manage/logic.ts +18 -5
- package/templates/events-meetups/tier5-src/_shared/branded-lite-hosted.ts +1 -0
- package/templates/events-meetups/tier5-src/_shared/collection-query.ts +43 -9
- package/templates/events-meetups/tier5-src/_shared/collection-service.ts +15 -3
- package/templates/events-meetups/tier5-src/_shared/iap-presentation.ts +3 -1
- package/templates/events-meetups-owner/dev/index.ts +3 -14
- package/templates/events-meetups-owner/tier5-src/_shared/collection-query.ts +43 -9
- package/templates/events-meetups-owner/tier5-src/_shared/collection-service.ts +15 -3
- package/templates/events-meetups-owner/tier5-src/himi-owner-lite-manage/index.ts +17 -12
- package/templates/events-meetups-owner/tier5-src/himi-owner-lite-manage/logic.ts +18 -5
- package/templates/gym-fitness/tier5-src/_shared/branded-lite-hosted.ts +1 -0
- package/templates/gym-fitness/tier5-src/_shared/collection-query.ts +43 -9
- package/templates/gym-fitness/tier5-src/_shared/collection-service.ts +15 -3
- package/templates/gym-fitness/tier5-src/_shared/iap-presentation.ts +3 -1
- package/templates/gym-fitness-owner/dev/index.ts +3 -14
- package/templates/gym-fitness-owner/tier5-src/_shared/collection-query.ts +43 -9
- package/templates/gym-fitness-owner/tier5-src/_shared/collection-service.ts +15 -3
- package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-manage/index.ts +17 -12
- package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-manage/logic.ts +18 -5
- package/templates/nonprofit-cause/tier5-src/_shared/branded-lite-hosted.ts +1 -0
- package/templates/nonprofit-cause/tier5-src/_shared/collection-query.ts +43 -9
- package/templates/nonprofit-cause/tier5-src/_shared/collection-service.ts +15 -3
- package/templates/nonprofit-cause/tier5-src/_shared/iap-presentation.ts +3 -1
- package/templates/nonprofit-cause-owner/dev/index.ts +3 -14
- package/templates/nonprofit-cause-owner/tier5-src/_shared/collection-query.ts +43 -9
- package/templates/nonprofit-cause-owner/tier5-src/_shared/collection-service.ts +15 -3
- package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-manage/index.ts +17 -12
- package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-manage/logic.ts +18 -5
- package/templates/owner-lite/dev/index.ts +3 -14
- package/templates/owner-lite/tier5-src/_shared/collection-query.ts +43 -9
- package/templates/owner-lite/tier5-src/_shared/collection-service.ts +15 -3
- package/templates/owner-lite/tier5-src/himi-owner-lite-manage/index.ts +17 -12
- package/templates/owner-lite/tier5-src/himi-owner-lite-manage/logic.ts +18 -5
- package/templates/podcast-creator/tier5-src/_shared/branded-lite-hosted.ts +1 -0
- package/templates/podcast-creator/tier5-src/_shared/collection-query.ts +43 -9
- package/templates/podcast-creator/tier5-src/_shared/collection-service.ts +15 -3
- package/templates/podcast-creator/tier5-src/_shared/iap-presentation.ts +3 -1
- package/templates/podcast-creator-owner/dev/index.ts +3 -14
- package/templates/podcast-creator-owner/tier5-src/_shared/collection-query.ts +43 -9
- package/templates/podcast-creator-owner/tier5-src/_shared/collection-service.ts +15 -3
- package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-manage/index.ts +17 -12
- package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-manage/logic.ts +18 -5
- package/templates/portfolio-creative/tier5-src/_shared/branded-lite-hosted.ts +1 -0
- package/templates/portfolio-creative/tier5-src/_shared/collection-query.ts +43 -9
- package/templates/portfolio-creative/tier5-src/_shared/collection-service.ts +15 -3
- package/templates/portfolio-creative/tier5-src/_shared/iap-presentation.ts +3 -1
- package/templates/portfolio-creative-owner/dev/index.ts +3 -14
- package/templates/portfolio-creative-owner/tier5-src/_shared/collection-query.ts +43 -9
- package/templates/portfolio-creative-owner/tier5-src/_shared/collection-service.ts +15 -3
- package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-manage/index.ts +17 -12
- package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-manage/logic.ts +18 -5
- package/templates/restaurant/tier5-src/_shared/branded-lite-hosted.ts +1 -0
- package/templates/restaurant/tier5-src/_shared/collection-query.ts +43 -9
- package/templates/restaurant/tier5-src/_shared/collection-service.ts +15 -3
- package/templates/restaurant/tier5-src/_shared/iap-presentation.ts +3 -1
- package/templates/restaurant-owner/dev/index.ts +3 -14
- package/templates/restaurant-owner/tier5-src/_shared/collection-query.ts +43 -9
- package/templates/restaurant-owner/tier5-src/_shared/collection-service.ts +15 -3
- package/templates/restaurant-owner/tier5-src/himi-owner-lite-manage/index.ts +17 -12
- package/templates/restaurant-owner/tier5-src/himi-owner-lite-manage/logic.ts +18 -5
- package/templates/retail-store/tier5-src/_shared/branded-lite-hosted.ts +1 -0
- package/templates/retail-store/tier5-src/_shared/collection-query.ts +43 -9
- package/templates/retail-store/tier5-src/_shared/collection-service.ts +15 -3
- package/templates/retail-store/tier5-src/_shared/iap-presentation.ts +3 -1
- package/templates/retail-store-owner/dev/index.ts +3 -14
- package/templates/retail-store-owner/tier5-src/_shared/collection-query.ts +43 -9
- package/templates/retail-store-owner/tier5-src/_shared/collection-service.ts +15 -3
- package/templates/retail-store-owner/tier5-src/himi-owner-lite-manage/index.ts +17 -12
- package/templates/retail-store-owner/tier5-src/himi-owner-lite-manage/logic.ts +18 -5
- package/templates/yoga-studio/tier5-src/_shared/branded-lite-hosted.ts +1 -0
- package/templates/yoga-studio/tier5-src/_shared/collection-query.ts +43 -9
- package/templates/yoga-studio/tier5-src/_shared/collection-service.ts +15 -3
- package/templates/yoga-studio/tier5-src/_shared/iap-presentation.ts +3 -1
- package/templates/yoga-studio-owner/dev/index.ts +3 -14
- package/templates/yoga-studio-owner/tier5-src/_shared/collection-query.ts +43 -9
- package/templates/yoga-studio-owner/tier5-src/_shared/collection-service.ts +15 -3
- package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-manage/index.ts +17 -12
- package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-manage/logic.ts +18 -5
package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/sdk/state/src/index.d.ts
CHANGED
|
@@ -19,7 +19,8 @@ export interface ServiceDefinition<S> {
|
|
|
19
19
|
* Keys that start each VISIT from `state`: when a cached embed shows the screen again (its warm worker
|
|
20
20
|
* reclaimed), these go back to their declared values before the screen reloads. For the same kind of
|
|
21
21
|
* per-run UI state as `notHydrated` (an editor's one-shot seed, an open sheet); never data a returning
|
|
22
|
-
* screen should keep showing. A full app's own screens and an
|
|
22
|
+
* screen should keep showing. A cold boot never restores them either. A full app's own screens and an
|
|
23
|
+
* uncached embed ignore the reset.
|
|
23
24
|
*/
|
|
24
25
|
perVisit?: readonly (keyof S & string)[];
|
|
25
26
|
}
|
package/dist/cli.mjs
CHANGED
|
@@ -11111,6 +11111,16 @@ function responseBytes(value) {
|
|
|
11111
11111
|
return 0;
|
|
11112
11112
|
}
|
|
11113
11113
|
}
|
|
11114
|
+
function restoredState(def, snapshot2) {
|
|
11115
|
+
const declared = def?.state ?? {};
|
|
11116
|
+
if (!snapshot2 || typeof snapshot2 !== "object" || Array.isArray(snapshot2)) return declared;
|
|
11117
|
+
const fresh = [def?.notHydrated, def?.perVisit].flatMap((keys) => Array.isArray(keys) ? keys : []);
|
|
11118
|
+
const merged = { ...declared };
|
|
11119
|
+
for (const [key3, value] of Object.entries(snapshot2)) {
|
|
11120
|
+
if (key3 !== "_error" && !fresh.includes(key3)) merged[key3] = value;
|
|
11121
|
+
}
|
|
11122
|
+
return merged;
|
|
11123
|
+
}
|
|
11114
11124
|
async function bootBundleWorker(bundleJs, opts) {
|
|
11115
11125
|
const budgetMs = opts.budgetMs && opts.budgetMs > 0 ? opts.budgetMs : 5e3;
|
|
11116
11126
|
const calls = [];
|
|
@@ -11170,7 +11180,7 @@ async function bootBundleWorker(bundleJs, opts) {
|
|
|
11170
11180
|
dispatch: dispatchHost,
|
|
11171
11181
|
defineService: (name, def) => {
|
|
11172
11182
|
const entry = {
|
|
11173
|
-
state: def?.
|
|
11183
|
+
state: restoredState(def, opts.persistedState?.[String(name)]),
|
|
11174
11184
|
actions: def?.actions ?? {}
|
|
11175
11185
|
};
|
|
11176
11186
|
captured.push({ name: String(name), def: entry });
|
|
@@ -161513,32 +161523,32 @@ var init_preview_assets_generated = __esm({
|
|
|
161513
161523
|
"../../packages/serve/src/preview-assets.generated.ts"() {
|
|
161514
161524
|
init_preview_assets_format();
|
|
161515
161525
|
PREVIEW_ASSET_MANIFEST = [
|
|
161516
|
-
{ key: "assets/DashboardHost-
|
|
161517
|
-
{ key: "assets/DevDrawer
|
|
161518
|
-
{ key: "assets/IdiomCompare-
|
|
161519
|
-
{ key: "assets/LightboxViewer-
|
|
161520
|
-
{ key: "assets/PhonePreviewControl-
|
|
161521
|
-
{ key: "assets/SchemeCompare-
|
|
161522
|
-
{ key: "assets/index-
|
|
161523
|
-
{ key: "assets/pdf-
|
|
161524
|
-
{ key: "assets/pdf.worker.min-BQa_JDET.js", contentType: "application/javascript; charset=utf-8", offset:
|
|
161525
|
-
{ key: "assets/pdf.worker.min-BmVo14Nb.mjs", contentType: "application/javascript; charset=utf-8", offset:
|
|
161526
|
-
{ key: "assets/pdfDocument-
|
|
161527
|
-
{ key: "fonts/Roboto-LICENSE.txt", contentType: "application/octet-stream", offset:
|
|
161528
|
-
{ key: "fonts/Roboto-Variable.ttf", contentType: "font/ttf", offset:
|
|
161529
|
-
{ key: "fonts/WixMadeforDisplay-Bold.ttf", contentType: "font/ttf", offset:
|
|
161530
|
-
{ key: "fonts/WixMadeforDisplay-ExtraBold.ttf", contentType: "font/ttf", offset:
|
|
161531
|
-
{ key: "fonts/WixMadeforDisplay-Medium.ttf", contentType: "font/ttf", offset:
|
|
161532
|
-
{ key: "fonts/WixMadeforDisplay-Regular.ttf", contentType: "font/ttf", offset:
|
|
161533
|
-
{ key: "fonts/WixMadeforDisplay-SemiBold.ttf", contentType: "font/ttf", offset:
|
|
161534
|
-
{ key: "fonts/WixMadeforText-Bold.ttf", contentType: "font/ttf", offset:
|
|
161535
|
-
{ key: "fonts/WixMadeforText-ExtraBold.ttf", contentType: "font/ttf", offset:
|
|
161536
|
-
{ key: "fonts/WixMadeforText-Medium.ttf", contentType: "font/ttf", offset:
|
|
161537
|
-
{ key: "fonts/WixMadeforText-Regular.ttf", contentType: "font/ttf", offset:
|
|
161538
|
-
{ key: "fonts/WixMadeforText-SemiBold.ttf", contentType: "font/ttf", offset:
|
|
161539
|
-
{ key: "himi-sw.js", contentType: "application/javascript; charset=utf-8", offset:
|
|
161540
|
-
{ key: "index.html", contentType: "text/html; charset=utf-8", offset:
|
|
161541
|
-
{ key: "t5-frame.html", contentType: "text/html; charset=utf-8", offset:
|
|
161526
|
+
{ key: "assets/DashboardHost-C0zY6LXc.js", contentType: "application/javascript; charset=utf-8", offset: 8, length: 2916, sha256: "2ae0514356f33d624fbdea4f37763d9485585ae8c47efda81e9fd4976134aa66" },
|
|
161527
|
+
{ key: "assets/DevDrawer--HNffqm4.js", contentType: "application/javascript; charset=utf-8", offset: 2924, length: 16744, sha256: "bc1aa20ec7d0244b30ee071da75acf380ebc7087013247c78a6a4f813ee42658" },
|
|
161528
|
+
{ key: "assets/IdiomCompare-BcPm59UD.js", contentType: "application/javascript; charset=utf-8", offset: 19668, length: 1892, sha256: "e47795d60a59d872dc9d1a31cf166940f4eeb1683e2bc858b62da24f1d7835b2" },
|
|
161529
|
+
{ key: "assets/LightboxViewer-B3iMQPin.js", contentType: "application/javascript; charset=utf-8", offset: 21560, length: 8132, sha256: "7a035923df5986b7775d3398725f9234d1d7ddf46e0a5af8e9f6bd74d771590d" },
|
|
161530
|
+
{ key: "assets/PhonePreviewControl-CRKLF95b.js", contentType: "application/javascript; charset=utf-8", offset: 29692, length: 37729, sha256: "e65e0be6f821b3fd13c847c5ae7fff2880a39bf603a3bd426878af78e3d814a7" },
|
|
161531
|
+
{ key: "assets/SchemeCompare-Bn99KkCH.js", contentType: "application/javascript; charset=utf-8", offset: 67421, length: 1824, sha256: "7f2f13e931a686d80e03ce3673dc16e93619c93326fb447d216ee2e5bffc4807" },
|
|
161532
|
+
{ key: "assets/index-DFazEEqa.js", contentType: "application/javascript; charset=utf-8", offset: 69245, length: 1268538, sha256: "3718cafcaf0cdc962f5d1c5d6bc3cf5e9ee2b1f11e918fb7e9c629e5b0a49576" },
|
|
161533
|
+
{ key: "assets/pdf-NnG-NcU-.js", contentType: "application/javascript; charset=utf-8", offset: 1337783, length: 488199, sha256: "f60ce93a839789588474a844c60da2ebc210efbd3f50f04cc4e44eb8e127cddc" },
|
|
161534
|
+
{ key: "assets/pdf.worker.min-BQa_JDET.js", contentType: "application/javascript; charset=utf-8", offset: 1825982, length: 73, sha256: "41bcfda4f3c8c0a81c785ea87c69afc8e7d0ed8c333324aee94c273d881bb6aa" },
|
|
161535
|
+
{ key: "assets/pdf.worker.min-BmVo14Nb.mjs", contentType: "application/javascript; charset=utf-8", offset: 1826055, length: 1317034, sha256: "a33cfe728c584fdba4fcc1fd54bcdc2f9f2f13889ddbb5b2bd1d0f8cbe49b84e" },
|
|
161536
|
+
{ key: "assets/pdfDocument-MaeR1HVD.js", contentType: "application/javascript; charset=utf-8", offset: 3143089, length: 2868, sha256: "2752019f5f58a8818f6471de600ccfb647cfab7577501d19d0eface21e7820dc" },
|
|
161537
|
+
{ key: "fonts/Roboto-LICENSE.txt", contentType: "application/octet-stream", offset: 3145957, length: 4610, sha256: "994062dd7af4186a739de55dd9e151c63b6301f8e2cbafc608d35c2e87da0cd2" },
|
|
161538
|
+
{ key: "fonts/Roboto-Variable.ttf", contentType: "font/ttf", offset: 3150567, length: 488584, sha256: "d7598e12c5dbef095ff8272cfc55da0250bd07fbdecbac8a530b9b277872a134" },
|
|
161539
|
+
{ key: "fonts/WixMadeforDisplay-Bold.ttf", contentType: "font/ttf", offset: 3639151, length: 90924, sha256: "ec4c7d63772d980d6e147b2df6215fd2737e4b04cebe0cf7e11dc695fb63cdcf" },
|
|
161540
|
+
{ key: "fonts/WixMadeforDisplay-ExtraBold.ttf", contentType: "font/ttf", offset: 3730075, length: 90984, sha256: "bf4c16a2774edd38a921edf3099469866cd74c094fd596f3fa4e81f54a04dfaa" },
|
|
161541
|
+
{ key: "fonts/WixMadeforDisplay-Medium.ttf", contentType: "font/ttf", offset: 3821059, length: 90980, sha256: "368faca44fb9f7207e4af784e94750b1ee59c1098b8689e0f4a918926f244fb5" },
|
|
161542
|
+
{ key: "fonts/WixMadeforDisplay-Regular.ttf", contentType: "font/ttf", offset: 3912039, length: 90872, sha256: "97ed8f1a6772abe26e3d8af5cecfc4b02faa94497d585b0398b5053b739a70c1" },
|
|
161543
|
+
{ key: "fonts/WixMadeforDisplay-SemiBold.ttf", contentType: "font/ttf", offset: 4002911, length: 90980, sha256: "642feab8a48ddf6c7a89d7c2a7c0edfa0e6e773f671287e460e222703563191e" },
|
|
161544
|
+
{ key: "fonts/WixMadeforText-Bold.ttf", contentType: "font/ttf", offset: 4093891, length: 108636, sha256: "37c179b4fefb63ea9637322b84583a2df53aa226c150294eea1675c14b1b8e29" },
|
|
161545
|
+
{ key: "fonts/WixMadeforText-ExtraBold.ttf", contentType: "font/ttf", offset: 4202527, length: 109084, sha256: "62e2fe4718129f36ecdb0ae4de385bd6b81ecc876146447167c6c140fad699a9" },
|
|
161546
|
+
{ key: "fonts/WixMadeforText-Medium.ttf", contentType: "font/ttf", offset: 4311611, length: 109048, sha256: "4ecf270136af928612c76fe4ebade91d8156975e5ea4c0cf9225501dd1788e02" },
|
|
161547
|
+
{ key: "fonts/WixMadeforText-Regular.ttf", contentType: "font/ttf", offset: 4420659, length: 107700, sha256: "dee56f09fd3efc127c1ea95557edc74110c1460a46287da36e0af25efd341ef5" },
|
|
161548
|
+
{ key: "fonts/WixMadeforText-SemiBold.ttf", contentType: "font/ttf", offset: 4528359, length: 88108, sha256: "dced97a77624bc5f8b1ae65850360908297f4588e09feb6f22792d29dc7e765b" },
|
|
161549
|
+
{ key: "himi-sw.js", contentType: "application/javascript; charset=utf-8", offset: 4616467, length: 6748, sha256: "4a92459977d7cefa1a6a7e020c3a63509737f083b3f2773557c1a5771665811a" },
|
|
161550
|
+
{ key: "index.html", contentType: "text/html; charset=utf-8", offset: 4623215, length: 4947, sha256: "31e294c8a2eff7f23f4b24e1696713854eb517ad048ef9cc6967c90b9abca5ba" },
|
|
161551
|
+
{ key: "t5-frame.html", contentType: "text/html; charset=utf-8", offset: 4628162, length: 17786, sha256: "084107ca997628c248f1b0ba3a7494744850df39fad813546ab8bdb82a6058da" }
|
|
161542
161552
|
];
|
|
161543
161553
|
}
|
|
161544
161554
|
});
|
|
Binary file
|
package/fixtures/wix-canned.json
CHANGED
|
@@ -1387,6 +1387,26 @@
|
|
|
1387
1387
|
"total": 2
|
|
1388
1388
|
}
|
|
1389
1389
|
},
|
|
1390
|
+
"POST https://manage.wix.com/_api/wix-chat-tokens-server/v1/chat-tokens": {
|
|
1391
|
+
"chatToken": "fixture-chat-token"
|
|
1392
|
+
},
|
|
1393
|
+
"POST https://manage.wix.com/_api/chat-web/v1/chatrooms-for-list-view": {
|
|
1394
|
+
"chatrooms": {
|
|
1395
|
+
"items": [
|
|
1396
|
+
{ "id": "room-1", "participantId": "member-1", "participantCount": 2, "clientReferenceType": "contactId", "businessContext": { "id": "business-1", "name": "Acme" } }
|
|
1397
|
+
]
|
|
1398
|
+
}
|
|
1399
|
+
},
|
|
1400
|
+
"GET https://manage.wix.com/_api/chat-web/v1/chatrooms/room-1/messages": {
|
|
1401
|
+
"items": [
|
|
1402
|
+
{ "id": "chat-m3", "sequence": 3, "createdAt": 1792942000000, "messageType": "template/card", "appId": "ai-chat-app", "originMethod": "site:AI_SITE_CHAT", "sender": { "userId": "staff-1", "type": "user" }, "senderDisplayData": { "name": "Acme Staff" }, "templateData": { "text": "Where shall we start?", "buttons": [{ "text": "Show me calm routines", "payload": { "appId": "ai-chat-app", "interactionId": "calm" } }] } },
|
|
1403
|
+
{ "id": "chat-m2", "sequence": 2, "createdAt": 1792941000000, "sender": { "userId": "member-1", "type": "user" }, "data": [{ "mimeType": "text/plain", "data": { "text": "Do you have a slot on Thursday?" } }] },
|
|
1404
|
+
{ "id": "chat-m1", "sequence": 1, "createdAt": 1792940000000, "sender": { "userId": "staff-1", "type": "user" }, "senderDisplayData": { "name": "Acme Staff" }, "data": [{ "mimeType": "text/plain", "data": { "text": "Thursday 18:00 is yours." } }] }
|
|
1405
|
+
]
|
|
1406
|
+
},
|
|
1407
|
+
"GET /wix-assistant-widget/v1/messages/list": {
|
|
1408
|
+
"messages": []
|
|
1409
|
+
},
|
|
1390
1410
|
"POST https://manage.wix.com/_api/vod/public/graphql": {
|
|
1391
1411
|
"channels": {
|
|
1392
1412
|
"entities": [
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"name": "ServiceDefinition",
|
|
9
9
|
"kind": "interface",
|
|
10
10
|
"jsdoc": "",
|
|
11
|
-
"decl": "export interface ServiceDefinition<S> {\n /** Initial state for the service. Must be JSON-serializable. */\n state: S;\n\n /**\n * Actions: pure functions of `(state, ...args) → newState` (or async). The\n * platform wraps each action so calling it from JS or from `__invokeAction`\n * (native side) updates the service's state and notifies subscribers.\n */\n actions?: Record<\n string,\n // `never[]`, not `unknown[]` — same reason as defineService's constraint below:\n // parameters are contravariant, so `unknown[]` rejects every handler that narrows\n // its payload. Leaving this one loose would reintroduce the same TS2344 for anyone\n // who annotates a `ServiceDefinition<S>` explicitly instead of inferring it.\n (state: S, ...args: never[]) => S | undefined | Promise<S | undefined>\n >;\n\n /**\n * Keys a cold boot takes from `state`, never from the persisted snapshot (cold-boot\n * hydration). For UI state that must describe THIS run — an editor's one-shot seed, a\n * \"still loading\" gate, an open sheet — where last run's value would be wrong, not just stale.\n * Runtimes that honour it set `himalaya.hydrationOptOut`; an older one ignores the option.\n */\n notHydrated?: readonly (keyof S & string)[];\n\n /**\n * Keys that start each VISIT from `state`: when a cached embed shows the screen again (its warm worker\n * reclaimed), these go back to their declared values before the screen reloads. For the same kind of\n * per-run UI state as `notHydrated` (an editor's one-shot seed, an open sheet); never data a returning\n * screen should keep showing. A full app's own screens and an uncached embed ignore
|
|
11
|
+
"decl": "export interface ServiceDefinition<S> {\n /** Initial state for the service. Must be JSON-serializable. */\n state: S;\n\n /**\n * Actions: pure functions of `(state, ...args) → newState` (or async). The\n * platform wraps each action so calling it from JS or from `__invokeAction`\n * (native side) updates the service's state and notifies subscribers.\n */\n actions?: Record<\n string,\n // `never[]`, not `unknown[]` — same reason as defineService's constraint below:\n // parameters are contravariant, so `unknown[]` rejects every handler that narrows\n // its payload. Leaving this one loose would reintroduce the same TS2344 for anyone\n // who annotates a `ServiceDefinition<S>` explicitly instead of inferring it.\n (state: S, ...args: never[]) => S | undefined | Promise<S | undefined>\n >;\n\n /**\n * Keys a cold boot takes from `state`, never from the persisted snapshot (cold-boot\n * hydration). For UI state that must describe THIS run — an editor's one-shot seed, a\n * \"still loading\" gate, an open sheet — where last run's value would be wrong, not just stale.\n * Runtimes that honour it set `himalaya.hydrationOptOut`; an older one ignores the option.\n */\n notHydrated?: readonly (keyof S & string)[];\n\n /**\n * Keys that start each VISIT from `state`: when a cached embed shows the screen again (its warm worker\n * reclaimed), these go back to their declared values before the screen reloads. For the same kind of\n * per-run UI state as `notHydrated` (an editor's one-shot seed, an open sheet); never data a returning\n * screen should keep showing. A cold boot never restores them either. A full app's own screens and an\n * uncached embed ignore the reset.\n */\n perVisit?: readonly (keyof S & string)[];\n}"
|
|
12
12
|
},
|
|
13
13
|
{
|
|
14
14
|
"name": "ServiceHandle",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wix/himalaya-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.1447.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": "ec7806275850384530f4bb3f9e2a1ee23e66bf5223389c5dc7a3446a"
|
|
75
75
|
}
|
|
@@ -72,6 +72,7 @@ export const HOSTED_SCREEN_VERTICALS: Readonly<Record<string, readonly BrandedLi
|
|
|
72
72
|
himi_blog: ["blog"],
|
|
73
73
|
himi_bookings: ["bookings"],
|
|
74
74
|
himi_cart: ["store"],
|
|
75
|
+
himi_business_chat: ["inbox"],
|
|
75
76
|
himi_chat_thread: ["inbox", "members"],
|
|
76
77
|
himi_cms: ["cms"],
|
|
77
78
|
himi_collection: ["store"],
|
|
@@ -20,6 +20,13 @@ export interface CollectionQueryState<Row> extends CollectionPage<Row> {
|
|
|
20
20
|
pageError: boolean;
|
|
21
21
|
complete: boolean;
|
|
22
22
|
error: string;
|
|
23
|
+
/** The rows are the source's `cached` page, shown while the live read is still out. */
|
|
24
|
+
provisional?: boolean;
|
|
25
|
+
}
|
|
26
|
+
/** What a collection showed, as `shownPage` hands it out and `seed` takes it back on a restored boot. */
|
|
27
|
+
export interface CollectionShownPage<Row> {
|
|
28
|
+
state: CollectionQueryState<Row>;
|
|
29
|
+
loadedPages: number;
|
|
23
30
|
}
|
|
24
31
|
export interface CollectionQuerySource<Row> {
|
|
25
32
|
fetch(criteria: CollectionQuery, cursor: string): Promise<CollectionPage<Row>>;
|
|
@@ -50,13 +57,19 @@ export function createCollectionQuery<Row>(source: CollectionQuerySource<Row>, p
|
|
|
50
57
|
let state: CollectionQueryState<Row> = { criteria: normalize({}), items: [], nextCursor: '', status: 'idle', loadingMore: false, pageError: false, complete: false, error: '' };
|
|
51
58
|
/** The query a revalidation is running under: its snapshots are held back until it is done. */
|
|
52
59
|
let revalidating = -1;
|
|
53
|
-
/**
|
|
54
|
-
|
|
60
|
+
/** The last page the screen showed whole (rows ready, nothing paging, failing or standing in): what a revalidation
|
|
61
|
+
* keeps on screen and restores when it fails, and what a restored boot shows. A next page loading or failing after
|
|
62
|
+
* it leaves those rows on screen, so it is still this. */
|
|
63
|
+
let lastShown: { state: CollectionQueryState<Row>; loadedPages: number } | undefined;
|
|
64
|
+
const sameCriteria = (a: CollectionQuery, b: CollectionQuery) => JSON.stringify(a) === JSON.stringify(b);
|
|
55
65
|
let clearedByError = false;
|
|
56
66
|
const snapshot = (): CollectionQueryState<Row> => ({ ...state, criteria: copyQuery(state.criteria), items: [...state.items] });
|
|
57
67
|
const emit = () => {
|
|
58
68
|
if (disposed || revalidating === generation) return;
|
|
59
|
-
|
|
69
|
+
// A settled answer replaces it, an empty one included: rows the live query proved gone are never kept.
|
|
70
|
+
if (state.status === 'ready' && !state.pageError && !state.loadingMore && !state.provisional) {
|
|
71
|
+
lastShown = state.items.length > 0 ? { state: snapshot(), loadedPages } : undefined;
|
|
72
|
+
}
|
|
60
73
|
publish(snapshot());
|
|
61
74
|
};
|
|
62
75
|
const merge = (old: Row[], page: Row[]): Row[] => {
|
|
@@ -77,14 +90,14 @@ export function createCollectionQuery<Row>(source: CollectionQuerySource<Row>, p
|
|
|
77
90
|
const criteria = copyQuery(state.criteria);
|
|
78
91
|
if (!append) loadedPages = 0;
|
|
79
92
|
state = { ...state, status: append ? 'ready' : 'loading', loadingMore: append, pageError: false, error: '',
|
|
80
|
-
...(append ? {} : { items: [], nextCursor: '', total: undefined, metadata: undefined, complete: false }) };
|
|
93
|
+
...(append ? {} : { items: [], nextCursor: '', total: undefined, metadata: undefined, complete: false, provisional: false }) };
|
|
81
94
|
emit();
|
|
82
95
|
try {
|
|
83
96
|
if (!append && source.cached) {
|
|
84
97
|
const cached = await source.cached(criteria).catch(() => undefined);
|
|
85
98
|
if (disposed || generation !== revision) return;
|
|
86
99
|
if (cached?.items.length) {
|
|
87
|
-
state = { ...state, items: merge([], cached.items), metadata: cached.metadata, status: 'ready', complete: false };
|
|
100
|
+
state = { ...state, items: merge([], cached.items), metadata: cached.metadata, status: 'ready', complete: false, provisional: true };
|
|
88
101
|
emit();
|
|
89
102
|
}
|
|
90
103
|
}
|
|
@@ -94,17 +107,18 @@ export function createCollectionQuery<Row>(source: CollectionQuerySource<Row>, p
|
|
|
94
107
|
state = { ...state, items: merge(append ? state.items : [], page.items), nextCursor: page.nextCursor,
|
|
95
108
|
total: typeof page.total === 'number' && Number.isFinite(page.total) && page.total >= 0 ? page.total : (append ? state.total : undefined),
|
|
96
109
|
metadata: page.metadata ?? (append ? state.metadata : undefined),
|
|
97
|
-
status: 'ready', loadingMore: false, pageError: false, complete: page.complete ?? !page.nextCursor };
|
|
110
|
+
status: 'ready', loadingMore: false, pageError: false, complete: page.complete ?? !page.nextCursor, provisional: false };
|
|
98
111
|
loadedPages++;
|
|
99
112
|
} catch (error) {
|
|
100
113
|
if (disposed || generation !== revision) return;
|
|
101
114
|
const clear = source.clearOnError?.(error) === true;
|
|
102
115
|
// Revoked access always ends what a revalidation showed, whether or not the source clears its rows.
|
|
103
116
|
clearedByError = clear || /HTTP 40[13]\b/.test(String(error));
|
|
117
|
+
if (clearedByError) lastShown = undefined;
|
|
104
118
|
if (clear) loadedPages = 0;
|
|
105
119
|
state = { ...state, ...(clear ? { items: [], nextCursor: '', total: undefined, metadata: undefined } : {}),
|
|
106
120
|
status: append && !clear ? 'ready' : 'error', loadingMore: false, pageError: append && !clear, complete: false,
|
|
107
|
-
error: source.errorMessage?.(error) ?? '' };
|
|
121
|
+
provisional: false, error: source.errorMessage?.(error) ?? '' };
|
|
108
122
|
}
|
|
109
123
|
emit();
|
|
110
124
|
};
|
|
@@ -117,8 +131,7 @@ export function createCollectionQuery<Row>(source: CollectionQuerySource<Row>, p
|
|
|
117
131
|
const next = normalize(criteria);
|
|
118
132
|
const same = JSON.stringify(next) === JSON.stringify(state.criteria);
|
|
119
133
|
const count = restorePages && same ? Math.max(loadedPages, restoringPages, 1) : 1;
|
|
120
|
-
const showing =
|
|
121
|
-
&& !shown.state.loadingMore && JSON.stringify(shown.state.criteria) === JSON.stringify(next) ? shown : undefined;
|
|
134
|
+
const showing = lastShown && sameCriteria(lastShown.state.criteria, next) ? lastShown : undefined;
|
|
122
135
|
restoringPages = count;
|
|
123
136
|
state = { ...state, criteria: next };
|
|
124
137
|
if (revalidate && showing) { revalidating = generation + 1; clearedByError = false; }
|
|
@@ -140,6 +153,27 @@ export function createCollectionQuery<Row>(source: CollectionQuerySource<Row>, p
|
|
|
140
153
|
emit();
|
|
141
154
|
}
|
|
142
155
|
},
|
|
156
|
+
/** The page the screen shows (rows ready, nothing failing or still paging), for the service to keep with its
|
|
157
|
+
* state; undefined when it shows none. */
|
|
158
|
+
shownPage(): CollectionShownPage<Row> | undefined {
|
|
159
|
+
if (!lastShown || !sameCriteria(lastShown.state.criteria, state.criteria)) return undefined;
|
|
160
|
+
const page = lastShown.state;
|
|
161
|
+
return { state: { ...page, criteria: copyQuery(page.criteria), items: [...page.items] }, loadedPages: lastShown.loadedPages };
|
|
162
|
+
},
|
|
163
|
+
/** A cold boot restored from disk: what the last run showed becomes what this one shows, so its first `load`
|
|
164
|
+
* revalidates behind it instead of starting empty. Only before the first query, and only a well-formed page. */
|
|
165
|
+
seed(page: unknown): boolean {
|
|
166
|
+
const candidate = page && typeof page === 'object' ? page as Partial<CollectionShownPage<Row>> : undefined;
|
|
167
|
+
const restored = candidate?.state;
|
|
168
|
+
if (generation !== 0 || state.status !== 'idle' || !restored || restored.status !== 'ready'
|
|
169
|
+
|| !Array.isArray(restored.items) || restored.items.length === 0 || typeof restored.nextCursor !== 'string') return false;
|
|
170
|
+
try { restored.items.forEach(item => { if (!source.id(item)) throw new Error('A backend collection row requires an identity'); }); } catch { return false; }
|
|
171
|
+
state = { ...state, ...restored, criteria: normalize(restored.criteria ?? {}), items: [...restored.items], status: 'ready',
|
|
172
|
+
loadingMore: false, pageError: false, provisional: false, error: '' };
|
|
173
|
+
loadedPages = Math.max(1, Math.floor(Number(candidate?.loadedPages) || 1));
|
|
174
|
+
lastShown = { state: snapshot(), loadedPages };
|
|
175
|
+
return true;
|
|
176
|
+
},
|
|
143
177
|
refresh: () => { restoringPages = 0; return fetch(false); },
|
|
144
178
|
loadMore: () => fetch(true),
|
|
145
179
|
retry: () => fetch(state.pageError),
|
|
@@ -142,6 +142,7 @@ export function decorateCollection(name: string, def: { state: State; actions?:
|
|
|
142
142
|
// the query controller carries a recoverable failure. Treat each shape as
|
|
143
143
|
// one collection state so the shared UI never renders a blank catalog.
|
|
144
144
|
const backendState = backend?.controller.getState();
|
|
145
|
+
const provisional = backendState?.provisional === true;
|
|
145
146
|
const errored = s.status === 'error' || s.isErrored === true || s.errored === true || backendState?.status === 'error';
|
|
146
147
|
const domainError = typeof s.error === 'string' ? s.error.trim() : '';
|
|
147
148
|
const queryError = typeof backendState?.error === 'string' ? backendState.error.trim() : '';
|
|
@@ -177,13 +178,17 @@ export function decorateCollection(name: string, def: { state: State; actions?:
|
|
|
177
178
|
// un-narrowed failure. A re-query while typing keeps it (and the search field's focus).
|
|
178
179
|
const hideChrome = (!narrowed && !s.nextCursor && projected.loaded < COLLECTION_CHROME_MIN_ITEMS)
|
|
179
180
|
|| sourceEmpty || ((loading || errored) && rows.length === 0 && !narrowed);
|
|
180
|
-
|
|
181
|
+
// Kept with the state, so a cold boot restored from disk shows its last page and revalidates behind it.
|
|
182
|
+
const collectionShown = backend?.controller.shownPage();
|
|
183
|
+
return { ...s, ...(backend ? { collectionShown: collectionShown ?? null } : {}), collection: { ...prefs, ...projected,
|
|
181
184
|
hideChrome, noActiveCriteria: !narrowed,
|
|
182
185
|
viewName, viewMessage, saveMessage, noSavedViews: prefs.savedViews.length === 0,
|
|
183
186
|
cursor: s.pageError === true || s.loadingMore === true ? '' : s.nextCursor,
|
|
184
187
|
noMore: !s.nextCursor || s.pageError === true || s.loadingMore === true,
|
|
185
188
|
noPageError: s.pageError !== true,
|
|
186
|
-
|
|
189
|
+
// Rows read from the device ahead of the live read are shown as they are: "saved offline" is what stays on
|
|
190
|
+
// screen when the live read failed, not a first visit that is still loading.
|
|
191
|
+
noOffline: s.fromCache !== true || provisional,
|
|
187
192
|
noError: !errored,
|
|
188
193
|
noRetry: !canRetry || signInRequired,
|
|
189
194
|
noSignIn: !signInRequired,
|
|
@@ -202,7 +207,7 @@ export function decorateCollection(name: string, def: { state: State; actions?:
|
|
|
202
207
|
filtersPresented, previewPresented: !!previewId, preview: projected.items.filter(row => row.collectionId === previewId),
|
|
203
208
|
persistenceLabel: persisted.available ? copyParam('collection.onDevice', 'Saved on this device') : copyParam('collection.sessionOnly', 'Choices last for this session'),
|
|
204
209
|
noEmpty: loading || errored || sourceEmpty || domainNarrowed || projected.items.length > 0,
|
|
205
|
-
summary: backend && config.backendQuery && s.fromCache
|
|
210
|
+
summary: backend && config.backendQuery && s.fromCache && !provisional
|
|
206
211
|
? copyParam('collection.cached', 'Saved offline · may be outdated')
|
|
207
212
|
: collectionSummary({ shown: projected.items.length, loaded: projected.loaded, backend: !!(backend && config.backendQuery),
|
|
208
213
|
total: typeof s.total === 'number' ? s.total : undefined, noun: config.countNoun
|
|
@@ -345,6 +350,13 @@ export function defineBackendCollection<S extends object, Row extends object>(na
|
|
|
345
350
|
apply: (state, page) => options.apply(state as S, page as unknown as CollectionQueryState<Row>) as State,
|
|
346
351
|
});
|
|
347
352
|
const handle = (options.register ?? register)(name, definition);
|
|
353
|
+
// Registration restored the state a cold boot saved: the page it showed is this run's, before the first load. Only
|
|
354
|
+
// when the service's own rows came back with it: a bundle that keeps them out of the restore (its `notHydrated`)
|
|
355
|
+
// starts its first load as before, never from rows it chose not to restore.
|
|
356
|
+
const restored = (handle as { getState?: () => State }).getState?.();
|
|
357
|
+
const rowsKey = COLLECTIONS[name]?.rows;
|
|
358
|
+
const restoredRows = rowsKey ? restored?.[rowsKey] : undefined;
|
|
359
|
+
if (Array.isArray(restoredRows) && restoredRows.length > 0) controller.seed(restored?.collectionShown);
|
|
348
360
|
publish = () => handle.collectionDataChanged();
|
|
349
361
|
return handle;
|
|
350
362
|
}
|
|
@@ -45,7 +45,9 @@ export function withIapPresentation<S extends object>(options: {
|
|
|
45
45
|
return project(next);
|
|
46
46
|
}]));
|
|
47
47
|
const service = (options.register ?? defineService)(name, {
|
|
48
|
-
|
|
48
|
+
// The collection's shown page goes with the rows and chrome it copies: kept out of the restore with them.
|
|
49
|
+
...definition, notHydrated: [...(definition.notHydrated ?? []), ...options.transientKeys,
|
|
50
|
+
...((options.transientKeys as string[]).includes('collection') ? ['collectionShown' as keyof S & string] : [])],
|
|
49
51
|
actions: { ...actions, iapPublish: project, iapPoll: async () => { await commerce.poll(); return undefined; },
|
|
50
52
|
iapRefresh: async () => { await commerce.loadPrices(items); return undefined; } },
|
|
51
53
|
});
|
|
@@ -51696,20 +51696,6 @@ const SCREENS = runtimeSetting({
|
|
|
51696
51696
|
}
|
|
51697
51697
|
}
|
|
51698
51698
|
},
|
|
51699
|
-
{
|
|
51700
|
-
"id": "",
|
|
51701
|
-
"body": {
|
|
51702
|
-
"package": "himalaya",
|
|
51703
|
-
"type": "Text",
|
|
51704
|
-
"props": {
|
|
51705
|
-
"text": "$service:himiOwnerLiteManage.summaryNote",
|
|
51706
|
-
"style": "caption",
|
|
51707
|
-
"color": "textSecondary",
|
|
51708
|
-
"maxLines": 2,
|
|
51709
|
-
"collapseWhenEmpty": true
|
|
51710
|
-
}
|
|
51711
|
-
}
|
|
51712
|
-
},
|
|
51713
51699
|
{
|
|
51714
51700
|
"id": "",
|
|
51715
51701
|
"body": {
|
|
@@ -52334,6 +52320,9 @@ const SCREENS = runtimeSetting({
|
|
|
52334
52320
|
}
|
|
52335
52321
|
]
|
|
52336
52322
|
}
|
|
52323
|
+
},
|
|
52324
|
+
"modifiers": {
|
|
52325
|
+
"animateContentSize": true
|
|
52337
52326
|
}
|
|
52338
52327
|
}
|
|
52339
52328
|
}
|
|
@@ -20,6 +20,13 @@ export interface CollectionQueryState<Row> extends CollectionPage<Row> {
|
|
|
20
20
|
pageError: boolean;
|
|
21
21
|
complete: boolean;
|
|
22
22
|
error: string;
|
|
23
|
+
/** The rows are the source's `cached` page, shown while the live read is still out. */
|
|
24
|
+
provisional?: boolean;
|
|
25
|
+
}
|
|
26
|
+
/** What a collection showed, as `shownPage` hands it out and `seed` takes it back on a restored boot. */
|
|
27
|
+
export interface CollectionShownPage<Row> {
|
|
28
|
+
state: CollectionQueryState<Row>;
|
|
29
|
+
loadedPages: number;
|
|
23
30
|
}
|
|
24
31
|
export interface CollectionQuerySource<Row> {
|
|
25
32
|
fetch(criteria: CollectionQuery, cursor: string): Promise<CollectionPage<Row>>;
|
|
@@ -50,13 +57,19 @@ export function createCollectionQuery<Row>(source: CollectionQuerySource<Row>, p
|
|
|
50
57
|
let state: CollectionQueryState<Row> = { criteria: normalize({}), items: [], nextCursor: '', status: 'idle', loadingMore: false, pageError: false, complete: false, error: '' };
|
|
51
58
|
/** The query a revalidation is running under: its snapshots are held back until it is done. */
|
|
52
59
|
let revalidating = -1;
|
|
53
|
-
/**
|
|
54
|
-
|
|
60
|
+
/** The last page the screen showed whole (rows ready, nothing paging, failing or standing in): what a revalidation
|
|
61
|
+
* keeps on screen and restores when it fails, and what a restored boot shows. A next page loading or failing after
|
|
62
|
+
* it leaves those rows on screen, so it is still this. */
|
|
63
|
+
let lastShown: { state: CollectionQueryState<Row>; loadedPages: number } | undefined;
|
|
64
|
+
const sameCriteria = (a: CollectionQuery, b: CollectionQuery) => JSON.stringify(a) === JSON.stringify(b);
|
|
55
65
|
let clearedByError = false;
|
|
56
66
|
const snapshot = (): CollectionQueryState<Row> => ({ ...state, criteria: copyQuery(state.criteria), items: [...state.items] });
|
|
57
67
|
const emit = () => {
|
|
58
68
|
if (disposed || revalidating === generation) return;
|
|
59
|
-
|
|
69
|
+
// A settled answer replaces it, an empty one included: rows the live query proved gone are never kept.
|
|
70
|
+
if (state.status === 'ready' && !state.pageError && !state.loadingMore && !state.provisional) {
|
|
71
|
+
lastShown = state.items.length > 0 ? { state: snapshot(), loadedPages } : undefined;
|
|
72
|
+
}
|
|
60
73
|
publish(snapshot());
|
|
61
74
|
};
|
|
62
75
|
const merge = (old: Row[], page: Row[]): Row[] => {
|
|
@@ -77,14 +90,14 @@ export function createCollectionQuery<Row>(source: CollectionQuerySource<Row>, p
|
|
|
77
90
|
const criteria = copyQuery(state.criteria);
|
|
78
91
|
if (!append) loadedPages = 0;
|
|
79
92
|
state = { ...state, status: append ? 'ready' : 'loading', loadingMore: append, pageError: false, error: '',
|
|
80
|
-
...(append ? {} : { items: [], nextCursor: '', total: undefined, metadata: undefined, complete: false }) };
|
|
93
|
+
...(append ? {} : { items: [], nextCursor: '', total: undefined, metadata: undefined, complete: false, provisional: false }) };
|
|
81
94
|
emit();
|
|
82
95
|
try {
|
|
83
96
|
if (!append && source.cached) {
|
|
84
97
|
const cached = await source.cached(criteria).catch(() => undefined);
|
|
85
98
|
if (disposed || generation !== revision) return;
|
|
86
99
|
if (cached?.items.length) {
|
|
87
|
-
state = { ...state, items: merge([], cached.items), metadata: cached.metadata, status: 'ready', complete: false };
|
|
100
|
+
state = { ...state, items: merge([], cached.items), metadata: cached.metadata, status: 'ready', complete: false, provisional: true };
|
|
88
101
|
emit();
|
|
89
102
|
}
|
|
90
103
|
}
|
|
@@ -94,17 +107,18 @@ export function createCollectionQuery<Row>(source: CollectionQuerySource<Row>, p
|
|
|
94
107
|
state = { ...state, items: merge(append ? state.items : [], page.items), nextCursor: page.nextCursor,
|
|
95
108
|
total: typeof page.total === 'number' && Number.isFinite(page.total) && page.total >= 0 ? page.total : (append ? state.total : undefined),
|
|
96
109
|
metadata: page.metadata ?? (append ? state.metadata : undefined),
|
|
97
|
-
status: 'ready', loadingMore: false, pageError: false, complete: page.complete ?? !page.nextCursor };
|
|
110
|
+
status: 'ready', loadingMore: false, pageError: false, complete: page.complete ?? !page.nextCursor, provisional: false };
|
|
98
111
|
loadedPages++;
|
|
99
112
|
} catch (error) {
|
|
100
113
|
if (disposed || generation !== revision) return;
|
|
101
114
|
const clear = source.clearOnError?.(error) === true;
|
|
102
115
|
// Revoked access always ends what a revalidation showed, whether or not the source clears its rows.
|
|
103
116
|
clearedByError = clear || /HTTP 40[13]\b/.test(String(error));
|
|
117
|
+
if (clearedByError) lastShown = undefined;
|
|
104
118
|
if (clear) loadedPages = 0;
|
|
105
119
|
state = { ...state, ...(clear ? { items: [], nextCursor: '', total: undefined, metadata: undefined } : {}),
|
|
106
120
|
status: append && !clear ? 'ready' : 'error', loadingMore: false, pageError: append && !clear, complete: false,
|
|
107
|
-
error: source.errorMessage?.(error) ?? '' };
|
|
121
|
+
provisional: false, error: source.errorMessage?.(error) ?? '' };
|
|
108
122
|
}
|
|
109
123
|
emit();
|
|
110
124
|
};
|
|
@@ -117,8 +131,7 @@ export function createCollectionQuery<Row>(source: CollectionQuerySource<Row>, p
|
|
|
117
131
|
const next = normalize(criteria);
|
|
118
132
|
const same = JSON.stringify(next) === JSON.stringify(state.criteria);
|
|
119
133
|
const count = restorePages && same ? Math.max(loadedPages, restoringPages, 1) : 1;
|
|
120
|
-
const showing =
|
|
121
|
-
&& !shown.state.loadingMore && JSON.stringify(shown.state.criteria) === JSON.stringify(next) ? shown : undefined;
|
|
134
|
+
const showing = lastShown && sameCriteria(lastShown.state.criteria, next) ? lastShown : undefined;
|
|
122
135
|
restoringPages = count;
|
|
123
136
|
state = { ...state, criteria: next };
|
|
124
137
|
if (revalidate && showing) { revalidating = generation + 1; clearedByError = false; }
|
|
@@ -140,6 +153,27 @@ export function createCollectionQuery<Row>(source: CollectionQuerySource<Row>, p
|
|
|
140
153
|
emit();
|
|
141
154
|
}
|
|
142
155
|
},
|
|
156
|
+
/** The page the screen shows (rows ready, nothing failing or still paging), for the service to keep with its
|
|
157
|
+
* state; undefined when it shows none. */
|
|
158
|
+
shownPage(): CollectionShownPage<Row> | undefined {
|
|
159
|
+
if (!lastShown || !sameCriteria(lastShown.state.criteria, state.criteria)) return undefined;
|
|
160
|
+
const page = lastShown.state;
|
|
161
|
+
return { state: { ...page, criteria: copyQuery(page.criteria), items: [...page.items] }, loadedPages: lastShown.loadedPages };
|
|
162
|
+
},
|
|
163
|
+
/** A cold boot restored from disk: what the last run showed becomes what this one shows, so its first `load`
|
|
164
|
+
* revalidates behind it instead of starting empty. Only before the first query, and only a well-formed page. */
|
|
165
|
+
seed(page: unknown): boolean {
|
|
166
|
+
const candidate = page && typeof page === 'object' ? page as Partial<CollectionShownPage<Row>> : undefined;
|
|
167
|
+
const restored = candidate?.state;
|
|
168
|
+
if (generation !== 0 || state.status !== 'idle' || !restored || restored.status !== 'ready'
|
|
169
|
+
|| !Array.isArray(restored.items) || restored.items.length === 0 || typeof restored.nextCursor !== 'string') return false;
|
|
170
|
+
try { restored.items.forEach(item => { if (!source.id(item)) throw new Error('A backend collection row requires an identity'); }); } catch { return false; }
|
|
171
|
+
state = { ...state, ...restored, criteria: normalize(restored.criteria ?? {}), items: [...restored.items], status: 'ready',
|
|
172
|
+
loadingMore: false, pageError: false, provisional: false, error: '' };
|
|
173
|
+
loadedPages = Math.max(1, Math.floor(Number(candidate?.loadedPages) || 1));
|
|
174
|
+
lastShown = { state: snapshot(), loadedPages };
|
|
175
|
+
return true;
|
|
176
|
+
},
|
|
143
177
|
refresh: () => { restoringPages = 0; return fetch(false); },
|
|
144
178
|
loadMore: () => fetch(true),
|
|
145
179
|
retry: () => fetch(state.pageError),
|
|
@@ -142,6 +142,7 @@ export function decorateCollection(name: string, def: { state: State; actions?:
|
|
|
142
142
|
// the query controller carries a recoverable failure. Treat each shape as
|
|
143
143
|
// one collection state so the shared UI never renders a blank catalog.
|
|
144
144
|
const backendState = backend?.controller.getState();
|
|
145
|
+
const provisional = backendState?.provisional === true;
|
|
145
146
|
const errored = s.status === 'error' || s.isErrored === true || s.errored === true || backendState?.status === 'error';
|
|
146
147
|
const domainError = typeof s.error === 'string' ? s.error.trim() : '';
|
|
147
148
|
const queryError = typeof backendState?.error === 'string' ? backendState.error.trim() : '';
|
|
@@ -177,13 +178,17 @@ export function decorateCollection(name: string, def: { state: State; actions?:
|
|
|
177
178
|
// un-narrowed failure. A re-query while typing keeps it (and the search field's focus).
|
|
178
179
|
const hideChrome = (!narrowed && !s.nextCursor && projected.loaded < COLLECTION_CHROME_MIN_ITEMS)
|
|
179
180
|
|| sourceEmpty || ((loading || errored) && rows.length === 0 && !narrowed);
|
|
180
|
-
|
|
181
|
+
// Kept with the state, so a cold boot restored from disk shows its last page and revalidates behind it.
|
|
182
|
+
const collectionShown = backend?.controller.shownPage();
|
|
183
|
+
return { ...s, ...(backend ? { collectionShown: collectionShown ?? null } : {}), collection: { ...prefs, ...projected,
|
|
181
184
|
hideChrome, noActiveCriteria: !narrowed,
|
|
182
185
|
viewName, viewMessage, saveMessage, noSavedViews: prefs.savedViews.length === 0,
|
|
183
186
|
cursor: s.pageError === true || s.loadingMore === true ? '' : s.nextCursor,
|
|
184
187
|
noMore: !s.nextCursor || s.pageError === true || s.loadingMore === true,
|
|
185
188
|
noPageError: s.pageError !== true,
|
|
186
|
-
|
|
189
|
+
// Rows read from the device ahead of the live read are shown as they are: "saved offline" is what stays on
|
|
190
|
+
// screen when the live read failed, not a first visit that is still loading.
|
|
191
|
+
noOffline: s.fromCache !== true || provisional,
|
|
187
192
|
noError: !errored,
|
|
188
193
|
noRetry: !canRetry || signInRequired,
|
|
189
194
|
noSignIn: !signInRequired,
|
|
@@ -202,7 +207,7 @@ export function decorateCollection(name: string, def: { state: State; actions?:
|
|
|
202
207
|
filtersPresented, previewPresented: !!previewId, preview: projected.items.filter(row => row.collectionId === previewId),
|
|
203
208
|
persistenceLabel: persisted.available ? copyParam('collection.onDevice', 'Saved on this device') : copyParam('collection.sessionOnly', 'Choices last for this session'),
|
|
204
209
|
noEmpty: loading || errored || sourceEmpty || domainNarrowed || projected.items.length > 0,
|
|
205
|
-
summary: backend && config.backendQuery && s.fromCache
|
|
210
|
+
summary: backend && config.backendQuery && s.fromCache && !provisional
|
|
206
211
|
? copyParam('collection.cached', 'Saved offline · may be outdated')
|
|
207
212
|
: collectionSummary({ shown: projected.items.length, loaded: projected.loaded, backend: !!(backend && config.backendQuery),
|
|
208
213
|
total: typeof s.total === 'number' ? s.total : undefined, noun: config.countNoun
|
|
@@ -345,6 +350,13 @@ export function defineBackendCollection<S extends object, Row extends object>(na
|
|
|
345
350
|
apply: (state, page) => options.apply(state as S, page as unknown as CollectionQueryState<Row>) as State,
|
|
346
351
|
});
|
|
347
352
|
const handle = (options.register ?? register)(name, definition);
|
|
353
|
+
// Registration restored the state a cold boot saved: the page it showed is this run's, before the first load. Only
|
|
354
|
+
// when the service's own rows came back with it: a bundle that keeps them out of the restore (its `notHydrated`)
|
|
355
|
+
// starts its first load as before, never from rows it chose not to restore.
|
|
356
|
+
const restored = (handle as { getState?: () => State }).getState?.();
|
|
357
|
+
const rowsKey = COLLECTIONS[name]?.rows;
|
|
358
|
+
const restoredRows = rowsKey ? restored?.[rowsKey] : undefined;
|
|
359
|
+
if (Array.isArray(restoredRows) && restoredRows.length > 0) controller.seed(restored?.collectionShown);
|
|
348
360
|
publish = () => handle.collectionDataChanged();
|
|
349
361
|
return handle;
|
|
350
362
|
}
|