@wix/himalaya-cli 0.1157.0 → 0.1158.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/cli.mjs CHANGED
@@ -6783,6 +6783,7 @@ function walk(node, registry, path2, out2) {
6783
6783
  var SYSTEM_FONT_FAMILY, TONAL_ELEVATION_ALPHA, COLOR_TOKEN_PROPS, TYPOGRAPHY_TOKEN_PROPS;
6784
6784
  var init_tokens = __esm({
6785
6785
  "../../core/ts/src/tokens.ts"() {
6786
+ "use strict";
6786
6787
  SYSTEM_FONT_FAMILY = "$system";
6787
6788
  TONAL_ELEVATION_ALPHA = [0, 0.05, 0.08, 0.11, 0.12, 0.14];
6788
6789
  COLOR_TOKEN_PROPS = {
@@ -6833,6 +6834,7 @@ function resolveTranslation(value, catalog, sourceCatalog) {
6833
6834
  var BINDING_SIGILS, BINDING_SIGIL_RE, TRANSLATION_PREFIX, TRANSLATION_KEY_RE;
6834
6835
  var init_bindings = __esm({
6835
6836
  "../../core/ts/src/bindings.ts"() {
6837
+ "use strict";
6836
6838
  BINDING_SIGILS = ["service", "flag", "item", "t"];
6837
6839
  BINDING_SIGIL_RE = new RegExp(
6838
6840
  `^\\$(?:${BINDING_SIGILS.filter((s) => s !== "t").join("|")})\\b|^\\$t:`
@@ -450,6 +450,14 @@ I want to create a mobile app for my business. Follow this skill:
450
450
  https://apps.wix.com/_api/himi-serve/skill.md
451
451
  ```
452
452
 
453
+ ## Home-screen widgets
454
+
455
+ Read `himi docs widgets` **before** you write a `defineWidget`. A widget is a frozen poster frame:
456
+ its `render` runs once, at build time, over each service's **initial** `state`. Actions, `fetch` and
457
+ Wix data never reach it, and there is no live-data path yet, so a widget written over fetched data
458
+ ships its placeholder. Design it around initial state, constants or a `timeline`, and deep-link the
459
+ tap into the screen that has the live data.
460
+
453
461
  ## Native UX evidence gate
454
462
 
455
463
  Before declaring a multi-screen app ready, run `himi ux audit --content . --strict` and resolve its findings. After driving each available native shell, persist the actual checks with `himi ux record`; record an unavailable platform with its reason rather than claiming coverage.
package/guides/index.json CHANGED
@@ -190,6 +190,12 @@
190
190
  "category": "Workers & SDKs",
191
191
  "file": "cms-collections.md"
192
192
  },
193
+ {
194
+ "id": "widgets",
195
+ "title": "Home-screen widgets (what a widget can show)",
196
+ "category": "Workers & SDKs",
197
+ "file": "widgets.md"
198
+ },
193
199
  {
194
200
  "id": "wix-sdk-overview",
195
201
  "title": "Wix SDK overview (front door)",
@@ -0,0 +1,233 @@
1
+ # Home-screen widgets
2
+
3
+ A widget is a **frozen poster frame** of your app. It is drawn by the operating system on a home
4
+ screen, lock screen or watch face, and opened with a tap. Your worker never runs
5
+ inside it. This page explains where a widget's content comes from, because that is the part that
6
+ surprises people: **a widget shows what your worker's state is at build time, not what the worker
7
+ later fetches.**
8
+
9
+ ## The one rule: a widget renders initial state, once
10
+
11
+ You declare a widget with `defineWidget` in the same worker file as your `defineService`:
12
+
13
+ ```ts
14
+ import { defineWidget } from "@wix/himalaya/widget";
15
+
16
+ defineWidget("<widget id>", {
17
+ family: "ios_home", // which OS surface — see the table below
18
+ size: "small",
19
+ deepLink: "<your app scheme>://screen/<screen id>",
20
+ refresh: { ttlSeconds: 900 },
21
+ render: (services) => ({ /* a component tree of literals */ }),
22
+ });
23
+ ```
24
+
25
+ When your app is built or served, the platform **produces** each widget:
26
+
27
+ 1. It evaluates your worker in a sandbox and records each `defineService`'s **initial `state`**.
28
+ 2. It calls `render(services)` **once, synchronously**, where `services` is
29
+ `{ <service name>: <its initial state> }`.
30
+ 3. It freezes the tree `render` returned and serves it as the widget's snapshot.
31
+
32
+ So, inside that sandbox:
33
+
34
+ | What your worker does | What the widget sees |
35
+ |---|---|
36
+ | `state: { … }` in `defineService` | ✅ exactly that object |
37
+ | Constants and plain functions in the file | ✅ available to `render` |
38
+ | An action (`load`, `refresh`, …), sync or `async` | ❌ never runs — the widget keeps the initial state |
39
+ | `fetch`, Wix data queries, any Wix SDK call | ❌ `fetch` throws; nothing it would return arrives |
40
+ | `setTimeout` / `setInterval` | ❌ never fire |
41
+ | `$service:` / `$item` bindings in the tree | ❌ rejected — the snapshot must be literals |
42
+
43
+ `render` must therefore be **synchronous and side-effect free**: no network, no awaiting, no
44
+ bindings, only **resolved literal values**. Reading the clock is the one allowed dependency.
45
+ `Date.now()` works and is evaluated at produce time, so a value computed from it is as fresh as
46
+ the last produce (see [refresh](#refresh-taps-and-previews)). A `render` that throws, or returns a
47
+ tree with a binding in it, makes that widget fail to produce.
48
+
49
+ ### There is no live-data path yet
50
+
51
+ Data your worker fetches **cannot** reach a widget today. `refresh.pushTopic` exists in the type
52
+ definitions, but nothing consumes it yet: setting it does not make the widget update when data
53
+ changes. Plan the widget around what you know when the app is built — a call to action, a
54
+ constant, or a schedule (see [timelines](#content-whose-future-you-know-live-time-and-timelines)) — and let the
55
+ tap take the user into the app, where the live data is.
56
+
57
+ ## A widget that stays inside the contract
58
+
59
+ This widget shows the studio's name and a fixed call to action, both from initial state. Tapping
60
+ it opens the booking screen, which loads the real schedule.
61
+
62
+ <!-- widget-example: within-contract -->
63
+ ```ts
64
+ import { defineService } from "@wix/himalaya/state";
65
+ import { defineWidget } from "@wix/himalaya/widget";
66
+
67
+ defineService("studio", {
68
+ state: { name: "Harbor Yoga", cta: "Book your next class" },
69
+ });
70
+
71
+ defineWidget("studio.book", {
72
+ family: "ios_home",
73
+ size: "small",
74
+ deepLink: "harboryoga://screen/book",
75
+ refresh: { ttlSeconds: 86400 },
76
+ render: (services) => {
77
+ const studio = services.studio as { name: string; cta: string };
78
+ return {
79
+ id: "root",
80
+ body: { package: "himalaya", type: "VStack", props: { spacing: 4, children: [
81
+ { id: "name", body: { package: "himalaya", type: "Text", props: { text: studio.name, style: "headline" } } },
82
+ { id: "cta", body: { package: "himalaya", type: "Text", props: { text: studio.cta, style: "caption" } } },
83
+ ] } },
84
+ };
85
+ },
86
+ });
87
+ ```
88
+
89
+ It produces exactly this frozen tree:
90
+
91
+ <!-- widget-example: within-contract.payload -->
92
+ ```json
93
+ {
94
+ "id": "root",
95
+ "body": { "package": "himalaya", "type": "VStack", "props": { "spacing": 4, "children": [
96
+ { "id": "name", "body": { "package": "himalaya", "type": "Text", "props": { "text": "Harbor Yoga", "style": "headline" } } },
97
+ { "id": "cta", "body": { "package": "himalaya", "type": "Text", "props": { "text": "Book your next class", "style": "caption" } } }
98
+ ] } }
99
+ }
100
+ ```
101
+
102
+ ## The mistake: a widget over fetched data
103
+
104
+ This is the natural way to write it, and it ships the placeholder:
105
+
106
+ <!-- widget-example: anti-pattern -->
107
+ ```ts
108
+ import { defineService } from "@wix/himalaya/state";
109
+ import { defineWidget } from "@wix/himalaya/widget";
110
+
111
+ defineService("latest", {
112
+ state: { title: "Loading…" },
113
+ actions: {
114
+ async load(s) {
115
+ const res = await fetch("https://example.com/latest.json");
116
+ const { title } = (await res.json()) as { title: string };
117
+ return { ...s, title };
118
+ },
119
+ },
120
+ });
121
+
122
+ defineWidget("latest.story", {
123
+ family: "ios_home",
124
+ size: "small",
125
+ deepLink: "myapp://screen/home",
126
+ refresh: { ttlSeconds: 900 },
127
+ render: (services) => ({
128
+ id: "w",
129
+ body: { package: "himalaya", type: "Text", props: { text: (services.latest as { title: string }).title } },
130
+ }),
131
+ });
132
+ ```
133
+
134
+ `load` never runs at produce time, so `render` reads the initial state and the widget says
135
+ "Loading…" forever:
136
+
137
+ <!-- widget-example: anti-pattern.payload -->
138
+ ```json
139
+ { "id": "w", "body": { "package": "himalaya", "type": "Text", "props": { "text": "Loading…" } } }
140
+ ```
141
+
142
+ Calling the action yourself at the top level (`svc.load()`) does not help either: at produce time
143
+ every action resolves to the initial state.
144
+
145
+ ## Content whose future you know: live time and timelines
146
+
147
+ **Countdowns and clock text: `Text.timeRef`.** A `Text` with
148
+ `timeRef: { at: <epoch seconds>, style: "timer" | "relative" | "date" | "time" | "offset" }`
149
+ shows a time value that overrides the `Text`'s `text`. How live it is depends on the platform:
150
+
151
+ - **iPhone (home, lock, Live Activity)**: the OS ticks it every second, with no refresh and no worker.
152
+ - **Android home screen**: it is formatted only when the widget is redrawn, which the OS
153
+ schedules (typically no more often than every 15 minutes), so do not rely on it to count seconds.
154
+ - **`web_card`**: `timeRef` is ignored and the `Text`'s own `text` is shown, so always set a
155
+ `text` fallback.
156
+
157
+ ```ts
158
+ { id: "starts", body: { package: "himalaya", type: "Text",
159
+ props: { text: "Starts soon", timeRef: { at: Math.floor(Date.now() / 1000) + 3600, style: "timer" } } } }
160
+ ```
161
+
162
+ **Discrete future states: `timeline`.** When you know in advance which *different content* the
163
+ widget should show later — today's agenda, a streak that resets at midnight — add a `timeline`. It returns frozen frames, each with the epoch
164
+ second it becomes current, and the OS advances through them with no network and no worker:
165
+
166
+ ```ts
167
+ // frame(title, time) returns a component tree; DAY_START is today's midnight in epoch seconds.
168
+ defineWidget("studio.agenda", {
169
+ family: "ios_home",
170
+ size: "small",
171
+ deepLink: "harboryoga://screen/schedule",
172
+ refresh: { ttlSeconds: 900 },
173
+ render: () => frame("Morning flow", "7:00"), // also frame 0 for older clients
174
+ timeline: () => [
175
+ { startsAt: DAY_START + 7 * 3600, component: frame("Morning flow", "7:00") },
176
+ { startsAt: DAY_START + 12 * 3600, component: frame("Lunch stretch", "12:00") },
177
+ ],
178
+ });
179
+ ```
180
+
181
+ `timeline` follows the same rule as `render`: synchronous, no network, literals only. It is
182
+ evaluated at produce time, so its frames are only as current as the last produce, and it keeps at
183
+ most 48 frames — never bake a second-by-second countdown into it; that is what `timeRef` is for.
184
+
185
+ ## Families and sizes
186
+
187
+ | `family` | Surface |
188
+ |---|---|
189
+ | `ios_home` | iPhone home screen |
190
+ | `ios_lock` | iPhone lock screen |
191
+ | `android_home` | Android home screen |
192
+ | `web_card` | A card on the web preview |
193
+ | `ios_live_activity` | iPhone Live Activity |
194
+ | `watch_complication` | Watch face complication |
195
+
196
+ `size` is the OS size hint: `small`, `medium` or `large` for home-screen widgets, and `inline`,
197
+ `circular` or `rectangular` for lock-screen and complication slots.
198
+
199
+ One worker can declare several widgets, one per family and size. To give a widget a *different
200
+ layout* from your phone screen while reading the same services, use `defineView` with a
201
+ `widget:<family>` surface instead. It is the same frozen contract, chosen per surface, and
202
+ `size`, `deepLink`, `refresh` and `timeline` are accepted only on those frozen surfaces.
203
+
204
+ **In-car screens are not a widget family.** The car display is authored with
205
+ `defineView("<view id>", { surface: "car", deepLink, refresh, render })`: frozen like a widget,
206
+ with the same rules for `render`. `deepLink` and `refresh` are required, as for a widget; `size` is
207
+ not, because the car lays out its own rows. It never goes through `defineWidget`.
208
+
209
+ ## Refresh, taps and previews
210
+
211
+ - **`refresh.ttlSeconds`** is how long a snapshot may be served before it is produced again. A new
212
+ produce still renders the *initial* state, so a shorter TTL only helps content that is computed
213
+ from the clock at produce time. It does not make fetched data appear.
214
+ - **`refresh.preferredRefreshSeconds`** (optional) is a hint to the OS background-refresh
215
+ scheduler about how often to redraw the widget; when unset, `ttlSeconds` is used. The OS may
216
+ clamp or defer it (Android never redraws more often than every 15 minutes), so treat it as a
217
+ request, not a schedule.
218
+ - **`deepLink`** is where a tap takes the user. Point it at the screen that shows the live version
219
+ of what the widget summarises.
220
+ - **Preview a widget** while you work: `himi run --watch --content .` serves it at
221
+ `GET /v1/widget/<widget id>`, and `GET /v1/widgets` lists every widget the app produces.
222
+ `himi surfaces --measure-widgets` reports which families your package actually produces.
223
+
224
+ ## What `himi validate` checks
225
+
226
+ A widget is **declared** when a worker calls `defineWidget` (or `defineView` with a `widget:`
227
+ surface). If a declared widget fails to produce — `render` threw, returned a binding, or used a
228
+ component its family does not allow — `himi validate` reports `widget-not-produced`. It is a
229
+ warning by default and an **error under `--strict`**, so a package cannot ship a widget it
230
+ declared but that renders nothing.
231
+
232
+ A widget that produces the *placeholder* (the mistake above) is not an error: it renders, it just
233
+ shows initial state. Check the snapshot at `GET /v1/widget/<widget id>` before you ship.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wix/himalaya-cli",
3
- "version": "0.1157.0",
3
+ "version": "0.1158.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": [
@@ -67,5 +67,5 @@
67
67
  "artifactId": "himalaya-cli"
68
68
  }
69
69
  },
70
- "falconPackageHash": "3855653c89440e946466c0cb88bbc773e9933df0411400c4fbd70c04"
70
+ "falconPackageHash": "e2949fb643b50db7dff29f93ca7ee3235cd80cdac4a32d59ba2f89a1"
71
71
  }