@wix/himalaya-cli 0.1454.0 → 0.1456.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.
@@ -195,6 +195,12 @@ long-form Ricos content rather than flattening it into text rows.*
195
195
  `Picker`/`WheelPicker`, `DatePicker`, `ColorPicker`, `OtpInput`, `ChipsInput`. *Reach for `Toggle` for
196
196
  on/off, `SelectionControl` for pick-from-set — not a row of `Button`s.*
197
197
 
198
+ **Search** — use a dedicated search screen by default when search starts from a broader context
199
+ (such as Home or a catalog); keep search inline when it only filters the list already on screen.
200
+ Read `himi docs recipe-search` for the dedicated flow, including focus, debounce, result controls,
201
+ return navigation, states, and the documented `$env:topBarSearch` fallback for clients without the
202
+ native top-bar API.
203
+
198
204
  **Validation is a prop on the field, not a reason to reach for `Form`.** Put a `validation` block on
199
205
  any `TextField` — loose or wrapped — and the renderer evaluates it with no worker running and no
200
206
  round-trip:
@@ -284,7 +290,7 @@ Concrete patterns lifted from real usage:
284
290
  - **Screen root** — `withModifiers(root, { padding: { leading: 16, trailing: 16, top: 16, bottom: 40 }, background: { token: "surfacePrimary" } })`. Consistent gutter, comfortable bottom inset.
285
291
  - **Row with a leading label and trailing detail** — `hstack({ spacing: 8, children: [ icon, text, spacer(), trailingText ] })`. The `spacer()` does the push; don't hand-pad.
286
292
  - **Fill-width controls share a row** — put fill-width controls in **one** `HStack` and let them share it (weight/flex), rather than stacking `fillMaxWidth` elements. This is a real iOS↔Android parity requirement, not a preference — a full-width control alone in an HStack renders differently across platforms.
287
- - **A screen that owns its top bar (search beside back)** — set `topBar: { leading: "none" }` and put the row in `stickyHeader`: `hstack([ iconButton({ icon: "arrow_left", accessibilityLabel: "Back", action: { actionId: "nav.back", presentation: "back" } }), withModifiers(textField({ placeholder: "Search", … }), { weight: 1 }) ])`. With **both** set, every shell draws no bar and no back control of its own (iOS hides the system bar, Android floats no chevron and reserves no band, web hides its bar on every look), so the row is the only top bar. `leading: "none"` without a `stickyHeader` keeps the bar's title and trailing actions but drops the back control (and the hamburger, for that screen). System back — hardware, gesture, edge swipe, browser — still pops. Any other `leading` value keeps today's chrome (#3486; demo: `gallery_search_topbar`, linked from the gallery's "Lists & data").
293
+ - **A screen that owns its top bar (search beside back)** — set `topBar: { leading: "none" }` and put the row in `stickyHeader`: `hstack([ iconButton({ icon: "arrow_left", accessibilityLabel: "Back", action: { actionId: "nav.back", presentation: "back" } }), withModifiers(textField({ placeholder: "Search", … }), { weight: 1 }) ])`. With **both** set, every shell draws no bar and no back control of its own (iOS hides the system bar, Android floats no chevron and reserves no band, web hides its bar on every look), so the row is the only top bar. `leading: "none"` without a `stickyHeader` keeps the bar's title and trailing actions but drops the back control (and the hamburger, for that screen). System back — hardware, gesture, edge swipe, browser — still pops. Any other `leading` value keeps today's chrome; demo: `gallery_search_topbar`, linked from the gallery's "Lists & data".
288
294
  - **Cell list** — `card([ vstack({ spacing: 0, alignment: "leading", children: [ …ListItem cells… ] }) ])`. `spacing: 0` because the cells own their own dividers/padding.
289
295
  - **Overlay** — `zstack({ alignment: "center", children: [ backdropImage(cornerRadius 12), withModifiers(label, { padding, background: { token: "accentPrimary" }, cornerRadius: 10 }) ] })`.
290
296
  - **Responsive** — `grid({ columns: 1, columnsMedium: 2, spacing: 12 })` for 1-up on phone / 2-up on tablet; `splitView({ primary, detail, primaryMaxWidth: 280 })` for master/detail that stacks on a phone; set `contentMaxWidth` in tokens so wide surfaces keep a readable column instead of running edge-to-edge.
package/guides/hhp.md CHANGED
@@ -145,6 +145,22 @@ transitions, haptics, and measurement remain explicit native modifiers.
145
145
 
146
146
  Local HTML images resolve relative to their document and must live in the app's
147
147
  declared assets directory. They use the existing content-addressed asset channel.
148
+ The descriptor carries a host-less `/v1/asset/<sha>?app=<id>` URL. iOS and Android
149
+ resolve it against the origin that served the screen; on web it resolves against the page,
150
+ so it loads when the web host is served from the serve origin (`himi run`, `/app/a/<id>`)
151
+ but not yet behind a gateway path prefix or inside an embed.
152
+
153
+ iOS and Android have no SVG image decoder. When an `<img>` points at a package
154
+ `.svg`, the compiler renders it to a PNG at 3x the SVG's intrinsic size, writes
155
+ the PNG to `<assets>/hhp-raster/<name>.<hash>.png` (a nested name is flattened,
156
+ with `/` written as `__`), and references that PNG on every
157
+ surface. The compiler owns that folder: do not edit it. It does need to be
158
+ committed and pushed with the package. Rasterizing uses the optional image
159
+ toolchain (`sharp`). Without it, the build reuses a committed raster, but only
160
+ one rendered from the SVG's current bytes (the hash in its name). If no such
161
+ raster is committed, `himi hhp lint` and `himi build` fail with
162
+ `HHP-ASSET-SVG`. To fix it, export the image as PNG or WebP, or install `sharp`.
163
+ Images chosen from controller state are not supported yet.
148
164
 
149
165
  ## SDKs and preview fixtures
150
166
 
@@ -1,5 +1,130 @@
1
1
  # Recipe: search & filter a list
2
2
 
3
+ ## Choose the right search surface
4
+
5
+ Use a **dedicated search screen by default** when search starts a broader discovery
6
+ flow—for example, the user taps Search from Home or a catalog. Push the search
7
+ screen over the current context so Back returns to the same place, and keep the
8
+ query, sort, and filters with that search session.
9
+
10
+ Use **inline filtering** when the user is already looking at a list and the query
11
+ only narrows the rows on that screen. The rest of this recipe's original
12
+ `SearchField` + worker example is that inline pattern.
13
+
14
+ ## Dedicated search screen
15
+
16
+ The dedicated flow has two renderers for one search model:
17
+
18
+ 1. Use the native top-bar search slot (`topBar.searchable`) only when the
19
+ current client explicitly supports it. This proposed slot belongs in the
20
+ shell-owned top bar; choose `placement: "bar"` for a pushed search screen so
21
+ the search field sits beside Back. The slot is **not implemented in the
22
+ current client schema or renderers**. Treat this shape as a target API; do
23
+ not expect today's `himi validate` or `himi render` to recognize it.
24
+ 2. Until a client supports the slot, provide the `sticky_header` fallback
25
+ described below. The `$env:topBarSearch` gate hides that row only on a client
26
+ that actually draws native search.
27
+
28
+ ### Query lifecycle
29
+
30
+ - **Enter and focus.** From a wider context, push the dedicated search screen
31
+ and give it a real Back route. With the native slot, seed `focusPath` with a
32
+ new non-empty token when the screen opens; that focuses the field and opens
33
+ the keyboard. If the screen has no worker, seed the token through local state.
34
+ - **Debounce.** Let `onChange` update the query and start the search after the
35
+ user pauses. The proposed native slot uses 250 ms when `debounceMs` is zero
36
+ or unset; a pending change flushes before `onSubmit`, so the submitted query
37
+ is current.
38
+ For the existing inline `SearchField`, set `debounceMs` explicitly (150–300 ms
39
+ for local filtering; 300–400 ms for a network search).
40
+ - **Clear.** Clear sets the query to `""`, runs `onChange` with the empty value,
41
+ and returns to the initial state. It is not cancellation and must not invoke
42
+ `onCancel`.
43
+ - **Submit.** Use `onSubmit` for the keyboard Search action, especially when
44
+ requests are expensive or search should wait for an explicit commit. It
45
+ receives the final text as `args.value`; do not issue a duplicate request if
46
+ the final debounced `onChange` already did so.
47
+ - **Results and controls.** Keep sort, filters, result count, and the result
48
+ list in the search screen. Bind them to the same worker state as the query.
49
+ Changing a filter reruns the search without losing the query; changing the
50
+ query should reset paging/scroll as appropriate. Keep the result count
51
+ consistent with the currently applied controls.
52
+ - **Return navigation.** Use the shell's Back action to pop the search screen
53
+ and reveal the prior screen in its existing state. Do not replace the home or
54
+ catalog route to enter search. If product requirements retain a query across
55
+ separate visits, persist it intentionally; otherwise start a fresh session.
56
+
57
+ ### Screen states
58
+
59
+ Model these states separately in the worker; an empty `items` array alone does
60
+ not say why there are no rows.
61
+
62
+ | State | What to show |
63
+ | --- | --- |
64
+ | Initial, no query | Search prompt plus useful recent queries, categories, or suggestions. Avoid presenting this as a failed search. |
65
+ | Loading | First load: a skeleton or progress state. Refresh: keep current results visible and show a compact progress indicator when possible. |
66
+ | Results | Matching rows, result count, and the available sort/filter controls. |
67
+ | No matches | Explain that the current query or filters found nothing; offer Clear or a way to adjust them. |
68
+ | Empty catalog | Explain that the catalog itself has no items yet, independent of the query; provide a useful browse/back action. |
69
+ | Error | Show an error with Retry, retain the query and controls, and keep it distinct from no matches. |
70
+
71
+ ### Native slot and old-client fallback
72
+
73
+ The following pairs the proposed native slot with the supported fallback. The
74
+ `searchable` field is illustrative: current clients do not decode it. The
75
+ `stickyHeader` root is gated, not just its search field, so the whole fallback
76
+ row disappears when native search is drawn. `leading: "none"` lets the fallback
77
+ own the bar on a client without the native slot. On a client that draws native
78
+ search, the shell must interpret this as `leading: "auto"` while search is
79
+ active so the native Back control remains visible.
80
+
81
+ ```ts
82
+ const descriptor = {
83
+ topBar: {
84
+ // Native-search clients interpret this as "auto" to keep the shell Back control visible.
85
+ leading: "none",
86
+ // Proposed target API; current clients do not decode this yet.
87
+ searchable: {
88
+ placement: "bar",
89
+ placeholder: "Search products",
90
+ valuePath: "$service:search.query",
91
+ focusPath: "$service:search.focusToken",
92
+ debounceMs: 250,
93
+ onChange: { actionId: "tier5.search.setQuery" },
94
+ onSubmit: { actionId: "tier5.search.submit" },
95
+ onCancel: { actionId: "tier5.search.cancel" },
96
+ },
97
+ },
98
+ // Fallback for clients without native search. `hiddenPath` belongs on the sticky header root.
99
+ stickyHeader: withModifiers(hstack({
100
+ spacing: 4,
101
+ children: [
102
+ iconButton({
103
+ icon: "arrow_left",
104
+ accessibilityLabel: "Back",
105
+ action: { actionId: "nav.back", presentation: "back" },
106
+ }),
107
+ withModifiers(searchField({
108
+ placeholder: "Search products",
109
+ value: "$service:search.query",
110
+ debounceMs: 250,
111
+ onChange: { actionId: "tier5.search.setQuery" },
112
+ }), { weight: 1 }),
113
+ ],
114
+ }), { hiddenPath: "$env:topBarSearch" }),
115
+ };
116
+ ```
117
+
118
+ The gate is a client `$env:` value, not an app configuration flag.
119
+ Clients that do not draw the slot leave the fallback visible. On older clients
120
+ that must open the fallback focused, use a `TextField` with its supported
121
+ `focusPath` and `valuePath`; `SearchField.focusPath` may not exist in those
122
+ binaries. Keep one search row: do not add a
123
+ second visible field outside the gated sticky header.
124
+
125
+ For the simpler inline list-filter case, continue with the worker-side recipe
126
+ below.
127
+
3
128
  Add a **`SearchField`** above a dynamic list and filter **in the worker** — keep
4
129
  the full result set in state, derive the visible rows from a query string. No
5
130
  network round-trip per keystroke, instant results, and the empty state can say
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wix/himalaya-cli",
3
- "version": "0.1454.0",
3
+ "version": "0.1456.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": "eefcf19354e83f21581f78ca930495b7ae70059df5480ba579c463cb"
74
+ "falconPackageHash": "6a9425d0ac743f2ada10d1e2e6c84f4ef1ce790732d9fff33a36c315"
75
75
  }