@wix/himalaya-cli 0.1525.0 → 0.1526.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/dev-server/src/builder.d.ts +14 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/ts/src/schema.d.ts +22 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/ts/src/worker-subtree-safe-types.generated.d.ts +4 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/core.mjs +7 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/sdui.mjs +13 -0
- package/dist/cli.mjs +137 -39
- package/dist/preview-assets.generated.bin +0 -0
- package/guides/designing-himi-screens.md +43 -18
- package/guides/recipe-dynamic-list.md +7 -7
- package/guides/recipe-four-state.md +47 -41
- package/guides/recipe-search.md +1 -1
- package/package.json +2 -2
- package/templates/beauty-salon/dev/index.ts +69 -40
- package/templates/beauty-salon/tier5-src/himi-branded-lite-agenda/index.ts +9 -1
- package/templates/beauty-salon/tier5-src/himi-branded-lite-place-summary/index.ts +12 -1
- package/templates/beauty-salon/tier5-src/himi-store-account/index.ts +69 -10
- package/templates/branded-lite/dev/index.ts +165 -88
- package/templates/branded-lite/tier5-src/business-chat/index.ts +4 -0
- package/templates/branded-lite/tier5-src/himi-branded-lite-agenda/index.ts +9 -1
- package/templates/branded-lite/tier5-src/himi-branded-lite-place-summary/index.ts +12 -1
- package/templates/branded-lite/tier5-src/himi-store-account/index.ts +69 -10
- package/templates/community-club/dev/index.ts +40 -21
- package/templates/community-club/tier5-src/himi-store-account/index.ts +69 -10
- package/templates/events-meetups/dev/index.ts +36 -19
- package/templates/events-meetups/tier5-src/himi-store-account/index.ts +69 -10
- package/templates/gym-fitness/dev/index.ts +73 -42
- package/templates/gym-fitness/tier5-src/himi-branded-lite-agenda/index.ts +9 -1
- package/templates/gym-fitness/tier5-src/himi-branded-lite-place-summary/index.ts +12 -1
- package/templates/gym-fitness/tier5-src/himi-store-account/index.ts +69 -10
- package/templates/nonprofit-cause/dev/index.ts +47 -29
- package/templates/nonprofit-cause/tier5-src/himi-branded-lite-place-summary/index.ts +12 -1
- package/templates/nonprofit-cause/tier5-src/himi-store-account/index.ts +69 -10
- package/templates/podcast-creator/dev/index.ts +47 -29
- package/templates/podcast-creator/tier5-src/himi-branded-lite-place-summary/index.ts +12 -1
- package/templates/podcast-creator/tier5-src/himi-store-account/index.ts +69 -10
- package/templates/portfolio-creative/dev/index.ts +52 -27
- package/templates/portfolio-creative/tier5-src/himi-store-account/index.ts +69 -10
- package/templates/restaurant/dev/index.ts +48 -25
- package/templates/restaurant/tier5-src/himi-store-account/index.ts +69 -10
- package/templates/retail-store/dev/index.ts +44 -23
- package/templates/retail-store/tier5-src/himi-store-account/index.ts +69 -10
- package/templates/yoga-studio/dev/index.ts +57 -34
- package/templates/yoga-studio/tier5-src/himi-branded-lite-agenda/index.ts +9 -1
- package/templates/yoga-studio/tier5-src/himi-branded-lite-place-summary/index.ts +12 -1
- package/templates/yoga-studio/tier5-src/himi-store-account/index.ts +69 -10
|
Binary file
|
|
@@ -257,7 +257,7 @@ scroll. *Reach for `SegmentedControl` for 2–4 in-content modes; `Tabs`/tab-bar
|
|
|
257
257
|
> in the *outer* column, between the pager and the bottom CTA). Its built-in indicator is faint; pair an
|
|
258
258
|
> explicit `PageControl` if the dots need to read.
|
|
259
259
|
|
|
260
|
-
**Feedback & overlays** — `Banner` for a persistent inline message (with a retry action when a refresh fails over content you kept; a screen with nothing to show uses an
|
|
260
|
+
**Feedback & overlays** — `Banner` for a persistent inline message (with a retry action when a refresh fails over content you kept; a screen with nothing to show uses an `ErrorState`, see §5);
|
|
261
261
|
`Snackbar` for a transient confirmation; `AlertDialog` for a blocking decision; `BottomSheet` for
|
|
262
262
|
contextual actions/detail; `Tooltip`/`Popover` for a small anchored hint. *Reach for `Snackbar`, not
|
|
263
263
|
`AlertDialog`, to confirm a completed action — don't block on success.* The `Snackbar` COMPONENT draws
|
|
@@ -345,7 +345,7 @@ to drive them:
|
|
|
345
345
|
|---|---|---|
|
|
346
346
|
| **Loading** | `Skeleton` (shape of the eventual content) or `StatefulSection` / `ActivityIndicator` | Required on every screen that loads data asynchronously, and re-checked on every redesign. Prefer a content-shaped `Skeleton` over a bare spinner — it previews the layout. The descriptor renders against the state-skeleton, then repaints as worker state lands. See [Loading states](#loading-states-part-of-the-design) below. |
|
|
347
347
|
| **Empty** | `EmptyState` (`title`, optional `icon` / `subtitle`) **plus a directive action** (`action` + `actionLabel`) | An empty screen is an invitation to act, not a dead end. Give it the next step, for example `"actionLabel": "Add your first item"`. Use `EmptyState` for **every** empty state, so all empty states in the app have the same layout and style. See [Empty states](#empty-states-one-component-one-layout) below. |
|
|
348
|
-
| **Error** | Classified first. Nothing on screen: an `
|
|
348
|
+
| **Error** | Classified first. Nothing on screen: an `ErrorState` per class (user-facing `title` + `body`, a `cta` only for a real recovery). Rows on screen: keep them, add a `Banner` with retry. Field and action errors stay local. | Say what happened in the user's words and offer recovery only when there is one; never a blank screen or a raw error string. See [Error states](#error-states-classify-then-present) below. |
|
|
349
349
|
| **Loaded** | the real content | The one everyone remembers to build. |
|
|
350
350
|
|
|
351
351
|
Bind visibility to worker state with `hidden_path` (`$service:…`/`$item.` data-bound collapse in
|
|
@@ -433,13 +433,14 @@ its own recovery.
|
|
|
433
433
|
| **Field validation** | A value the user typed is not valid. | Beside the field, in its own error slot (see **Inputs & forms** in §3). | "Enter a valid email." | The user corrects the field. No retry. |
|
|
434
434
|
| **Action failure** | A write or a command failed (save, delete, book). | Near the action or the affected content — a line by the button, or a `danger` snackbar (`ui.snackbar({ message, tone: "danger" })`). | "Couldn't save your changes. Try again." | Keep what the user entered; the action itself is the retry. |
|
|
435
435
|
|
|
436
|
-
- **A screen- or section-level error is an `
|
|
437
|
-
`
|
|
438
|
-
recovery — retry, reload, the app's auth flow, or
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
436
|
+
- **A screen- or section-level error is an `ErrorState`.** Set its `variant`, and `title` and
|
|
437
|
+
`body` when the default copy does not fit. Add a `cta` (with `ctaLabel` when the default label
|
|
438
|
+
does not fit) only when the user has a useful recovery — retry, reload, the app's auth flow, or
|
|
439
|
+
the site's join flow when available. Every error then has the same layout and style. Do not
|
|
440
|
+
build one by hand from `Icon`, `Text` and `Button` nodes.
|
|
441
|
+
- **One node per class.** iOS and Android do not resolve a `$service:` binding in `ErrorState`
|
|
442
|
+
`title` or `body`, so the worker cannot swap the text. Give each class the screen can hit its
|
|
443
|
+
own `ErrorState` with literal copy or `$t:` keys, and gate each one with `hiddenPath` bound to a
|
|
443
444
|
worker flag. The worker classifies the failure once and sets the flags — see
|
|
444
445
|
`himi docs recipe-four-state` for the whole pattern.
|
|
445
446
|
- **User-facing language, never the raw error.** Say what happened and what the user can do. Never
|
|
@@ -461,17 +462,41 @@ its own recovery.
|
|
|
461
462
|
|
|
462
463
|
```jsonc
|
|
463
464
|
{ "id": "orders.error.offline",
|
|
464
|
-
"body": { "package": "himalaya", "type": "
|
|
465
|
-
"props": { "
|
|
466
|
-
"
|
|
467
|
-
"action": { "actionId": "tier5.orders.retry" },
|
|
468
|
-
"actionLabel": "Try again" } },
|
|
465
|
+
"body": { "package": "himalaya", "type": "ErrorState",
|
|
466
|
+
"props": { "variant": "offline",
|
|
467
|
+
"cta": { "actionId": "tier5.orders.retry" } } },
|
|
469
468
|
"modifiers": { "hiddenPath": "$service:orders.hideOfflineError" } }
|
|
470
469
|
```
|
|
471
470
|
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
471
|
+
**`ErrorState` draws the same layout on iOS, Android, macOS, web and React Native:** a centred
|
|
472
|
+
glyph, title, body and one primary button, with no icon tile and no card.
|
|
473
|
+
|
|
474
|
+
| `variant` | Glyph | Title | Body | Button |
|
|
475
|
+
|---|---|---|---|---|
|
|
476
|
+
| `server` (default) | error | Unable to load this content | There was a technical issue on our end. Please wait and try again later. | Try again |
|
|
477
|
+
| `offline` | wifi | Reconnect to the Internet | Connect to the internet, then try again. | Try again |
|
|
478
|
+
| `permission` | error | You need permission to view this screen | none | Contact customer care |
|
|
479
|
+
| `signIn` | person | Sign in to view this content | none | Sign in |
|
|
480
|
+
|
|
481
|
+
- `title`, `body` and `ctaLabel` replace the copy of the variant, for example
|
|
482
|
+
`"title": "Unable to load orders"`. Site membership has no variant: use `permission` with your
|
|
483
|
+
own `title` and `body`. Write the copy as literal text or `$t:` keys: iOS and Android show a
|
|
484
|
+
`$service:` binding there as raw text, so `himi validate` refuses it.
|
|
485
|
+
- `variantPath` is a `$service:` binding that selects the variant and wins over `variant`. An
|
|
486
|
+
unknown value (`membership`, `""`) keeps `variant`. It switches only the default copy and glyph:
|
|
487
|
+
`cta`, `title`, `body` and `ctaLabel` apply to every variant. So one node bound to the worker's
|
|
488
|
+
`errorKind` covers the kinds that share a button — offline and server, both **Try again** — and
|
|
489
|
+
sign-in (its own action) and permission (no button) each keep a node of their own.
|
|
490
|
+
- The button draws only when you set `cta`. Leave it out for a permission error.
|
|
491
|
+
- `placement` is `screen` (default, about 96px from the top) or `section` (about 24px).
|
|
492
|
+
|
|
493
|
+
```jsonc
|
|
494
|
+
{ "id": "orders.error",
|
|
495
|
+
"body": { "package": "himalaya", "type": "ErrorState",
|
|
496
|
+
"props": { "variantPath": "$service:orders.errorKind",
|
|
497
|
+
"cta": { "actionId": "tier5.orders.retry" } } },
|
|
498
|
+
"modifiers": { "hiddenPath": "$service:orders.hideRetryableError" } } // offline or server
|
|
499
|
+
```
|
|
475
500
|
|
|
476
501
|
**Loading & empty states are first-class — use the `Skeleton` and `EmptyState` component types.**
|
|
477
502
|
|
|
@@ -546,7 +571,7 @@ Run this punch list on the screen (it's also a good reviewer checklist):
|
|
|
546
571
|
|
|
547
572
|
- [ ] **One** primary action, on the one `accentPrimary` surface?
|
|
548
573
|
- [ ] All four states designed — loading / empty / error / loaded (empty has a directive action; error has a recovery action when one exists)?
|
|
549
|
-
- [ ] Errors classified (offline / server / sign-in / site membership / permission), each an
|
|
574
|
+
- [ ] Errors classified (offline / server / sign-in / site membership / permission), each an `ErrorState` with user-facing copy — no raw error text? Empty results shown as empty, not as an error? A failed refresh keeps the content?
|
|
550
575
|
- [ ] Loading state matches the loaded layout (a content-shaped `Skeleton` where the layout is known)? For a redesign, was the existing loading state reviewed against the new layout? A static screen has none?
|
|
551
576
|
- [ ] Every value a token — zero literal hex / off-scale spacing / invented type sizes?
|
|
552
577
|
- [ ] Is every user-facing string routed through a copy key with a sensible fallback (`copyOf(key, fallback)` backed by your own `COPY` map; worker strings via `onAppear` params) — none baked directly into the descriptor?
|
|
@@ -157,8 +157,9 @@ A list loads data, so it ships all four states — the full idiom is
|
|
|
157
157
|
It shows only while the first load runs with nothing on screen; a pull-to-refresh
|
|
158
158
|
keeps the rows and uses the list's own spinner.
|
|
159
159
|
- **Empty** — an `EmptyState` with a directive action, gated by `notEmpty`.
|
|
160
|
-
- **Error, nothing on screen** — one `
|
|
161
|
-
return (offline, server, sign-in, site membership, permission)
|
|
160
|
+
- **Error, nothing on screen** — one `ErrorState` per class `classifyError` can
|
|
161
|
+
return (offline, server, sign-in, site membership, permission): no icon tile or custom icon
|
|
162
|
+
(the `variant` picks the glyph), user-facing copy.
|
|
162
163
|
Offline and server get **Try again** → `tier5.products.retry`; sign-in gets
|
|
163
164
|
**Sign in**; membership opens the site's join flow when available; permission gets no action. A list with no sign-in flow maps
|
|
164
165
|
`"signIn"` to `"permission"` — a visitor still gets a 401 or 403 (Bookings
|
|
@@ -178,10 +179,9 @@ A list loads data, so it ships all four states — the full idiom is
|
|
|
178
179
|
"props": { "height": 72, "cornerRadius": 12, "count": 6 } },
|
|
179
180
|
"modifiers": { "hiddenPath": "$service:products.hideLoading" } },
|
|
180
181
|
{ "id": "error.server",
|
|
181
|
-
"body": { "package": "himalaya", "type": "
|
|
182
|
-
"props": { "title": "Unable to load products",
|
|
183
|
-
"
|
|
184
|
-
"action": { "actionId": "tier5.products.retry" }, "actionLabel": "Try again" } },
|
|
182
|
+
"body": { "package": "himalaya", "type": "ErrorState",
|
|
183
|
+
"props": { "variant": "server", "title": "Unable to load products",
|
|
184
|
+
"cta": { "actionId": "tier5.products.retry" } } },
|
|
185
185
|
"modifiers": { "hiddenPath": "$service:products.hideServerError" } }
|
|
186
186
|
```
|
|
187
187
|
|
|
@@ -234,7 +234,7 @@ word:
|
|
|
234
234
|
- **Except when page 2 loses access.** A `signIn`, `membership` or `permission` failure on
|
|
235
235
|
`loadMore` is not retryable and the rows already shown are no longer the
|
|
236
236
|
user's to see: clear them and go to the same sign-in, membership or
|
|
237
|
-
permission `
|
|
237
|
+
permission `ErrorState` a refresh uses — membership with its join action (`load()` in `himi docs recipe-four-state`). Keep
|
|
238
238
|
page 1 and offer the cursor retry only for `offline` and `server`.
|
|
239
239
|
- APIs that don't page (or v1 fallbacks without cursors) simply never set
|
|
240
240
|
`nextCursor` — the gesture props are safe to leave in place.
|
|
@@ -13,7 +13,7 @@ Every data-backed screen has four states, and Himi's idiom renders all of them
|
|
|
13
13
|
from **one flat layout** — no conditional trees, no separate loading screen. Each
|
|
14
14
|
state is its own node, gated with the `hiddenPath` modifier bound to a worker
|
|
15
15
|
boolean, and the worker derives every flag from one `status` field. The empty state
|
|
16
|
-
and each error class
|
|
16
|
+
is an `EmptyState` node and each error class an `ErrorState` node, gated the same way.
|
|
17
17
|
|
|
18
18
|
This is the corpus norm: of the in-repo worker bundles, ~3 in 4 model loading,
|
|
19
19
|
more than half model error, and the reference apps (`apps/himi-owner-lite`,
|
|
@@ -32,7 +32,7 @@ interface State {
|
|
|
32
32
|
// hiddenPath hides a node when its flag is TRUE — name flags in the hide direction.
|
|
33
33
|
hideLoading: boolean; // the skeleton: shown only while loading with nothing on screen
|
|
34
34
|
notEmpty: boolean; // the EmptyState: shown only when ready with zero rows
|
|
35
|
-
hideOfflineError: boolean; // one
|
|
35
|
+
hideOfflineError: boolean; // one ErrorState per error class …
|
|
36
36
|
hideServerError: boolean;
|
|
37
37
|
hideSignInError: boolean;
|
|
38
38
|
hideMembershipError: boolean;
|
|
@@ -141,40 +141,40 @@ only when the screen shows the user something different for it.
|
|
|
141
141
|
"actionLabel": "Add your first product" } },
|
|
142
142
|
"modifiers": { "hiddenPath": "$service:products.notEmpty" } },
|
|
143
143
|
|
|
144
|
-
// Errors: an
|
|
145
|
-
//
|
|
144
|
+
// Errors: an ErrorState per class, with user-facing copy and a `cta` only when the
|
|
145
|
+
// user can recover. `variant` picks the glyph; the authored copy wins over its default.
|
|
146
146
|
{ "id": "error.offline",
|
|
147
|
-
"body": { "package": "himalaya", "type": "
|
|
148
|
-
"props": { "title": "Reconnect to the internet",
|
|
149
|
-
"
|
|
150
|
-
"
|
|
151
|
-
"
|
|
147
|
+
"body": { "package": "himalaya", "type": "ErrorState",
|
|
148
|
+
"props": { "variant": "offline", "title": "Reconnect to the internet",
|
|
149
|
+
"body": "Connect to the internet, then try again.",
|
|
150
|
+
"cta": { "actionId": "tier5.products.retry" },
|
|
151
|
+
"ctaLabel": "Try again" } },
|
|
152
152
|
"modifiers": { "hiddenPath": "$service:products.hideOfflineError" } },
|
|
153
153
|
{ "id": "error.server",
|
|
154
|
-
"body": { "package": "himalaya", "type": "
|
|
155
|
-
"props": { "title": "Unable to load products",
|
|
156
|
-
"
|
|
157
|
-
"
|
|
158
|
-
"
|
|
154
|
+
"body": { "package": "himalaya", "type": "ErrorState",
|
|
155
|
+
"props": { "variant": "server", "title": "Unable to load products",
|
|
156
|
+
"body": "There was a technical issue on our end. Please wait and try again later.",
|
|
157
|
+
"cta": { "actionId": "tier5.products.retry" },
|
|
158
|
+
"ctaLabel": "Try again" } },
|
|
159
159
|
"modifiers": { "hiddenPath": "$service:products.hideServerError" } },
|
|
160
160
|
{ "id": "error.signIn",
|
|
161
|
-
"body": { "package": "himalaya", "type": "
|
|
162
|
-
"props": { "title": "Sign in to view products",
|
|
163
|
-
"
|
|
164
|
-
"
|
|
165
|
-
"
|
|
161
|
+
"body": { "package": "himalaya", "type": "ErrorState",
|
|
162
|
+
"props": { "variant": "signIn", "title": "Sign in to view products",
|
|
163
|
+
"body": "Your session has ended.",
|
|
164
|
+
"cta": { "actionId": "tier5.products.openSignIn" },
|
|
165
|
+
"ctaLabel": "Sign in" } },
|
|
166
166
|
"modifiers": { "hiddenPath": "$service:products.hideSignInError" } },
|
|
167
167
|
{ "id": "error.membership",
|
|
168
|
-
"body": { "package": "himalaya", "type": "
|
|
169
|
-
"props": { "title": "Join this site to view products",
|
|
170
|
-
"
|
|
171
|
-
"
|
|
172
|
-
"
|
|
168
|
+
"body": { "package": "himalaya", "type": "ErrorState",
|
|
169
|
+
"props": { "variant": "permission", "title": "Join this site to view products",
|
|
170
|
+
"body": "Use this site's join flow, or ask the owner for access.",
|
|
171
|
+
"cta": { "actionId": "tier5.products.openJoin" },
|
|
172
|
+
"ctaLabel": "Join this site" } },
|
|
173
173
|
"modifiers": { "hiddenPath": "$service:products.hideMembershipError" } },
|
|
174
174
|
{ "id": "error.permission", // no action: retrying gives the same answer
|
|
175
|
-
"body": { "package": "himalaya", "type": "
|
|
176
|
-
"props": { "title": "You need permission to view this screen",
|
|
177
|
-
"
|
|
175
|
+
"body": { "package": "himalaya", "type": "ErrorState",
|
|
176
|
+
"props": { "variant": "permission", "title": "You need permission to view this screen",
|
|
177
|
+
"body": "Ask the owner of this business for access." } },
|
|
178
178
|
"modifiers": { "hiddenPath": "$service:products.hidePermissionError" } },
|
|
179
179
|
|
|
180
180
|
// A refresh that failed over rows we kept: say so above the rows, keep the rows.
|
|
@@ -193,13 +193,13 @@ only when the screen shows the user something different for it.
|
|
|
193
193
|
- Ready state is simply the data itself (the list rows, the detail fields) — it
|
|
194
194
|
needs no dedicated marker.
|
|
195
195
|
- **Sign in uses a real auth flow.** A renderer cannot dispatch `himi.wix.*`, so
|
|
196
|
-
the sign-in `
|
|
196
|
+
the sign-in `ErrorState` calls an app action that opens its configured auth gate
|
|
197
197
|
or sign-in screen, then reloads after authentication. Do not dispatch
|
|
198
198
|
`himi.wix.signInDirect` from this button: it requires the email and password
|
|
199
199
|
collected by a credential form (`himi docs wix-login-integration`).
|
|
200
200
|
- **Membership is not sign-in.** `NotSiteMember` (Android: `not a member of
|
|
201
201
|
site`) means the account must join this site. Route it to the membership state;
|
|
202
|
-
wire its
|
|
202
|
+
wire its `cta` to the app's join/registration UI, or omit the `cta` when no
|
|
203
203
|
such flow exists. A `403 WDE0027` is NOT membership: Wix Data returns it to a
|
|
204
204
|
signed-in member without access too, so it stays a permission error.
|
|
205
205
|
- **A 403 is a permission error** — the user lacks access and signing in again
|
|
@@ -211,29 +211,35 @@ only when the screen shows the user something different for it.
|
|
|
211
211
|
layout — the same rows at about the same size. `StatefulSection` or
|
|
212
212
|
`ActivityIndicator` are fine where they fit better. A plain `"Loading…"` `Text`
|
|
213
213
|
bound to a worker string (`collapseWhenEmpty: true`) is the minimum, not the goal.
|
|
214
|
-
- **Use `EmptyState` for every empty state and every screen-level
|
|
215
|
-
draws one standard layout on every platform with the app's tokens.
|
|
216
|
-
|
|
217
|
-
default label
|
|
218
|
-
`
|
|
219
|
-
- Write the
|
|
220
|
-
|
|
221
|
-
why each error class is its own node instead of one
|
|
214
|
+
- **Use `EmptyState` for every empty state and `ErrorState` for every screen-level
|
|
215
|
+
error.** Each draws one standard layout on every platform with the app's tokens.
|
|
216
|
+
An `EmptyState` needs `title`, and `actionLabel` whenever it has an `action` (there
|
|
217
|
+
is no default label; `himi validate` reports a missing one). An `ErrorState` needs
|
|
218
|
+
nothing: its `variant` supplies default copy and a button label.
|
|
219
|
+
- Write the copy as literal text or `$t:` keys. Today, iOS and Android do not
|
|
220
|
+
resolve a `$service:` binding in those props (`himi validate` refuses one in
|
|
221
|
+
`ErrorState` copy) — which is why each error class is its own node instead of one
|
|
222
|
+
node with bound text.
|
|
222
223
|
- **A node for every class `classifyError` can return** — otherwise that failure
|
|
223
224
|
paints a blank screen. A screen with no sign-in flow still gets a 401 when a
|
|
224
225
|
session ends; map `"signIn"` to `"permission"` in its classifier rather than drop
|
|
225
226
|
the node.
|
|
226
|
-
- **
|
|
227
|
-
|
|
227
|
+
- **Fewer nodes with `variantPath`.** `variantPath: "$service:products.errorKind"`
|
|
228
|
+
switches only the default copy and glyph; `cta`, `title`, `body` and `ctaLabel`
|
|
229
|
+
apply to every variant. So `offline` and `server` can share ONE node with the retry
|
|
230
|
+
`cta` and no authored copy — gate it on one flag `shape()` sets for either
|
|
231
|
+
(`hideRetryableError: !(blank && (errorKind === "offline" || errorKind === "server"))`),
|
|
232
|
+
since `hiddenPath` takes a single boolean — while `signIn`, `permission` and
|
|
233
|
+
`membership` keep their own. See `himi docs designing-himi-screens` → Error states.
|
|
228
234
|
|
|
229
235
|
## 3. Which state when
|
|
230
236
|
|
|
231
237
|
| State | Set where | User affordance |
|
|
232
238
|
|---|---|---|
|
|
233
239
|
| loading | initial state (`status: "loading"`) | content-shaped skeleton; NO retry |
|
|
234
|
-
| error, nothing on screen | `catch` in the first load | the class's `
|
|
240
|
+
| error, nothing on screen | `catch` in the first load | the class's `ErrorState`: user-facing copy + a recovery `cta` where one exists |
|
|
235
241
|
| error, rows on screen | `catch` in a refresh (offline or server) | rows stay; a `Banner` with **Try again** above them |
|
|
236
|
-
| sign-in, membership or permission | `catch` in any load | rows go; the class's `
|
|
242
|
+
| sign-in, membership or permission | `catch` in any load | rows go; the class's `ErrorState` (auth flow, join flow, or no `cta`) |
|
|
237
243
|
| empty | derived: ready + 0 rows | say what *would* fill it, and how |
|
|
238
244
|
| ready | successful fetch | the content |
|
|
239
245
|
|
package/guides/recipe-search.md
CHANGED
|
@@ -66,7 +66,7 @@ not say why there are no rows.
|
|
|
66
66
|
| Results | Matching rows, result count, and the available sort/filter controls. |
|
|
67
67
|
| No matches | An `EmptyState` explaining that the current query or filters found nothing; offer a way to adjust them, and Clear only where the field is controlled (the native slot) — the inline `SearchField` is uncontrolled, so Clear there empties the query but not the field. |
|
|
68
68
|
| Empty catalog | Explain that the catalog itself has no items yet, independent of the query; provide a useful browse/back action. |
|
|
69
|
-
| Error | Classified as in `himi docs recipe-four-state`: an
|
|
69
|
+
| Error | Classified as in `himi docs recipe-four-state`: an `ErrorState` per class with user-facing copy, a Try again `cta` only for offline and server, the app's auth flow for sign-in, the site's join flow for `NotSiteMember`, and no `cta` for permission. Retain the query and controls, and keep it distinct from no matches. |
|
|
70
70
|
|
|
71
71
|
### Native slot and old-client fallback
|
|
72
72
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wix/himalaya-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.1526.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": "78420cfba501244cd7abb4a48357db143350fcae0d24d8cf3027b161"
|
|
75
75
|
}
|
|
@@ -2618,8 +2618,10 @@ const SCREENS = runtimeSetting({
|
|
|
2618
2618
|
"actionId": "himi.open",
|
|
2619
2619
|
"params": {
|
|
2620
2620
|
"screenId": "himi_sign_in",
|
|
2621
|
-
"mode": "signin"
|
|
2622
|
-
|
|
2621
|
+
"mode": "signin",
|
|
2622
|
+
"modal": "1"
|
|
2623
|
+
},
|
|
2624
|
+
"presentation": "sheet"
|
|
2623
2625
|
}
|
|
2624
2626
|
}
|
|
2625
2627
|
}
|
|
@@ -2637,8 +2639,10 @@ const SCREENS = runtimeSetting({
|
|
|
2637
2639
|
"actionId": "himi.open",
|
|
2638
2640
|
"params": {
|
|
2639
2641
|
"screenId": "himi_sign_in",
|
|
2640
|
-
"mode": "register"
|
|
2641
|
-
|
|
2642
|
+
"mode": "register",
|
|
2643
|
+
"modal": "1"
|
|
2644
|
+
},
|
|
2645
|
+
"presentation": "sheet"
|
|
2642
2646
|
}
|
|
2643
2647
|
}
|
|
2644
2648
|
}
|
|
@@ -3921,6 +3925,8 @@ const SCREENS = runtimeSetting({
|
|
|
3921
3925
|
"memberAccountLinks": "1",
|
|
3922
3926
|
"restorePurchases": "1",
|
|
3923
3927
|
"signInScreen": "himi_sign_in",
|
|
3928
|
+
"signInPresentation": "sheet",
|
|
3929
|
+
"signInReturnTab": "profile",
|
|
3924
3930
|
"pollIdentity": "1",
|
|
3925
3931
|
"cartCount": "1",
|
|
3926
3932
|
"copy": "{\"account.subtitle\":\"Orders, bookings, memberships and saved content in one place.\",\"storeAccount.authBody\":\"Sign in to continue — cart, bookings and memberships stay with you.\",\"storeAccount.authBodyRegister\":\"Create a member account to continue — cart, bookings and memberships stay with you.\"}"
|
|
@@ -4196,8 +4202,10 @@ const SCREENS = runtimeSetting({
|
|
|
4196
4202
|
"params": {
|
|
4197
4203
|
"screenId": "himi_sign_in",
|
|
4198
4204
|
"mode": "signin",
|
|
4199
|
-
"returnScreenId": "himi_account_settings"
|
|
4200
|
-
|
|
4205
|
+
"returnScreenId": "himi_account_settings",
|
|
4206
|
+
"modal": "1"
|
|
4207
|
+
},
|
|
4208
|
+
"presentation": "sheet"
|
|
4201
4209
|
}
|
|
4202
4210
|
}
|
|
4203
4211
|
},
|
|
@@ -6107,8 +6115,7 @@ const SCREENS = runtimeSetting({
|
|
|
6107
6115
|
"style": "primary",
|
|
6108
6116
|
"fullWidth": true,
|
|
6109
6117
|
"action": {
|
|
6110
|
-
"actionId": "
|
|
6111
|
-
"presentation": "back"
|
|
6118
|
+
"actionId": "tier5.himiStoreAccount.leave"
|
|
6112
6119
|
}
|
|
6113
6120
|
}
|
|
6114
6121
|
}
|
|
@@ -6149,7 +6156,7 @@ const SCREENS = runtimeSetting({
|
|
|
6149
6156
|
"declaredCapabilities": [
|
|
6150
6157
|
"wix_api"
|
|
6151
6158
|
],
|
|
6152
|
-
"minBinaryVersion": "
|
|
6159
|
+
"minBinaryVersion": "1.7.0",
|
|
6153
6160
|
"bundleVersion": "0.0.1"
|
|
6154
6161
|
}
|
|
6155
6162
|
},
|
|
@@ -7016,8 +7023,10 @@ const SCREENS = runtimeSetting({
|
|
|
7016
7023
|
"screenId": "himi_sign_in",
|
|
7017
7024
|
"mode": "signin",
|
|
7018
7025
|
"intent": "profile",
|
|
7019
|
-
"returnScreenId": "himi_view_profile"
|
|
7020
|
-
|
|
7026
|
+
"returnScreenId": "himi_view_profile",
|
|
7027
|
+
"modal": "1"
|
|
7028
|
+
},
|
|
7029
|
+
"presentation": "sheet"
|
|
7021
7030
|
}
|
|
7022
7031
|
}
|
|
7023
7032
|
}
|
|
@@ -15843,8 +15852,10 @@ const SCREENS = runtimeSetting({
|
|
|
15843
15852
|
"screenId": "himi_sign_in",
|
|
15844
15853
|
"mode": "signin",
|
|
15845
15854
|
"intent": "orders",
|
|
15846
|
-
"returnScreenId": "himi_orders"
|
|
15847
|
-
|
|
15855
|
+
"returnScreenId": "himi_orders",
|
|
15856
|
+
"modal": "1"
|
|
15857
|
+
},
|
|
15858
|
+
"presentation": "sheet"
|
|
15848
15859
|
}
|
|
15849
15860
|
}
|
|
15850
15861
|
}
|
|
@@ -16477,8 +16488,10 @@ const SCREENS = runtimeSetting({
|
|
|
16477
16488
|
"screenId": "himi_sign_in",
|
|
16478
16489
|
"mode": "signin",
|
|
16479
16490
|
"intent": "addresses",
|
|
16480
|
-
"returnScreenId": "himi_addresses"
|
|
16481
|
-
|
|
16491
|
+
"returnScreenId": "himi_addresses",
|
|
16492
|
+
"modal": "1"
|
|
16493
|
+
},
|
|
16494
|
+
"presentation": "sheet"
|
|
16482
16495
|
}
|
|
16483
16496
|
}
|
|
16484
16497
|
}
|
|
@@ -17534,8 +17547,10 @@ const SCREENS = runtimeSetting({
|
|
|
17534
17547
|
"screenId": "himi_sign_in",
|
|
17535
17548
|
"mode": "signin",
|
|
17536
17549
|
"intent": "addresses",
|
|
17537
|
-
"returnScreenId": "himi_address"
|
|
17538
|
-
|
|
17550
|
+
"returnScreenId": "himi_address",
|
|
17551
|
+
"modal": "1"
|
|
17552
|
+
},
|
|
17553
|
+
"presentation": "sheet"
|
|
17539
17554
|
}
|
|
17540
17555
|
}
|
|
17541
17556
|
}
|
|
@@ -18205,8 +18220,10 @@ const SCREENS = runtimeSetting({
|
|
|
18205
18220
|
"screenId": "himi_sign_in",
|
|
18206
18221
|
"mode": "signin",
|
|
18207
18222
|
"intent": "wallet",
|
|
18208
|
-
"returnScreenId": "himi_wallet"
|
|
18209
|
-
|
|
18223
|
+
"returnScreenId": "himi_wallet",
|
|
18224
|
+
"modal": "1"
|
|
18225
|
+
},
|
|
18226
|
+
"presentation": "sheet"
|
|
18210
18227
|
}
|
|
18211
18228
|
}
|
|
18212
18229
|
}
|
|
@@ -18674,11 +18691,7 @@ const SCREENS = runtimeSetting({
|
|
|
18674
18691
|
"size": "sm",
|
|
18675
18692
|
"fullWidth": true,
|
|
18676
18693
|
"action": {
|
|
18677
|
-
"actionId": "
|
|
18678
|
-
"params": {
|
|
18679
|
-
"screenId": "$service:himiPlaceSummary.ctaScreenId",
|
|
18680
|
-
"mode": "$service:himiPlaceSummary.ctaMode"
|
|
18681
|
-
}
|
|
18694
|
+
"actionId": "tier5.himiPlaceSummary.openCta"
|
|
18682
18695
|
}
|
|
18683
18696
|
}
|
|
18684
18697
|
},
|
|
@@ -18719,7 +18732,8 @@ const SCREENS = runtimeSetting({
|
|
|
18719
18732
|
"onAppear": {
|
|
18720
18733
|
"actionId": "tier5.himiPlaceSummary.load",
|
|
18721
18734
|
"params": {
|
|
18722
|
-
"verticals": "[\"store\",\"bookings\",\"plans\",\"blog\"]"
|
|
18735
|
+
"verticals": "[\"store\",\"bookings\",\"plans\",\"blog\"]",
|
|
18736
|
+
"signInPresentation": "sheet"
|
|
18723
18737
|
}
|
|
18724
18738
|
}
|
|
18725
18739
|
}
|
|
@@ -21907,8 +21921,10 @@ const SCREENS = runtimeSetting({
|
|
|
21907
21921
|
"params": {
|
|
21908
21922
|
"screenId": "himi_sign_in",
|
|
21909
21923
|
"mode": "signin",
|
|
21910
|
-
"returnScreenId": "himi_bookings"
|
|
21911
|
-
|
|
21924
|
+
"returnScreenId": "himi_bookings",
|
|
21925
|
+
"modal": "1"
|
|
21926
|
+
},
|
|
21927
|
+
"presentation": "sheet"
|
|
21912
21928
|
}
|
|
21913
21929
|
}
|
|
21914
21930
|
}
|
|
@@ -25292,7 +25308,8 @@ const SCREENS = runtimeSetting({
|
|
|
25292
25308
|
"onAppear": {
|
|
25293
25309
|
"actionId": "tier5.himiBrandedAgenda.load",
|
|
25294
25310
|
"params": {
|
|
25295
|
-
"firstWeekday": "0"
|
|
25311
|
+
"firstWeekday": "0",
|
|
25312
|
+
"signInPresentation": "sheet"
|
|
25296
25313
|
}
|
|
25297
25314
|
}
|
|
25298
25315
|
}
|
|
@@ -28349,8 +28366,10 @@ const SCREENS = runtimeSetting({
|
|
|
28349
28366
|
"actionId": "himi.open",
|
|
28350
28367
|
"params": {
|
|
28351
28368
|
"screenId": "himi_sign_in",
|
|
28352
|
-
"mode": "signin"
|
|
28353
|
-
|
|
28369
|
+
"mode": "signin",
|
|
28370
|
+
"modal": "1"
|
|
28371
|
+
},
|
|
28372
|
+
"presentation": "sheet"
|
|
28354
28373
|
}
|
|
28355
28374
|
}
|
|
28356
28375
|
},
|
|
@@ -28735,8 +28754,10 @@ const SCREENS = runtimeSetting({
|
|
|
28735
28754
|
"screenId": "himi_sign_in",
|
|
28736
28755
|
"mode": "signin",
|
|
28737
28756
|
"intent": "post",
|
|
28738
|
-
"returnScreenId": "himi_create_post"
|
|
28739
|
-
|
|
28757
|
+
"returnScreenId": "himi_create_post",
|
|
28758
|
+
"modal": "1"
|
|
28759
|
+
},
|
|
28760
|
+
"presentation": "sheet"
|
|
28740
28761
|
}
|
|
28741
28762
|
}
|
|
28742
28763
|
}
|
|
@@ -32973,8 +32994,10 @@ const TEMPLATED: { pattern: string; display: string; liveSeed?: LiveSeed; static
|
|
|
32973
32994
|
"params": {
|
|
32974
32995
|
"screenId": "himi_sign_in",
|
|
32975
32996
|
"returnScreenId": "himi_order",
|
|
32976
|
-
"returnId": "$service:himiBrandedOrder.id"
|
|
32977
|
-
|
|
32997
|
+
"returnId": "$service:himiBrandedOrder.id",
|
|
32998
|
+
"modal": "1"
|
|
32999
|
+
},
|
|
33000
|
+
"presentation": "sheet"
|
|
32978
33001
|
}
|
|
32979
33002
|
}
|
|
32980
33003
|
},
|
|
@@ -38401,8 +38424,10 @@ const TEMPLATED: { pattern: string; display: string; liveSeed?: LiveSeed; static
|
|
|
38401
38424
|
"params": {
|
|
38402
38425
|
"screenId": "himi_sign_in",
|
|
38403
38426
|
"mode": "signin",
|
|
38404
|
-
"intent": "book"
|
|
38405
|
-
|
|
38427
|
+
"intent": "book",
|
|
38428
|
+
"modal": "1"
|
|
38429
|
+
},
|
|
38430
|
+
"presentation": "sheet"
|
|
38406
38431
|
}
|
|
38407
38432
|
}
|
|
38408
38433
|
},
|
|
@@ -38427,8 +38452,10 @@ const TEMPLATED: { pattern: string; display: string; liveSeed?: LiveSeed; static
|
|
|
38427
38452
|
"params": {
|
|
38428
38453
|
"screenId": "himi_sign_in",
|
|
38429
38454
|
"mode": "signin",
|
|
38430
|
-
"intent": "book"
|
|
38431
|
-
|
|
38455
|
+
"intent": "book",
|
|
38456
|
+
"modal": "1"
|
|
38457
|
+
},
|
|
38458
|
+
"presentation": "sheet"
|
|
38432
38459
|
}
|
|
38433
38460
|
}
|
|
38434
38461
|
},
|
|
@@ -38453,8 +38480,10 @@ const TEMPLATED: { pattern: string; display: string; liveSeed?: LiveSeed; static
|
|
|
38453
38480
|
"params": {
|
|
38454
38481
|
"screenId": "himi_sign_in",
|
|
38455
38482
|
"mode": "signin",
|
|
38456
|
-
"intent": "book"
|
|
38457
|
-
|
|
38483
|
+
"intent": "book",
|
|
38484
|
+
"modal": "1"
|
|
38485
|
+
},
|
|
38486
|
+
"presentation": "sheet"
|
|
38458
38487
|
}
|
|
38459
38488
|
}
|
|
38460
38489
|
},
|
|
@@ -598,6 +598,9 @@ function initial(): S {
|
|
|
598
598
|
};
|
|
599
599
|
}
|
|
600
600
|
|
|
601
|
+
/** How the app opens its sign-in screen, from the appear params ("" = push). */
|
|
602
|
+
let signInPresentation: "" | "sheet" | "full_screen" = "";
|
|
603
|
+
|
|
601
604
|
const svc = defineService<S, {
|
|
602
605
|
load: (s: S, p?: unknown) => Promise<S | undefined>;
|
|
603
606
|
retry: (s: S) => Promise<S | undefined>;
|
|
@@ -614,6 +617,8 @@ const svc = defineService<S, {
|
|
|
614
617
|
actions: {
|
|
615
618
|
load: async (s, p) => {
|
|
616
619
|
stashCopy(p);
|
|
620
|
+
const presentation = argOf(p, "signInPresentation");
|
|
621
|
+
if (presentation === "sheet" || presentation === "full_screen") signInPresentation = presentation;
|
|
617
622
|
const fw = Number(argOf(p, "firstWeekday") || s.firstWeekday);
|
|
618
623
|
const today = dayKey(new Date());
|
|
619
624
|
// First appearance: open on this week. A re-appearance keeps the member's place and
|
|
@@ -739,7 +744,10 @@ const svc = defineService<S, {
|
|
|
739
744
|
const before = await memberAccess().catch(() => undefined);
|
|
740
745
|
if (!before?.email) {
|
|
741
746
|
busy = false;
|
|
742
|
-
|
|
747
|
+
// As a modal it only closes, and this screen loads again (its re-appearance reload).
|
|
748
|
+
return navigate(s, signInPresentation
|
|
749
|
+
? { screenId: "himi_sign_in", presentation: signInPresentation, args: { mode: "signin", modal: "1" } }
|
|
750
|
+
: { screenId: "himi_sign_in", args: { mode: "signin", returnScreenId: "himi_agenda" } });
|
|
743
751
|
}
|
|
744
752
|
const name = await memberName();
|
|
745
753
|
const current = await memberAccess().catch(() => undefined);
|