@wix/himalaya-cli 0.1482.0 → 0.1484.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/auth.mjs +4 -1
  2. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/screens.mjs +4 -1
  3. package/dist/cli.mjs +71549 -71143
  4. package/dist/preview-assets.generated.bin +0 -0
  5. package/guides/qa-builds.md +22 -0
  6. package/package.json +2 -2
  7. package/templates/beauty-salon/tier5-src/_shared/collection-query.ts +13 -4
  8. package/templates/beauty-salon/tier5-src/_shared/collection-service.ts +2 -1
  9. package/templates/beauty-salon-owner/dev/index.ts +58 -19
  10. package/templates/beauty-salon-owner/tier5-src/_shared/collection-query.ts +13 -4
  11. package/templates/beauty-salon-owner/tier5-src/_shared/collection-service.ts +2 -1
  12. package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-manage/index.ts +5 -3
  13. package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-manage/logic.ts +30 -0
  14. package/templates/branded-lite/tier5-src/_shared/collection-query.ts +13 -4
  15. package/templates/branded-lite/tier5-src/_shared/collection-service.ts +2 -1
  16. package/templates/community-club/tier5-src/_shared/collection-query.ts +13 -4
  17. package/templates/community-club/tier5-src/_shared/collection-service.ts +2 -1
  18. package/templates/community-club-owner/dev/index.ts +58 -19
  19. package/templates/community-club-owner/tier5-src/_shared/collection-query.ts +13 -4
  20. package/templates/community-club-owner/tier5-src/_shared/collection-service.ts +2 -1
  21. package/templates/community-club-owner/tier5-src/himi-owner-lite-manage/index.ts +5 -3
  22. package/templates/community-club-owner/tier5-src/himi-owner-lite-manage/logic.ts +30 -0
  23. package/templates/events-meetups/tier5-src/_shared/collection-query.ts +13 -4
  24. package/templates/events-meetups/tier5-src/_shared/collection-service.ts +2 -1
  25. package/templates/events-meetups-owner/dev/index.ts +46 -16
  26. package/templates/events-meetups-owner/tier5-src/_shared/collection-query.ts +13 -4
  27. package/templates/events-meetups-owner/tier5-src/_shared/collection-service.ts +2 -1
  28. package/templates/events-meetups-owner/tier5-src/himi-owner-lite-manage/index.ts +5 -3
  29. package/templates/events-meetups-owner/tier5-src/himi-owner-lite-manage/logic.ts +30 -0
  30. package/templates/gym-fitness/tier5-src/_shared/collection-query.ts +13 -4
  31. package/templates/gym-fitness/tier5-src/_shared/collection-service.ts +2 -1
  32. package/templates/gym-fitness-owner/dev/index.ts +62 -20
  33. package/templates/gym-fitness-owner/tier5-src/_shared/collection-query.ts +13 -4
  34. package/templates/gym-fitness-owner/tier5-src/_shared/collection-service.ts +2 -1
  35. package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-manage/index.ts +5 -3
  36. package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-manage/logic.ts +30 -0
  37. package/templates/nonprofit-cause/tier5-src/_shared/collection-query.ts +13 -4
  38. package/templates/nonprofit-cause/tier5-src/_shared/collection-service.ts +2 -1
  39. package/templates/nonprofit-cause-owner/dev/index.ts +50 -17
  40. package/templates/nonprofit-cause-owner/tier5-src/_shared/collection-query.ts +13 -4
  41. package/templates/nonprofit-cause-owner/tier5-src/_shared/collection-service.ts +2 -1
  42. package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-manage/index.ts +5 -3
  43. package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-manage/logic.ts +30 -0
  44. package/templates/owner-lite/dev/index.ts +62 -20
  45. package/templates/owner-lite/tier5-src/_shared/collection-query.ts +13 -4
  46. package/templates/owner-lite/tier5-src/_shared/collection-service.ts +2 -1
  47. package/templates/owner-lite/tier5-src/himi-owner-lite-manage/index.ts +5 -3
  48. package/templates/owner-lite/tier5-src/himi-owner-lite-manage/logic.ts +30 -0
  49. package/templates/podcast-creator/tier5-src/_shared/collection-query.ts +13 -4
  50. package/templates/podcast-creator/tier5-src/_shared/collection-service.ts +2 -1
  51. package/templates/podcast-creator-owner/dev/index.ts +58 -19
  52. package/templates/podcast-creator-owner/tier5-src/_shared/collection-query.ts +13 -4
  53. package/templates/podcast-creator-owner/tier5-src/_shared/collection-service.ts +2 -1
  54. package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-manage/index.ts +5 -3
  55. package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-manage/logic.ts +30 -0
  56. package/templates/portfolio-creative/tier5-src/_shared/collection-query.ts +13 -4
  57. package/templates/portfolio-creative/tier5-src/_shared/collection-service.ts +2 -1
  58. package/templates/portfolio-creative-owner/dev/index.ts +58 -19
  59. package/templates/portfolio-creative-owner/tier5-src/_shared/collection-query.ts +13 -4
  60. package/templates/portfolio-creative-owner/tier5-src/_shared/collection-service.ts +2 -1
  61. package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-manage/index.ts +5 -3
  62. package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-manage/logic.ts +30 -0
  63. package/templates/restaurant/tier5-src/_shared/collection-query.ts +13 -4
  64. package/templates/restaurant/tier5-src/_shared/collection-service.ts +2 -1
  65. package/templates/restaurant-owner/dev/index.ts +62 -20
  66. package/templates/restaurant-owner/tier5-src/_shared/collection-query.ts +13 -4
  67. package/templates/restaurant-owner/tier5-src/_shared/collection-service.ts +2 -1
  68. package/templates/restaurant-owner/tier5-src/himi-owner-lite-manage/index.ts +5 -3
  69. package/templates/restaurant-owner/tier5-src/himi-owner-lite-manage/logic.ts +30 -0
  70. package/templates/retail-store/tier5-src/_shared/collection-query.ts +13 -4
  71. package/templates/retail-store/tier5-src/_shared/collection-service.ts +2 -1
  72. package/templates/retail-store-owner/dev/index.ts +54 -18
  73. package/templates/retail-store-owner/tier5-src/_shared/collection-query.ts +13 -4
  74. package/templates/retail-store-owner/tier5-src/_shared/collection-service.ts +2 -1
  75. package/templates/retail-store-owner/tier5-src/himi-owner-lite-manage/index.ts +5 -3
  76. package/templates/retail-store-owner/tier5-src/himi-owner-lite-manage/logic.ts +30 -0
  77. package/templates/yoga-studio/tier5-src/_shared/collection-query.ts +13 -4
  78. package/templates/yoga-studio/tier5-src/_shared/collection-service.ts +2 -1
  79. package/templates/yoga-studio-owner/dev/index.ts +50 -17
  80. package/templates/yoga-studio-owner/tier5-src/_shared/collection-query.ts +13 -4
  81. package/templates/yoga-studio-owner/tier5-src/_shared/collection-service.ts +2 -1
  82. package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-manage/index.ts +5 -3
  83. package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-manage/logic.ts +30 -0
Binary file
@@ -1095,6 +1095,28 @@ himi dist --app <app> --platform android --aab --wait --target bo
1095
1095
  `DIST_ANDROID_FORMAT=aab` in its environment no longer turns an APK job into a bundle; the check
1096
1096
  stays for a runner older than that rule. A later delivery that passes every check (a retry on a
1097
1097
  clean machine) clears the verdict.
1098
+ - **A Containers app's bundle is a store build.** For an app bound to a Wix site, the bundle is the
1099
+ file its owner uploads to Google Play, so it is checked last before the build starts: the caller
1100
+ owns the app's site (a self-serve caller's only path to a bundle; any other self-serve `--aab` is
1101
+ `403 android_format_not_allowed`) (the `--signing byo` check, so `--aab` sends your Wix login), the app holds its
1102
+ site's store app (`PUT /v1/admin/site/store-app`), and Wix agrees (`ValidateStoreBuild`: a package
1103
+ name is saved, and the site's plan allows publishing the app yourself). That check locks the
1104
+ package name at the app's first store build, and the bundle is built under it. Refusals:
1105
+ `store_build_android_only`, `store_build_incompatible_options` (`--signing byo`) and
1106
+ `store_build_shell_unsupported` (any shell but `branded-lite`, the one that both builds under the
1107
+ contract's applicationId and targets the API level Google Play requires) 400, `store_build_site_forbidden` 403,
1108
+ `store_id_missing`, `store_app_site_taken`, `premium_plan_required` and
1109
+ `release_predates_store_builds` (the release was built from a commit older than store builds, so
1110
+ its bundle would fail or Play would refuse it; checked before the name is locked — publish a new
1111
+ release and build again) 409,
1112
+ `store_builds_unconfigured` 501, `store_builds_unavailable` 503 (also when the app's site binding
1113
+ cannot be read, or Wix answers no usable package name). Each refusal carries `code`, equal to
1114
+ `error`.
1115
+ The same checks are available WITHOUT a build, for the Containers editor to ask before it
1116
+ activates a draft release: `GET /v1/admin/site/store-build-check?app=&site=[&shell=]` answers
1117
+ `200 { ok: true, android: { packageName } }` or the same refusals, through `ValidateStoreBuild`'s
1118
+ check-only mode, so it locks nothing. Until that mode is wired it answers
1119
+ `501 store_builds_unconfigured`; it never falls back to the locking call.
1098
1120
 
1099
1121
  **Why the Wix production key, and only for the bundle.** There are two Android keys: the committed
1100
1122
  QA keystore and the Wix production key (WixOne). WixOne is the Play **upload** key every Branded
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wix/himalaya-cli",
3
- "version": "0.1482.0",
3
+ "version": "0.1484.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": "fd8ba72a1838f65e8e437d82939b1734daa3b07881672fd7f2604e4e"
74
+ "falconPackageHash": "310eec6f7de3de9c4b4e96b9c25cb5913642adb3b8408fde6c3fc8a2"
75
75
  }
@@ -84,14 +84,17 @@ export function createCollectionQuery<Row>(source: CollectionQuerySource<Row>, p
84
84
  }
85
85
  return result;
86
86
  };
87
- const fetch = async (append: boolean): Promise<void> => {
87
+ /** [keepRows]: a search typed over the rows on screen leaves them there until its answer arrives (status stays
88
+ * `loading`, and nothing pages under the old answer), so the list is not blanked on each keystroke. */
89
+ const fetch = async (append: boolean, keepRows = false): Promise<void> => {
88
90
  if (disposed || (append && (state.loadingMore || state.status === 'loading' || !state.nextCursor))) return;
89
91
  const revision = append ? generation : ++generation;
90
92
  const cursor = append ? state.nextCursor : '';
91
93
  const criteria = copyQuery(state.criteria);
92
94
  if (!append) loadedPages = 0;
93
95
  state = { ...state, status: append ? 'ready' : 'loading', loadingMore: append, pageError: false, error: '',
94
- ...(append ? {} : { items: [], nextCursor: '', total: undefined, metadata: undefined, complete: false, provisional: false }) };
96
+ ...(append ? {} : keepRows ? { nextCursor: '', complete: false, provisional: false }
97
+ : { items: [], nextCursor: '', total: undefined, metadata: undefined, complete: false, provisional: false }) };
95
98
  emit();
96
99
  try {
97
100
  if (!append && source.cached) {
@@ -117,7 +120,9 @@ export function createCollectionQuery<Row>(source: CollectionQuerySource<Row>, p
117
120
  clearedByError = clear || /HTTP 40[13]\b/.test(String(error));
118
121
  if (clearedByError) lastShown = undefined;
119
122
  if (clear) loadedPages = 0;
120
- state = { ...state, ...(clear ? { items: [], nextCursor: '', total: undefined, metadata: undefined } : {}),
123
+ // The rows a typed search kept on screen while it ran were never its answer: a failed one shows none of them.
124
+ const drop = clear || keepRows;
125
+ state = { ...state, ...(drop ? { items: [], nextCursor: '', total: undefined, metadata: undefined } : {}),
121
126
  status: append && !clear ? 'ready' : 'error', loadingMore: false, pageError: append && !clear, complete: false,
122
127
  provisional: false, error: source.errorMessage?.(error) ?? '' };
123
128
  }
@@ -134,9 +139,13 @@ export function createCollectionQuery<Row>(source: CollectionQuerySource<Row>, p
134
139
  const count = restorePages && same ? Math.max(loadedPages, restoringPages, 1) : 1;
135
140
  const showing = lastShown && sameCriteria(lastShown.state.criteria, next) ? lastShown : undefined;
136
141
  restoringPages = count;
142
+ // Only the search text changed: the user is typing over the rows on screen, also while the last keystroke's
143
+ // search is still out.
144
+ const typing = !same && (state.status === 'ready' || state.status === 'loading')
145
+ && sameCriteria({ ...state.criteria, query: '' }, { ...next, query: '' });
137
146
  state = { ...state, criteria: next };
138
147
  if (revalidate && showing) { revalidating = generation + 1; clearedByError = false; }
139
- const pending = fetch(false);
148
+ const pending = fetch(false, typing);
140
149
  const revision = generation;
141
150
  await pending;
142
151
  // Re-appearance revalidates every previously opened page using fresh
@@ -194,7 +194,8 @@ export function decorateCollection(name: string, def: { state: State; actions?:
194
194
  noSignIn: !signInRequired,
195
195
  errorTitle: signInRequired ? copyParam('collection.signInTitle', 'Sign in to see this') : copyParam('collection.loadErrorTitle', 'This collection could not load'),
196
196
  errorMessage,
197
- noLoading: !loading,
197
+ // A skeleton only with nothing to show: a search re-running over the rows on screen keeps them.
198
+ noLoading: !loading || rows.length > 0,
198
199
  // De-dup against a domain that paints its OWN empty block (screen-kit emptyBlock is
199
200
  // gated on the domain's top-level `noEmpty`): only one empty state ever shows.
200
201
  noEmptySource: !sourceEmpty || s.noEmpty === false,
@@ -49603,7 +49603,10 @@ const SCREENS = runtimeSetting({
49603
49603
  "action": {
49604
49604
  "actionId": "himi.open",
49605
49605
  "params": {
49606
- "screenId": "himi_owner_lite_sites"
49606
+ "screenId": "himi_owner_lite_sites",
49607
+ "title": "Your salons",
49608
+ "subtitle": "Choose a salon to run",
49609
+ "search": "button"
49607
49610
  }
49608
49611
  }
49609
49612
  }
@@ -50789,7 +50792,10 @@ const SCREENS = runtimeSetting({
50789
50792
  "actionId": "himi.open",
50790
50793
  "params": {
50791
50794
  "screenId": "himi_owner_lite_orders",
50792
- "collectionFilter": "value:Recent 7 days"
50795
+ "collectionFilter": "value:Recent 7 days",
50796
+ "title": "Retail orders",
50797
+ "subtitle": "Take-home products your clients bought.",
50798
+ "search": "button"
50793
50799
  }
50794
50800
  },
50795
50801
  "pressFeedback": "scale",
@@ -50858,7 +50864,10 @@ const SCREENS = runtimeSetting({
50858
50864
  "actionId": "himi.open",
50859
50865
  "params": {
50860
50866
  "screenId": "himi_owner_lite_orders",
50861
- "collectionFilter": "value:Recent 7 days"
50867
+ "collectionFilter": "value:Recent 7 days",
50868
+ "title": "Retail orders",
50869
+ "subtitle": "Take-home products your clients bought.",
50870
+ "search": "button"
50862
50871
  }
50863
50872
  },
50864
50873
  "pressFeedback": "scale",
@@ -51075,7 +51084,10 @@ const SCREENS = runtimeSetting({
51075
51084
  "actionId": "himi.open",
51076
51085
  "params": {
51077
51086
  "screenId": "himi_owner_lite_appointments",
51078
- "collectionFilter": "all"
51087
+ "collectionFilter": "all",
51088
+ "title": "The chair",
51089
+ "subtitle": "Which client is in, with whom, and when.",
51090
+ "search": "button"
51079
51091
  }
51080
51092
  },
51081
51093
  "pressFeedback": "scale",
@@ -51156,7 +51168,10 @@ const SCREENS = runtimeSetting({
51156
51168
  "actionId": "himi.open",
51157
51169
  "params": {
51158
51170
  "screenId": "himi_owner_lite_products",
51159
- "collectionFilter": "all"
51171
+ "collectionFilter": "all",
51172
+ "title": "Retail",
51173
+ "subtitle": "Shampoos, tools and take-home lines on your shelf.",
51174
+ "search": "button"
51160
51175
  }
51161
51176
  },
51162
51177
  "pressFeedback": "scale",
@@ -51225,7 +51240,10 @@ const SCREENS = runtimeSetting({
51225
51240
  "actionId": "himi.open",
51226
51241
  "params": {
51227
51242
  "screenId": "himi_owner_lite_posts",
51228
- "collectionFilter": "all"
51243
+ "collectionFilter": "all",
51244
+ "title": "Salon notes",
51245
+ "subtitle": "Looks, offers and studio news — drafts and published.",
51246
+ "search": "button"
51229
51247
  }
51230
51248
  },
51231
51249
  "pressFeedback": "scale",
@@ -51294,7 +51312,10 @@ const SCREENS = runtimeSetting({
51294
51312
  "actionId": "himi.open",
51295
51313
  "params": {
51296
51314
  "screenId": "himi_owner_lite_sites",
51297
- "collectionFilter": "all"
51315
+ "collectionFilter": "all",
51316
+ "title": "Your salons",
51317
+ "subtitle": "Choose a salon to run",
51318
+ "search": "button"
51298
51319
  }
51299
51320
  },
51300
51321
  "pressFeedback": "scale",
@@ -51388,7 +51409,9 @@ const SCREENS = runtimeSetting({
51388
51409
  "action": {
51389
51410
  "actionId": "himi.open",
51390
51411
  "params": {
51391
- "screenId": "himi_owner_lite_brief"
51412
+ "screenId": "himi_owner_lite_brief",
51413
+ "title": "Today’s chair",
51414
+ "subtitle": "Gaps, no-show risks and low stock, laid out for you."
51392
51415
  }
51393
51416
  }
51394
51417
  }
@@ -51743,7 +51766,8 @@ const SCREENS = runtimeSetting({
51743
51766
  "action": {
51744
51767
  "actionId": "himi.open",
51745
51768
  "params": {
51746
- "screenId": "himi_owner_lite_manage"
51769
+ "screenId": "himi_owner_lite_manage",
51770
+ "search": "drawer"
51747
51771
  }
51748
51772
  }
51749
51773
  }
@@ -52770,7 +52794,10 @@ const SCREENS = runtimeSetting({
52770
52794
  "onTap": {
52771
52795
  "actionId": "himi.open",
52772
52796
  "params": {
52773
- "screenId": "himi_owner_lite_search"
52797
+ "screenId": "himi_owner_lite_search",
52798
+ "title": "Search records",
52799
+ "subtitle": "Across your business areas",
52800
+ "search": "drawer"
52774
52801
  }
52775
52802
  },
52776
52803
  "pressFeedback": "scale",
@@ -52970,7 +52997,9 @@ const SCREENS = runtimeSetting({
52970
52997
  "onTap": {
52971
52998
  "actionId": "himi.open",
52972
52999
  "params": {
52973
- "screenId": "himi_owner_lite_analytics"
53000
+ "screenId": "himi_owner_lite_analytics",
53001
+ "title": "Analytics",
53002
+ "subtitle": "How your site is doing"
52974
53003
  }
52975
53004
  },
52976
53005
  "pressFeedback": "scale",
@@ -52994,7 +53023,7 @@ const SCREENS = runtimeSetting({
52994
53023
  "onAppear": {
52995
53024
  "actionId": "tier5.himiOwnerLiteManage.load",
52996
53025
  "params": {
52997
- "areas": "[{\"id\":\"store\",\"title\":\"Retail\",\"subtitle\":\"Products, inventory and sales\",\"groupId\":\"commerce\",\"group\":\"Commerce\",\"screenId\":\"himi_owner_lite_products\",\"icon\":\"bag\",\"metricKey\":\"products\",\"countLabel\":\"products\",\"createScreenId\":\"himi_owner_lite_product_new\",\"createLabel\":\"Create product\",\"handoffLabel\":\"Open in Wix: Retail\"},{\"id\":\"himi_owner_lite_orders\",\"title\":\"Retail orders\",\"subtitle\":\"Review and complete customer orders\",\"groupId\":\"commerce\",\"group\":\"Commerce\",\"screenId\":\"himi_owner_lite_orders\",\"icon\":\"cart\",\"metricKey\":\"orders\",\"countLabel\":\"orders\",\"handoffLabel\":\"Open in Wix: Retail orders\"},{\"id\":\"bookings\",\"title\":\"Treatments\",\"subtitle\":\"Services and your schedule\",\"groupId\":\"scheduling\",\"group\":\"Scheduling\",\"screenId\":\"himi_owner_lite_services\",\"icon\":\"calendar\",\"metricKey\":\"services\",\"countLabel\":\"services\",\"handoffLabel\":\"Open in Wix: Treatments\"},{\"id\":\"himi_owner_lite_appointments\",\"title\":\"The chair\",\"subtitle\":\"Your upcoming appointments\",\"groupId\":\"scheduling\",\"group\":\"Scheduling\",\"screenId\":\"himi_owner_lite_appointments\",\"icon\":\"clock\",\"metricKey\":\"appointments\",\"countLabel\":\"appointments\",\"handoffLabel\":\"Open in Wix: The chair\"},{\"id\":\"blog\",\"title\":\"Salon notes\",\"subtitle\":\"Stories and publishing\",\"groupId\":\"content\",\"group\":\"Content\",\"screenId\":\"himi_owner_lite_posts\",\"icon\":\"newspaper\",\"metricKey\":\"posts\",\"countLabel\":\"posts\",\"createScreenId\":\"himi_owner_lite_post_new\",\"createLabel\":\"Write post\",\"handoffLabel\":\"Open in Wix: Salon notes\"},{\"id\":\"coupons\",\"title\":\"Coupons\",\"subtitle\":\"Offers, codes and usage\",\"groupId\":\"commerce\",\"group\":\"Commerce\",\"screenId\":\"himi_owner_lite_module_coupons\",\"icon\":\"tag\",\"metricKey\":\"module:coupons\",\"countLabel\":\"coupons\",\"handoffLabel\":\"Open in Wix: Coupons\"},{\"id\":\"invoices\",\"title\":\"Invoices\",\"subtitle\":\"Customers, balances and payment status\",\"groupId\":\"commerce\",\"group\":\"Commerce\",\"screenId\":\"himi_owner_lite_module_invoices\",\"icon\":\"document\",\"metricKey\":\"module:invoices\",\"countLabel\":\"invoices\",\"handoffLabel\":\"Open in Wix: Invoices\"},{\"id\":\"giftCards\",\"title\":\"Gift cards\",\"subtitle\":\"Balances, recipients and expiration\",\"groupId\":\"commerce\",\"group\":\"Commerce\",\"screenId\":\"himi_owner_lite_module_giftCards\",\"icon\":\"ticket\",\"metricKey\":\"module:giftCards\",\"countLabel\":\"gift cards\",\"handoffLabel\":\"Open in Wix: Gift cards\"},{\"id\":\"contacts\",\"title\":\"Contacts\",\"subtitle\":\"Your customer relationships\",\"groupId\":\"people-and-community\",\"group\":\"People and community\",\"screenId\":\"himi_owner_lite_module_contacts\",\"icon\":\"person\",\"metricKey\":\"module:contacts\",\"countLabel\":\"contacts\",\"handoffLabel\":\"Open in Wix: Contacts\"},{\"id\":\"members\",\"title\":\"Members\",\"subtitle\":\"Membership access and profiles\",\"groupId\":\"people-and-community\",\"group\":\"People and community\",\"screenId\":\"himi_owner_lite_module_members\",\"icon\":\"person\",\"metricKey\":\"module:members\",\"countLabel\":\"members\",\"handoffLabel\":\"Open in Wix: Members\"},{\"id\":\"groups\",\"title\":\"Groups\",\"subtitle\":\"Communities and conversations\",\"groupId\":\"people-and-community\",\"group\":\"People and community\",\"screenId\":\"himi_owner_lite_module_groups\",\"icon\":\"person\",\"metricKey\":\"module:groups\",\"countLabel\":\"groups\",\"handoffLabel\":\"Open in Wix: Groups\"},{\"id\":\"reviews\",\"title\":\"Reviews\",\"subtitle\":\"Published and pending customer feedback\",\"groupId\":\"people-and-community\",\"group\":\"People and community\",\"screenId\":\"himi_owner_lite_module_reviews\",\"icon\":\"star\",\"metricKey\":\"module:reviews\",\"countLabel\":\"reviews\",\"handoffLabel\":\"Open in Wix: Reviews\"},{\"id\":\"plans\",\"title\":\"Plans\",\"subtitle\":\"Pricing, benefits and visibility\",\"groupId\":\"membership-and-learning\",\"group\":\"Membership and learning\",\"screenId\":\"himi_owner_lite_module_plans\",\"icon\":\"bookmark\",\"metricKey\":\"module:plans\",\"countLabel\":\"plans\",\"handoffLabel\":\"Open in Wix: Plans\"},{\"id\":\"subscriptions\",\"title\":\"Subscriptions\",\"subtitle\":\"Member plans and subscription status\",\"groupId\":\"membership-and-learning\",\"group\":\"Membership and learning\",\"screenId\":\"himi_owner_lite_module_subscriptions\",\"icon\":\"bookmark\",\"metricKey\":\"module:subscriptions\",\"countLabel\":\"subscriptions\",\"handoffLabel\":\"Open in Wix: Subscriptions\"},{\"id\":\"programs\",\"title\":\"Programs\",\"subtitle\":\"Learning experiences and participation\",\"groupId\":\"membership-and-learning\",\"group\":\"Membership and learning\",\"screenId\":\"himi_owner_lite_module_programs\",\"icon\":\"play\",\"metricKey\":\"module:programs\",\"countLabel\":\"programs\",\"handoffLabel\":\"Open in Wix: Programs\"},{\"id\":\"badges\",\"title\":\"Badges\",\"subtitle\":\"Recognition, access and member badges\",\"groupId\":\"membership-and-learning\",\"group\":\"Membership and learning\",\"screenId\":\"himi_owner_lite_module_badges\",\"icon\":\"star\",\"metricKey\":\"module:badges\",\"countLabel\":\"badges\",\"handoffLabel\":\"Open in Wix: Badges\"},{\"id\":\"reservations\",\"title\":\"Reservations\",\"subtitle\":\"Guests, tables and arrival times\",\"groupId\":\"scheduling\",\"group\":\"Scheduling\",\"screenId\":\"himi_owner_lite_module_reservations\",\"icon\":\"calendar\",\"metricKey\":\"module:reservations\",\"countLabel\":\"reservations\",\"handoffLabel\":\"Open in Wix: Reservations\"},{\"id\":\"forms\",\"title\":\"Forms\",\"subtitle\":\"Forms and their configuration\",\"groupId\":\"content\",\"group\":\"Content\",\"screenId\":\"himi_owner_lite_module_forms\",\"icon\":\"document\",\"metricKey\":\"module:forms\",\"countLabel\":\"forms\",\"handoffLabel\":\"Open in Wix: Forms\"},{\"id\":\"portfolio\",\"title\":\"Portfolio\",\"subtitle\":\"Projects, stories and galleries\",\"groupId\":\"content\",\"group\":\"Content\",\"screenId\":\"himi_owner_lite_module_portfolio\",\"icon\":\"image\",\"metricKey\":\"module:portfolio\",\"countLabel\":\"portfolio\",\"handoffLabel\":\"Open in Wix: Portfolio\"},{\"id\":\"donationCampaigns\",\"title\":\"Donation campaigns\",\"subtitle\":\"Campaign goals, status and fundraising\",\"groupId\":\"content\",\"group\":\"Content\",\"screenId\":\"himi_owner_lite_module_donationCampaigns\",\"icon\":\"heart\",\"metricKey\":\"module:donationCampaigns\",\"countLabel\":\"donation campaigns\",\"handoffLabel\":\"Open in Wix: Donation campaigns\"}]",
53026
+ "areas": "[{\"id\":\"store\",\"title\":\"Retail\",\"subtitle\":\"Products, inventory and sales\",\"groupId\":\"commerce\",\"group\":\"Commerce\",\"screenId\":\"himi_owner_lite_products\",\"icon\":\"bag\",\"metricKey\":\"products\",\"countLabel\":\"products\",\"createScreenId\":\"himi_owner_lite_product_new\",\"createLabel\":\"Create product\",\"handoffLabel\":\"Open in Wix: Retail\",\"navigation\":{\"title\":\"Retail\",\"subtitle\":\"Shampoos, tools and take-home lines on your shelf.\",\"search\":\"button\"}},{\"id\":\"himi_owner_lite_orders\",\"title\":\"Retail orders\",\"subtitle\":\"Review and complete customer orders\",\"groupId\":\"commerce\",\"group\":\"Commerce\",\"screenId\":\"himi_owner_lite_orders\",\"icon\":\"cart\",\"metricKey\":\"orders\",\"countLabel\":\"orders\",\"handoffLabel\":\"Open in Wix: Retail orders\",\"navigation\":{\"title\":\"Retail orders\",\"subtitle\":\"Take-home products your clients bought.\",\"search\":\"button\"}},{\"id\":\"bookings\",\"title\":\"Treatments\",\"subtitle\":\"Services and your schedule\",\"groupId\":\"scheduling\",\"group\":\"Scheduling\",\"screenId\":\"himi_owner_lite_services\",\"icon\":\"calendar\",\"metricKey\":\"services\",\"countLabel\":\"services\",\"handoffLabel\":\"Open in Wix: Treatments\",\"navigation\":{\"title\":\"Treatments\",\"subtitle\":\"Every treatment on the menu — cuts, colour, brows and the rest.\",\"search\":\"button\"}},{\"id\":\"himi_owner_lite_appointments\",\"title\":\"The chair\",\"subtitle\":\"Your upcoming appointments\",\"groupId\":\"scheduling\",\"group\":\"Scheduling\",\"screenId\":\"himi_owner_lite_appointments\",\"icon\":\"clock\",\"metricKey\":\"appointments\",\"countLabel\":\"appointments\",\"handoffLabel\":\"Open in Wix: The chair\",\"navigation\":{\"title\":\"The chair\",\"subtitle\":\"Which client is in, with whom, and when.\",\"search\":\"button\"}},{\"id\":\"blog\",\"title\":\"Salon notes\",\"subtitle\":\"Stories and publishing\",\"groupId\":\"content\",\"group\":\"Content\",\"screenId\":\"himi_owner_lite_posts\",\"icon\":\"newspaper\",\"metricKey\":\"posts\",\"countLabel\":\"posts\",\"createScreenId\":\"himi_owner_lite_post_new\",\"createLabel\":\"Write post\",\"handoffLabel\":\"Open in Wix: Salon notes\",\"navigation\":{\"title\":\"Salon notes\",\"subtitle\":\"Looks, offers and studio news — drafts and published.\",\"search\":\"button\"}},{\"id\":\"coupons\",\"title\":\"Coupons\",\"subtitle\":\"Offers, codes and usage\",\"groupId\":\"commerce\",\"group\":\"Commerce\",\"screenId\":\"himi_owner_lite_module_coupons\",\"icon\":\"tag\",\"metricKey\":\"module:coupons\",\"countLabel\":\"coupons\",\"handoffLabel\":\"Open in Wix: Coupons\",\"navigation\":{\"title\":\"Coupons\",\"subtitle\":\"Offers, codes and usage\",\"search\":\"button\"}},{\"id\":\"invoices\",\"title\":\"Invoices\",\"subtitle\":\"Customers, balances and payment status\",\"groupId\":\"commerce\",\"group\":\"Commerce\",\"screenId\":\"himi_owner_lite_module_invoices\",\"icon\":\"document\",\"metricKey\":\"module:invoices\",\"countLabel\":\"invoices\",\"handoffLabel\":\"Open in Wix: Invoices\",\"navigation\":{\"title\":\"Invoices\",\"subtitle\":\"Customers, balances and payment status\",\"search\":\"button\"}},{\"id\":\"giftCards\",\"title\":\"Gift cards\",\"subtitle\":\"Balances, recipients and expiration\",\"groupId\":\"commerce\",\"group\":\"Commerce\",\"screenId\":\"himi_owner_lite_module_giftCards\",\"icon\":\"ticket\",\"metricKey\":\"module:giftCards\",\"countLabel\":\"gift cards\",\"handoffLabel\":\"Open in Wix: Gift cards\",\"navigation\":{\"title\":\"Gift cards\",\"subtitle\":\"Balances, recipients and expiration\",\"search\":\"button\"}},{\"id\":\"contacts\",\"title\":\"Contacts\",\"subtitle\":\"Your customer relationships\",\"groupId\":\"people-and-community\",\"group\":\"People and community\",\"screenId\":\"himi_owner_lite_module_contacts\",\"icon\":\"person\",\"metricKey\":\"module:contacts\",\"countLabel\":\"contacts\",\"handoffLabel\":\"Open in Wix: Contacts\",\"navigation\":{\"title\":\"Contacts\",\"subtitle\":\"Your customer relationships\",\"search\":\"button\"}},{\"id\":\"members\",\"title\":\"Members\",\"subtitle\":\"Membership access and profiles\",\"groupId\":\"people-and-community\",\"group\":\"People and community\",\"screenId\":\"himi_owner_lite_module_members\",\"icon\":\"person\",\"metricKey\":\"module:members\",\"countLabel\":\"members\",\"handoffLabel\":\"Open in Wix: Members\",\"navigation\":{\"title\":\"Members\",\"subtitle\":\"Membership access and profiles\",\"search\":\"button\"}},{\"id\":\"groups\",\"title\":\"Groups\",\"subtitle\":\"Communities and conversations\",\"groupId\":\"people-and-community\",\"group\":\"People and community\",\"screenId\":\"himi_owner_lite_module_groups\",\"icon\":\"person\",\"metricKey\":\"module:groups\",\"countLabel\":\"groups\",\"handoffLabel\":\"Open in Wix: Groups\",\"navigation\":{\"title\":\"Groups\",\"subtitle\":\"Communities and conversations\",\"search\":\"button\"}},{\"id\":\"reviews\",\"title\":\"Reviews\",\"subtitle\":\"Published and pending customer feedback\",\"groupId\":\"people-and-community\",\"group\":\"People and community\",\"screenId\":\"himi_owner_lite_module_reviews\",\"icon\":\"star\",\"metricKey\":\"module:reviews\",\"countLabel\":\"reviews\",\"handoffLabel\":\"Open in Wix: Reviews\",\"navigation\":{\"title\":\"Reviews\",\"subtitle\":\"Published and pending customer feedback\",\"search\":\"button\"}},{\"id\":\"plans\",\"title\":\"Plans\",\"subtitle\":\"Pricing, benefits and visibility\",\"groupId\":\"membership-and-learning\",\"group\":\"Membership and learning\",\"screenId\":\"himi_owner_lite_module_plans\",\"icon\":\"bookmark\",\"metricKey\":\"module:plans\",\"countLabel\":\"plans\",\"handoffLabel\":\"Open in Wix: Plans\",\"navigation\":{\"title\":\"Plans\",\"subtitle\":\"Pricing, benefits and visibility\",\"search\":\"button\"}},{\"id\":\"subscriptions\",\"title\":\"Subscriptions\",\"subtitle\":\"Member plans and subscription status\",\"groupId\":\"membership-and-learning\",\"group\":\"Membership and learning\",\"screenId\":\"himi_owner_lite_module_subscriptions\",\"icon\":\"bookmark\",\"metricKey\":\"module:subscriptions\",\"countLabel\":\"subscriptions\",\"handoffLabel\":\"Open in Wix: Subscriptions\",\"navigation\":{\"title\":\"Subscriptions\",\"subtitle\":\"Member plans and subscription status\",\"search\":\"button\"}},{\"id\":\"programs\",\"title\":\"Programs\",\"subtitle\":\"Learning experiences and participation\",\"groupId\":\"membership-and-learning\",\"group\":\"Membership and learning\",\"screenId\":\"himi_owner_lite_module_programs\",\"icon\":\"play\",\"metricKey\":\"module:programs\",\"countLabel\":\"programs\",\"handoffLabel\":\"Open in Wix: Programs\",\"navigation\":{\"title\":\"Programs\",\"subtitle\":\"Learning experiences and participation\",\"search\":\"button\"}},{\"id\":\"badges\",\"title\":\"Badges\",\"subtitle\":\"Recognition, access and member badges\",\"groupId\":\"membership-and-learning\",\"group\":\"Membership and learning\",\"screenId\":\"himi_owner_lite_module_badges\",\"icon\":\"star\",\"metricKey\":\"module:badges\",\"countLabel\":\"badges\",\"handoffLabel\":\"Open in Wix: Badges\",\"navigation\":{\"title\":\"Badges\",\"subtitle\":\"Recognition, access and member badges\",\"search\":\"button\"}},{\"id\":\"reservations\",\"title\":\"Reservations\",\"subtitle\":\"Guests, tables and arrival times\",\"groupId\":\"scheduling\",\"group\":\"Scheduling\",\"screenId\":\"himi_owner_lite_module_reservations\",\"icon\":\"calendar\",\"metricKey\":\"module:reservations\",\"countLabel\":\"reservations\",\"handoffLabel\":\"Open in Wix: Reservations\",\"navigation\":{\"title\":\"Reservations\",\"subtitle\":\"Guests, tables and arrival times\",\"search\":\"button\"}},{\"id\":\"forms\",\"title\":\"Forms\",\"subtitle\":\"Forms and their configuration\",\"groupId\":\"content\",\"group\":\"Content\",\"screenId\":\"himi_owner_lite_module_forms\",\"icon\":\"document\",\"metricKey\":\"module:forms\",\"countLabel\":\"forms\",\"handoffLabel\":\"Open in Wix: Forms\",\"navigation\":{\"title\":\"Forms\",\"subtitle\":\"Forms and their configuration\",\"search\":\"button\"}},{\"id\":\"portfolio\",\"title\":\"Portfolio\",\"subtitle\":\"Projects, stories and galleries\",\"groupId\":\"content\",\"group\":\"Content\",\"screenId\":\"himi_owner_lite_module_portfolio\",\"icon\":\"image\",\"metricKey\":\"module:portfolio\",\"countLabel\":\"portfolio\",\"handoffLabel\":\"Open in Wix: Portfolio\",\"navigation\":{\"title\":\"Portfolio\",\"subtitle\":\"Projects, stories and galleries\",\"search\":\"button\"}},{\"id\":\"donationCampaigns\",\"title\":\"Donation campaigns\",\"subtitle\":\"Campaign goals, status and fundraising\",\"groupId\":\"content\",\"group\":\"Content\",\"screenId\":\"himi_owner_lite_module_donationCampaigns\",\"icon\":\"heart\",\"metricKey\":\"module:donationCampaigns\",\"countLabel\":\"donation campaigns\",\"handoffLabel\":\"Open in Wix: Donation campaigns\",\"navigation\":{\"title\":\"Donation campaigns\",\"subtitle\":\"Campaign goals, status and fundraising\",\"search\":\"button\"}}]",
52998
53027
  "copy": "{\"ownerManage.blogLabel\":\"Salon notes\",\"ownerManage.bookingsLabel\":\"Treatments\",\"ownerManage.storeLabel\":\"Retail\",\"ownerManage.title\":\"Salon\"}",
52999
53028
  "businessName": "__HIMI_APP_NAME__"
53000
53029
  }
@@ -55508,7 +55537,9 @@ const SCREENS = runtimeSetting({
55508
55537
  "onTap": {
55509
55538
  "actionId": "himi.open",
55510
55539
  "params": {
55511
- "screenId": "himi_owner_lite_analytics_report"
55540
+ "screenId": "himi_owner_lite_analytics_report",
55541
+ "title": "Build a report",
55542
+ "subtitle": "Any subject area your site tracks"
55512
55543
  }
55513
55544
  },
55514
55545
  "pressFeedback": "highlight"
@@ -56664,7 +56695,8 @@ const SCREENS = runtimeSetting({
56664
56695
  "action": {
56665
56696
  "actionId": "himi.open",
56666
56697
  "params": {
56667
- "screenId": "himi_owner_lite_manage"
56698
+ "screenId": "himi_owner_lite_manage",
56699
+ "search": "drawer"
56668
56700
  }
56669
56701
  }
56670
56702
  }
@@ -57301,7 +57333,8 @@ const SCREENS = runtimeSetting({
57301
57333
  "action": {
57302
57334
  "actionId": "himi.open",
57303
57335
  "params": {
57304
- "screenId": "himi_owner_lite_manage"
57336
+ "screenId": "himi_owner_lite_manage",
57337
+ "search": "drawer"
57305
57338
  }
57306
57339
  }
57307
57340
  }
@@ -62498,7 +62531,9 @@ const SCREENS = runtimeSetting({
62498
62531
  "onTap": {
62499
62532
  "actionId": "himi.open",
62500
62533
  "params": {
62501
- "screenId": "himi_owner_lite_aria_history"
62534
+ "screenId": "himi_owner_lite_aria_history",
62535
+ "title": "Conversations",
62536
+ "subtitle": "Pick up where you left off."
62502
62537
  }
62503
62538
  },
62504
62539
  "accessibilityLabel": "History",
@@ -63805,7 +63840,8 @@ const SCREENS = runtimeSetting({
63805
63840
  "actionId": "himi.open",
63806
63841
  "params": {
63807
63842
  "screenId": "himi_owner_lite_aria",
63808
- "conversationId": "$item.id"
63843
+ "conversationId": "$item.id",
63844
+ "title": "Aria"
63809
63845
  }
63810
63846
  }
63811
63847
  }
@@ -63943,7 +63979,9 @@ const SCREENS = runtimeSetting({
63943
63979
  "action": {
63944
63980
  "actionId": "himi.open",
63945
63981
  "params": {
63946
- "screenId": "himi_owner_lite_overview"
63982
+ "screenId": "himi_owner_lite_overview",
63983
+ "title": "Across your businesses",
63984
+ "subtitle": "What needs you, everywhere at once"
63947
63985
  }
63948
63986
  }
63949
63987
  }
@@ -84799,7 +84837,8 @@ const TEMPLATED: { pattern: string; display: string; liveSeed?: LiveSeed; static
84799
84837
  "action": {
84800
84838
  "actionId": "himi.open",
84801
84839
  "params": {
84802
- "screenId": "himi_owner_lite_manage"
84840
+ "screenId": "himi_owner_lite_manage",
84841
+ "search": "drawer"
84803
84842
  }
84804
84843
  }
84805
84844
  }
@@ -84,14 +84,17 @@ export function createCollectionQuery<Row>(source: CollectionQuerySource<Row>, p
84
84
  }
85
85
  return result;
86
86
  };
87
- const fetch = async (append: boolean): Promise<void> => {
87
+ /** [keepRows]: a search typed over the rows on screen leaves them there until its answer arrives (status stays
88
+ * `loading`, and nothing pages under the old answer), so the list is not blanked on each keystroke. */
89
+ const fetch = async (append: boolean, keepRows = false): Promise<void> => {
88
90
  if (disposed || (append && (state.loadingMore || state.status === 'loading' || !state.nextCursor))) return;
89
91
  const revision = append ? generation : ++generation;
90
92
  const cursor = append ? state.nextCursor : '';
91
93
  const criteria = copyQuery(state.criteria);
92
94
  if (!append) loadedPages = 0;
93
95
  state = { ...state, status: append ? 'ready' : 'loading', loadingMore: append, pageError: false, error: '',
94
- ...(append ? {} : { items: [], nextCursor: '', total: undefined, metadata: undefined, complete: false, provisional: false }) };
96
+ ...(append ? {} : keepRows ? { nextCursor: '', complete: false, provisional: false }
97
+ : { items: [], nextCursor: '', total: undefined, metadata: undefined, complete: false, provisional: false }) };
95
98
  emit();
96
99
  try {
97
100
  if (!append && source.cached) {
@@ -117,7 +120,9 @@ export function createCollectionQuery<Row>(source: CollectionQuerySource<Row>, p
117
120
  clearedByError = clear || /HTTP 40[13]\b/.test(String(error));
118
121
  if (clearedByError) lastShown = undefined;
119
122
  if (clear) loadedPages = 0;
120
- state = { ...state, ...(clear ? { items: [], nextCursor: '', total: undefined, metadata: undefined } : {}),
123
+ // The rows a typed search kept on screen while it ran were never its answer: a failed one shows none of them.
124
+ const drop = clear || keepRows;
125
+ state = { ...state, ...(drop ? { items: [], nextCursor: '', total: undefined, metadata: undefined } : {}),
121
126
  status: append && !clear ? 'ready' : 'error', loadingMore: false, pageError: append && !clear, complete: false,
122
127
  provisional: false, error: source.errorMessage?.(error) ?? '' };
123
128
  }
@@ -134,9 +139,13 @@ export function createCollectionQuery<Row>(source: CollectionQuerySource<Row>, p
134
139
  const count = restorePages && same ? Math.max(loadedPages, restoringPages, 1) : 1;
135
140
  const showing = lastShown && sameCriteria(lastShown.state.criteria, next) ? lastShown : undefined;
136
141
  restoringPages = count;
142
+ // Only the search text changed: the user is typing over the rows on screen, also while the last keystroke's
143
+ // search is still out.
144
+ const typing = !same && (state.status === 'ready' || state.status === 'loading')
145
+ && sameCriteria({ ...state.criteria, query: '' }, { ...next, query: '' });
137
146
  state = { ...state, criteria: next };
138
147
  if (revalidate && showing) { revalidating = generation + 1; clearedByError = false; }
139
- const pending = fetch(false);
148
+ const pending = fetch(false, typing);
140
149
  const revision = generation;
141
150
  await pending;
142
151
  // Re-appearance revalidates every previously opened page using fresh
@@ -194,7 +194,8 @@ export function decorateCollection(name: string, def: { state: State; actions?:
194
194
  noSignIn: !signInRequired,
195
195
  errorTitle: signInRequired ? copyParam('collection.signInTitle', 'Sign in to see this') : copyParam('collection.loadErrorTitle', 'This collection could not load'),
196
196
  errorMessage,
197
- noLoading: !loading,
197
+ // A skeleton only with nothing to show: a search re-running over the rows on screen keeps them.
198
+ noLoading: !loading || rows.length > 0,
198
199
  // De-dup against a domain that paints its OWN empty block (screen-kit emptyBlock is
199
200
  // gated on the domain's top-level `noEmpty`): only one empty state ever shows.
200
201
  noEmptySource: !sourceEmpty || s.noEmpty === false,
@@ -17,7 +17,7 @@ import { OWNER_MODULES, ownerModuleId } from '../_shared/owner-modules.js';
17
17
  import { localPreferences } from '../_shared/local-preferences.js';
18
18
  import { stashCopy, copyParam, copyTemplate } from '../_shared/himi-copy.js';
19
19
  import { fallbackNameFrom, selectedSiteName } from '../_shared/owner-selected-site.js';
20
- import { DASHBOARD_PATHS, deriveManage, parseAreas, recentAreas, metricFailure, rememberArea, withMetrics, type ManageArea, type Metric, type MetricFailure } from './logic.js';
20
+ import { DASHBOARD_PATHS, deriveManage, navigationArgs, parseAreas, recentAreas, metricFailure, rememberArea, withMetrics, type ManageArea, type Metric, type MetricFailure } from './logic.js';
21
21
 
22
22
  interface State {
23
23
  /** The business the header names: the one picked on Sites, else the descriptor's
@@ -339,13 +339,15 @@ const svc = defineService<State, {
339
339
  const area = findArea(state, arg);
340
340
  if (!area) return state;
341
341
  const next = remember(state, area);
342
- return navigate(next, { screenId: area.screenId }) as State;
342
+ const args = navigationArgs(area.navigation);
343
+ return navigate(next, { screenId: area.screenId, ...(args ? { args } : {}) }) as State;
343
344
  },
344
345
  create: (state, arg) => {
345
346
  const area = findArea(state, arg);
346
347
  if (!area?.createScreenId) return state;
347
348
  const next = remember(state, area);
348
- return navigate(next, { screenId: area.createScreenId }) as State;
349
+ const args = navigationArgs(area.createNavigation);
350
+ return navigate(next, { screenId: area.createScreenId, ...(args ? { args } : {}) }) as State;
349
351
  },
350
352
  handoff: async (state, arg) => {
351
353
  const area = findArea(state, arg);
@@ -52,6 +52,34 @@ export interface ManageArea {
52
52
  hideHandoff?: boolean;
53
53
  /** The accessible name of this area's "Open in Wix", which carries the area ("Open in Wix: Store"). */
54
54
  handoffLabel?: string;
55
+ /** The navigation its page declares (`hostTitle`, `topBar.searchable`), for the host's bar the moment it opens it. */
56
+ navigation?: ManageNavigation;
57
+ /** The same for its create screen. */
58
+ createNavigation?: ManageNavigation;
59
+ }
60
+
61
+ /** A page's navigation as the build declares it: its title and subtitle, and where its search goes (none without one). */
62
+ export interface ManageNavigation {
63
+ title?: string;
64
+ subtitle?: string;
65
+ search?: "drawer" | "button";
66
+ }
67
+
68
+ function parseNavigation(value: unknown): ManageNavigation | undefined {
69
+ if (!value || typeof value !== "object") return undefined;
70
+ const raw = value as Record<string, unknown>;
71
+ const title = clean(raw.title);
72
+ const subtitle = title ? clean(raw.subtitle) : "";
73
+ const search = raw.search === "drawer" || raw.search === "button" ? raw.search : undefined;
74
+ if (!title && !search) return undefined;
75
+ return { ...(title ? { title } : {}), ...(subtitle ? { subtitle } : {}), ...(search ? { search } : {}) };
76
+ }
77
+
78
+ /** The open action's arguments that carry [navigation] to the host: strings only, as `_nav.args` takes them. */
79
+ export function navigationArgs(navigation: ManageNavigation | undefined): Record<string, string> | undefined {
80
+ if (!navigation) return undefined;
81
+ return { ...(navigation.title ? { title: navigation.title } : {}), ...(navigation.subtitle ? { subtitle: navigation.subtitle } : {}),
82
+ ...(navigation.search ? { search: navigation.search } : {}) };
55
83
  }
56
84
 
57
85
  export interface ManageSection {
@@ -98,6 +126,8 @@ export function parseAreas(value: unknown): ManageArea[] {
98
126
  hideCreate: !(createScreenId && createLabel),
99
127
  hideHandoff: !DASHBOARD_PATHS[area.id],
100
128
  handoffLabel: clean(raw.handoffLabel) || `Open in Wix: ${area.title}`,
129
+ ...(parseNavigation(raw.navigation) ? { navigation: parseNavigation(raw.navigation) } : {}),
130
+ ...(createScreenId && createLabel && parseNavigation(raw.createNavigation) ? { createNavigation: parseNavigation(raw.createNavigation) } : {}),
101
131
  metricText: "—",
102
132
  statusText: "",
103
133
  unavailable: false,
@@ -84,14 +84,17 @@ export function createCollectionQuery<Row>(source: CollectionQuerySource<Row>, p
84
84
  }
85
85
  return result;
86
86
  };
87
- const fetch = async (append: boolean): Promise<void> => {
87
+ /** [keepRows]: a search typed over the rows on screen leaves them there until its answer arrives (status stays
88
+ * `loading`, and nothing pages under the old answer), so the list is not blanked on each keystroke. */
89
+ const fetch = async (append: boolean, keepRows = false): Promise<void> => {
88
90
  if (disposed || (append && (state.loadingMore || state.status === 'loading' || !state.nextCursor))) return;
89
91
  const revision = append ? generation : ++generation;
90
92
  const cursor = append ? state.nextCursor : '';
91
93
  const criteria = copyQuery(state.criteria);
92
94
  if (!append) loadedPages = 0;
93
95
  state = { ...state, status: append ? 'ready' : 'loading', loadingMore: append, pageError: false, error: '',
94
- ...(append ? {} : { items: [], nextCursor: '', total: undefined, metadata: undefined, complete: false, provisional: false }) };
96
+ ...(append ? {} : keepRows ? { nextCursor: '', complete: false, provisional: false }
97
+ : { items: [], nextCursor: '', total: undefined, metadata: undefined, complete: false, provisional: false }) };
95
98
  emit();
96
99
  try {
97
100
  if (!append && source.cached) {
@@ -117,7 +120,9 @@ export function createCollectionQuery<Row>(source: CollectionQuerySource<Row>, p
117
120
  clearedByError = clear || /HTTP 40[13]\b/.test(String(error));
118
121
  if (clearedByError) lastShown = undefined;
119
122
  if (clear) loadedPages = 0;
120
- state = { ...state, ...(clear ? { items: [], nextCursor: '', total: undefined, metadata: undefined } : {}),
123
+ // The rows a typed search kept on screen while it ran were never its answer: a failed one shows none of them.
124
+ const drop = clear || keepRows;
125
+ state = { ...state, ...(drop ? { items: [], nextCursor: '', total: undefined, metadata: undefined } : {}),
121
126
  status: append && !clear ? 'ready' : 'error', loadingMore: false, pageError: append && !clear, complete: false,
122
127
  provisional: false, error: source.errorMessage?.(error) ?? '' };
123
128
  }
@@ -134,9 +139,13 @@ export function createCollectionQuery<Row>(source: CollectionQuerySource<Row>, p
134
139
  const count = restorePages && same ? Math.max(loadedPages, restoringPages, 1) : 1;
135
140
  const showing = lastShown && sameCriteria(lastShown.state.criteria, next) ? lastShown : undefined;
136
141
  restoringPages = count;
142
+ // Only the search text changed: the user is typing over the rows on screen, also while the last keystroke's
143
+ // search is still out.
144
+ const typing = !same && (state.status === 'ready' || state.status === 'loading')
145
+ && sameCriteria({ ...state.criteria, query: '' }, { ...next, query: '' });
137
146
  state = { ...state, criteria: next };
138
147
  if (revalidate && showing) { revalidating = generation + 1; clearedByError = false; }
139
- const pending = fetch(false);
148
+ const pending = fetch(false, typing);
140
149
  const revision = generation;
141
150
  await pending;
142
151
  // Re-appearance revalidates every previously opened page using fresh
@@ -194,7 +194,8 @@ export function decorateCollection(name: string, def: { state: State; actions?:
194
194
  noSignIn: !signInRequired,
195
195
  errorTitle: signInRequired ? copyParam('collection.signInTitle', 'Sign in to see this') : copyParam('collection.loadErrorTitle', 'This collection could not load'),
196
196
  errorMessage,
197
- noLoading: !loading,
197
+ // A skeleton only with nothing to show: a search re-running over the rows on screen keeps them.
198
+ noLoading: !loading || rows.length > 0,
198
199
  // De-dup against a domain that paints its OWN empty block (screen-kit emptyBlock is
199
200
  // gated on the domain's top-level `noEmpty`): only one empty state ever shows.
200
201
  noEmptySource: !sourceEmpty || s.noEmpty === false,
@@ -84,14 +84,17 @@ export function createCollectionQuery<Row>(source: CollectionQuerySource<Row>, p
84
84
  }
85
85
  return result;
86
86
  };
87
- const fetch = async (append: boolean): Promise<void> => {
87
+ /** [keepRows]: a search typed over the rows on screen leaves them there until its answer arrives (status stays
88
+ * `loading`, and nothing pages under the old answer), so the list is not blanked on each keystroke. */
89
+ const fetch = async (append: boolean, keepRows = false): Promise<void> => {
88
90
  if (disposed || (append && (state.loadingMore || state.status === 'loading' || !state.nextCursor))) return;
89
91
  const revision = append ? generation : ++generation;
90
92
  const cursor = append ? state.nextCursor : '';
91
93
  const criteria = copyQuery(state.criteria);
92
94
  if (!append) loadedPages = 0;
93
95
  state = { ...state, status: append ? 'ready' : 'loading', loadingMore: append, pageError: false, error: '',
94
- ...(append ? {} : { items: [], nextCursor: '', total: undefined, metadata: undefined, complete: false, provisional: false }) };
96
+ ...(append ? {} : keepRows ? { nextCursor: '', complete: false, provisional: false }
97
+ : { items: [], nextCursor: '', total: undefined, metadata: undefined, complete: false, provisional: false }) };
95
98
  emit();
96
99
  try {
97
100
  if (!append && source.cached) {
@@ -117,7 +120,9 @@ export function createCollectionQuery<Row>(source: CollectionQuerySource<Row>, p
117
120
  clearedByError = clear || /HTTP 40[13]\b/.test(String(error));
118
121
  if (clearedByError) lastShown = undefined;
119
122
  if (clear) loadedPages = 0;
120
- state = { ...state, ...(clear ? { items: [], nextCursor: '', total: undefined, metadata: undefined } : {}),
123
+ // The rows a typed search kept on screen while it ran were never its answer: a failed one shows none of them.
124
+ const drop = clear || keepRows;
125
+ state = { ...state, ...(drop ? { items: [], nextCursor: '', total: undefined, metadata: undefined } : {}),
121
126
  status: append && !clear ? 'ready' : 'error', loadingMore: false, pageError: append && !clear, complete: false,
122
127
  provisional: false, error: source.errorMessage?.(error) ?? '' };
123
128
  }
@@ -134,9 +139,13 @@ export function createCollectionQuery<Row>(source: CollectionQuerySource<Row>, p
134
139
  const count = restorePages && same ? Math.max(loadedPages, restoringPages, 1) : 1;
135
140
  const showing = lastShown && sameCriteria(lastShown.state.criteria, next) ? lastShown : undefined;
136
141
  restoringPages = count;
142
+ // Only the search text changed: the user is typing over the rows on screen, also while the last keystroke's
143
+ // search is still out.
144
+ const typing = !same && (state.status === 'ready' || state.status === 'loading')
145
+ && sameCriteria({ ...state.criteria, query: '' }, { ...next, query: '' });
137
146
  state = { ...state, criteria: next };
138
147
  if (revalidate && showing) { revalidating = generation + 1; clearedByError = false; }
139
- const pending = fetch(false);
148
+ const pending = fetch(false, typing);
140
149
  const revision = generation;
141
150
  await pending;
142
151
  // Re-appearance revalidates every previously opened page using fresh
@@ -194,7 +194,8 @@ export function decorateCollection(name: string, def: { state: State; actions?:
194
194
  noSignIn: !signInRequired,
195
195
  errorTitle: signInRequired ? copyParam('collection.signInTitle', 'Sign in to see this') : copyParam('collection.loadErrorTitle', 'This collection could not load'),
196
196
  errorMessage,
197
- noLoading: !loading,
197
+ // A skeleton only with nothing to show: a search re-running over the rows on screen keeps them.
198
+ noLoading: !loading || rows.length > 0,
198
199
  // De-dup against a domain that paints its OWN empty block (screen-kit emptyBlock is
199
200
  // gated on the domain's top-level `noEmpty`): only one empty state ever shows.
200
201
  noEmptySource: !sourceEmpty || s.noEmpty === false,