@wix/himalaya-cli 0.850.0 → 0.852.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 (25) hide show
  1. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/sdk/local-db/src/index.d.ts +23 -0
  2. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/sdk/sdk-types/src/capabilities.generated.d.ts +2 -2
  3. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/sdk/secure-storage/src/index.d.ts +23 -0
  4. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/ts/src/define-app.d.ts +5 -1
  5. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/ts/src/schema.d.ts +4 -4
  6. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/audio-record.mjs +2 -2
  7. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/calendar.mjs +2 -2
  8. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/calls.mjs +2 -2
  9. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/contacts.mjs +2 -2
  10. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/core.mjs +15 -0
  11. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/local-db.mjs +10 -0
  12. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/location.mjs +2 -2
  13. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/motion.mjs +2 -2
  14. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/photos.mjs +2 -2
  15. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/sdk-types.mjs +2 -2
  16. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/secure-storage.mjs +10 -0
  17. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/tasks.mjs +2 -2
  18. package/dist/cli.mjs +3630 -3286
  19. package/guides/app-config.md +23 -1
  20. package/guides/component-reference.json +1 -1
  21. package/guides/component-reference.md +4 -4
  22. package/guides/mobile-ux.md +45 -32
  23. package/guides/sdk-reference.json +12 -0
  24. package/package.json +2 -2
  25. package/templates/surface-showcase/config.ts +1 -1
@@ -18,6 +18,12 @@ A content package configures itself in three layers:
18
18
  - **Theming:** `designTokensPath` → tokens.json (the kit is chosen *inside* tokens.json's `kit` block).
19
19
  - `capabilities: Capability[]` — the app's declared native capabilities.
20
20
  - `navigation: NavigationConfig`.
21
+ - `entities: Record<string, EntityConfig>` — **required**, and `entities: {}` is the normal value.
22
+ Each entry is a build-time DOMAIN entity and its cache materialization shape (`{ file }` or
23
+ `{ directory }`). It is **not** where an app declares data it persists at runtime: on-device
24
+ storage is `dataSchema` (the local-DB collections the `local_database` capability admits) and
25
+ Wix-backed collections are `cms`. Omitting the key entirely is a `defineApp` error naming the
26
+ field — the scaffold writes `entities: {},` for you.
21
27
  - `workerSubtreeScreens?: string[]` — the fail-closed gate for worker-built layout. Absent/empty denies
22
28
  `WorkerSubtree` everywhere; each listed stable screen id may contain at most one slot and still
23
29
  requires an explicit descriptor policy plus shipped fallback.
@@ -99,7 +105,7 @@ declare `wix_api`; the Wix Platform facade needs `wix_platform`.
99
105
 
100
106
  App-level capability vocabulary: `camera`, `barcode_scanner`, `biometrics`, `secure_storage`, `share`, `push`, `file_picker`, `files`, `open_url`, `pdf`, `clipboard`, `photos`, `presigned_upload`, `ui`, `calendar`, `contacts`, `tasks`, `audio_record`, `audio_record_background`, `calls`, `location`, `location_background`, `motion`, `media_playback`, `webview`, `browser_auth`, `haptic`, `himi_apps`, `iap`, `keyboard`, `wix_cashier`, `live_activity`, `local_database`, `speech`, `speaker_diarization`, `system_intents`, `on_device_ai`, `app_info`, `release_control`, `godot`.
101
107
 
102
- ## What `himi validate` checks (6 passes)
108
+ ## What `himi validate` checks (7 passes)
103
109
 
104
110
  1. **schema + tokens + components** — JSON-schema conformance; every `color:`/`spacing:`/
105
111
  `typography:`/`radius:` reference must exist in tokens.json; only allowlisted components.
@@ -115,6 +121,22 @@ App-level capability vocabulary: `camera`, `barcode_scanner`, `biometrics`, `sec
115
121
  `"watchOS"` is an ERROR — the server silently drops it), and every `navigation.surfaces`
116
122
  key must be a real surface inside the envelope (outside ⇒ warning: the server drops it,
117
123
  narrow-only rule).
124
+ 7. **nav** — the `navigation` block itself. ERRORS: an unknown key under `navigation` (the server
125
+ reads named fields only, so a typo like `tabs` never reaches a device); a missing `scheme`;
126
+ a nav field written at the TOP LEVEL of `defineApp` (`linkableScreens`, `tabBar`, `navKind`, …),
127
+ which nothing reads there; the MIRROR of that — `splash` or `locale` written UNDER
128
+ `navigation`, which typechecks because both are `NavigationConfig` members but is still
129
+ dropped: they are SERVED in that envelope and AUTHORED beside it, so `deriveAppMeta`
130
+ reads `config.splash` / `config.locale`. They land there for DIFFERENT reasons —
131
+ `splash` has a second consumer at BUILD time (the native build generates the OS launch
132
+ screen from the same declaration, which is why it cannot live inside an OTA-only block),
133
+ while `locale` is an app-level runtime setting merely carried inside the served
134
+ navigation envelope; `tabBar`/`drawer`/`sidebar` that is not `{ items: [ … ] }`; and an
135
+ unknown `navKind` value (the union is `bottomTabs` · `single` · `drawer` · `sidebar` ·
136
+ `responsive`, and the client coalesces anything else to `bottomTabs`). WARNING (`--strict`
137
+ promotes it): several top-level linkable screens under an effective `bottomTabs` navKind with
138
+ no `tabBar.items` — the shell has no destinations to root on, so every one past the home
139
+ screen is unreachable.
118
140
 
119
141
  ## Screen descriptor anatomy
120
142
 
@@ -881,7 +881,7 @@
881
881
  "ScreenDescriptor": "export interface ScreenDescriptor {\n screenId: string;\n renderer: RendererKind;\n title?: string;\n /** Present when `renderer` is `sdui_intent` or `sdui_layout`. */\n payload?: Component;\n /** A component pinned ABOVE the scrolling payload (sticky tab-header / toolbar).\n * Rendered by the host outside the screen scroll, so it stays put as content\n * scrolls. Optional; absent = no sticky header. */\n stickyHeader?: Component;\n /** A component pinned BELOW the scrolling payload (sticky footer — price/confirm\n * bar, peeking bottom-sheet). Rendered outside the screen scroll by the host. */\n stickyFooter?: Component;\n /** When true, the host wraps the screen in a scroll that starts pinned to the\n * bottom and shows a floating \"jump to latest\" button whenever the user scrolls\n * up (a chat transcript). Tapping it animates back to the bottom. Absent/false =\n * a normal top-anchored scroll. Honored by the app host (Router). */\n scrollToBottom?: boolean;\n /** Present when `renderer` is `webview_native_bridge` or `webview_ds`. */\n url?: string;\n capabilitiesRequired?: string[];\n schemaVersion: number;\n togglesApplied?: string[];\n /** Per-session flag/experiment assignments baked in for this request. */\n assignments?: import(\"./flags.js\").Assignment[];\n /** Per-screen top bar (trailing actions +\n * leading/style override). Fully replaces `NavigationConfig.topBarDefault`. */\n topBar?: TopBarConfig;\n etag?: string;\n cacheControl?: CacheControl;\n telemetryScreenName?: string;\n bundleSri?: string;\n manifestVersion?: number;\n /**\n * Present on Tier-5 screens. The presence of this field is the tier-5\n * signal — there is no separate `tier: 5` flag.\n */\n logicBundle?: LogicBundleRef;\n /**\n * Static per-screen input injected as `globalThis.himalaya.input` before the\n * Tier-5 worker bundle evaluates — the worker reads it synchronously in `load()`\n * (e.g. `himalaya.input.id` for a detail screen, or a baked catalog like\n * `himalaya.input.cmsCollections`).\n *\n * A push merges its navigation parameters into this object on web, iOS, and Android.\n * For example, `himi.open({ screenId: \"event\", id: \"$item.id\" })` makes the id\n * available as `himalaya.input.id` in the pushed worker; static `input` remains the\n * fallback for values baked into a descriptor or a templated route. The host also\n * supplies `screenId`. See docs/tier-5-add-a-screen.md → \"Navigate to a detail screen\".\n *\n * Arbitrary JSON object; consumed by the web host (ScreenController) and the\n * native JSWorker.\n */\n input?: Record<string, unknown>;\n /**\n * Tier 3 / Tier 4 — behavior of the hosted WebView. Ignored by SDUI tiers.\n * Authored in the descriptor builder (git); served OTA verbatim.\n */\n webViewConfig?: WebViewConfig;\n}",
882
882
  "Component": "export interface Component {\n id: string;\n body: ComponentBody;\n modifiers?: Modifiers;\n minVersion?: number;\n}",
883
883
  "LogicBundleRef": "export interface LogicBundleRef {\n url: string;\n /** \"sha256-...\" SRI string. */\n integrity: string;\n contentType?: string;\n /** SDK capability ids the bundle is permitted to import / invoke. */\n declaredCapabilities: string[];\n /** Bundle refuses to load on a binary older than this (semver). */\n minBinaryVersion?: string;\n /** Bundle's own semver. */\n bundleVersion?: string;\n /** Base64 detached publisher signature over the bundle bytes\n * (authenticity beyond SRI). Verified by a host-injected verifier when present;\n * unsigned bundles load as today. */\n signature?: string;\n /** The publisher key id a host pins, paired with `signature`. */\n keyId?: string;\n /** Ambient refresh (gap G2): the host invokes `action` (a `tier5.<svc>.<action>`\n * path) every `intervalMs` while the screen is visible — workers have no timer\n * of their own, so the host owns the cadence. */\n refresh?: { intervalMs: number; action: string };\n}",
884
- "Modifiers": "export interface Modifiers {\n padding?: EdgeInsets;\n background?: TokenRef;\n cornerRadius?: number;\n /** Fixed intrinsic width (pt/dp/px). Honored by iOS (`FrameIfPresent`), Android\n * (frame) and web (`modifiers.ts`). With no `ScrollView` in the vocabulary and\n * regions clipped at `maxHeight`, `width`/`height`/`fillMaxWidth` are the main\n * sizing tools for embed/host content. */\n width?: number;\n /** Fixed intrinsic height (pt/dp/px). See `width`. */\n height?: number;\n /** Fill the available cross-axis width (keeps any fixed `height`). Mirrors\n * Compose `fillMaxWidth` / SwiftUI `.frame(maxWidth: .infinity)`. See `width`. */\n fillMaxWidth?: boolean;\n /** CAP the node's laid-out width at this many points. It CONSTRAINS, it\n * does not SET: a node narrower than the cap keeps its intrinsic width, so a\n * short chat bubble stays short while a long one stops at the cap — the\n * asymmetry `width` cannot express and `weight` cannot either (weights divide\n * the *available* space, so every child lands on the same fraction).\n * The cap covers the node's TOTAL width, padding + border included (CSS\n * `max-width` under `box-sizing: border-box`). `fillMaxWidth` grabs the axis but\n * THE CAP WINS — fill + cap is exactly `maxWidth` wide; a literal `width` larger\n * than the cap is clamped to it too. POINTS, not a fraction: a modifier is\n * resolved with no portable notion of \"the row\" on any renderer — compose a\n * percentage from a weighted sibling gap instead. `0`/absent = no cap. */\n maxWidth?: number;\n /** Flex weight along the parent stack's MAIN axis (HStack = width, VStack =\n * height). The stack measures its intrinsic (unweighted) children first, then\n * divides what is LEFT among the weighted ones in proportion to their weights:\n * two children at `1` split the leftover equally, `2` and `1` split it 2:1.\n * Absent (or <= 0) keeps the node at its intrinsic size. Use this — not\n * `fillMaxWidth` — when a node should claim the remaining space *beside* its\n * siblings; a full-bleed fill grabs the whole axis and starves them. Ignored\n * outside an HStack/VStack.\n * CAVEAT — row-sharing controls have NO intrinsic width to measure first: in an\n * HStack a `Button` and a `Toggle` are implicitly weighted, so they DIVIDE the\n * row with your weighted child instead of taking a width ahead of it —\n * `[VStack(weight:1), Button, Button]` is a three-way split, not \"the VStack gets\n * the remainder\". The RELIABLE opt-outs are `iconOnly` and `tertiary`/`link` with\n * `fullWidth` unset. `fullWidth: false` is NOT reliable here: only Android honors\n * it in a weighted row (`wantsRowStretch`); iOS (`WeightedStack`) and web still\n * hand the button a share. Otherwise give the actions their own row.\n * See `himi docs designing-himi-screens` §4. */\n weight?: number;\n /** RTL — pin the subtree's writing direction: `\"ltr\"` | `\"rtl\"` | `\"locale\"`\n * (inherit). The common RTL pattern is the inverse of `Text.textDirection`: pin a\n * container `ltr` (a logotype, an attachment-tile row, a composer button row)\n * inside an otherwise-RTL screen. Absent = inherit the ambient direction. #1060. */\n layoutDirection?: \"ltr\" | \"rtl\" | \"locale\";\n /** Absolute visual translation (pt/dp/px). Applied as the OUTERMOST transform so\n * it shifts the fully-styled node WITHOUT changing its layout footprint — lets a\n * ZStack place polar/absolute children (e.g. AgentOrbit). Negative allowed. */\n offsetX?: number;\n offsetY?: number;\n onTap?: Action;\n /** Double-tap gesture (e.g. Instagram double-tap-to-like). Distinct from onTap. */\n onDoubleTap?: Action;\n accessibilityLabel?: string;\n hidden?: boolean;\n /** Data-bound conditional visibility: a `\"$service:<svc>.<field>\"`\n * or `\"$item.<field>\"` sentinel resolving to a bool at render; when true the\n * subtree fully COLLAPSES (zero layout, like `hidden`), overriding the\n * literal `hidden`. */\n hiddenPath?: string;\n /** Long-press gesture modifier. */\n onLongPress?: Action;\n /** Fires once when the component first appears on screen. */\n onAppear?: Action;\n /** Scoped token overrides for the subtree. */\n theme?: ThemeOverrides;\n /** Names a token from the app's `shadows` registry. */\n shadow?: TokenRef;\n /** Names a token from the app's `gradients` registry (background fill). */\n backgroundGradient?: TokenRef;\n /** Value-driven gradient: a `$service:`/`$item.` path resolving to a gradient\n * TOKEN NAME used as the live background (e.g. weather sky day→night). */\n backgroundGradientPath?: string;\n /** Value-driven background COLOR: a `$service:`/`$item.` path resolving to a\n * color TOKEN NAME used as the live background. Honored on iOS + Android\n * (`BoundBackgroundIfPresent`); web render support is tracked in #1057. */\n backgroundColorPath?: string;\n /** Value-driven background IMAGE: a `$service:`/`$item.` path resolving to an\n * image URL used as the live background. Honored on iOS + Android; web render\n * support is tracked in #1057. */\n backgroundImagePath?: string;\n /** #1051 — MEASURE (publish): report this node's laid-out size into the renderer's\n * self-contained (worker-free) measure store under the named keys, so a SIBLING can\n * feed the number back into its own layout via a `$measure:<key>` binding. The SDUI\n * equivalent of SwiftUI `.onGeometryChange` / Compose `onGloballyPositioned`; the\n * canonical use is a pinned bottom bar whose measured height becomes a scroll view's\n * bottom inset (sticky-footer + scroll-inset, unrepresentable before — #1051). */\n onMeasure?: Measure;\n /** #1051 — bound height: a `\"$measure:<key>\"` (measure store) or\n * `\"$service:<svc>.<field>\"` / `\"$item.<field>\"` (worker/row state) sentinel resolving\n * to a NUMBER at render. Wins over the literal `height`. String path only, so a\n * binding can never blank the node (never overload the numeric `height`). */\n heightPath?: string;\n /** #1051 — bound width. See `heightPath`. Wins over the literal `width`. */\n widthPath?: string;\n /** #1051 — bound TOP inset (the header-height → top-inset idiom). A `$measure:`/\n * `$service:`/`$item.` sentinel resolving to a number; added as top padding. */\n paddingTopPath?: string;\n /** #1051 — bound BOTTOM inset (the sticky-footer-height → bottom-inset idiom). A\n * `$measure:`/`$service:`/`$item.` sentinel resolving to a number; added as bottom\n * padding so the last scroll row clears a pinned bottom bar. */\n paddingBottomPath?: string;\n /** #1053 — INLINE gradient background paint. Distinct from `backgroundGradient`\n * (which names a `gradients` registry token an embed host can't register): this\n * carries its stops/colours in the descriptor, so it works anywhere. Painted as\n * a surface (behind children), below `material`/`backgroundGradient` if those are\n * also set. */\n gradient?: Gradient;\n /** #1053 — gradient ALPHA MASK: the node shows through where the gradient is\n * opaque and fades where it's transparent (a transparent-token stop = fully\n * clear). The material-under-gradient-scrim / scroll-fade-out idiom. Mirrors iOS\n * `.mask()`, Android `BlendMode.DstIn`, web `mask-image`. */\n mask?: Gradient;\n /** Inline border/stroke. */\n border?: Border;\n /** Subtree alpha 0.0–1.0; absent = opaque. */\n opacity?: number;\n /** #433 — constrain the subtree to a width:height ratio (e.g. `1` = square,\n * `1.777` = 16:9). Width-driven: the height derives from the resolved width.\n * Lets a data-bound Grid cell / photo tile hold its shape at any width. */\n aspectRatio?: number;\n /** Names a token from the app's `motions` registry; the subtree\n * fades + slides up on first appearance. */\n appear?: TokenRef;\n /** Surface material token (flat/glass/frosted). */\n material?: TokenRef;\n /** Per-node construction-language override. */\n variant?: string;\n /** Per-node density / motion override. */\n density?: string;\n motion?: string;\n /** Motion grammar (Batch 1) — press/tap feedback style\n * (auto|none|scale|highlight|scaleHighlight|lift); timing comes from `motion`.\n * Applied only when the node is interactive. Unknown degrades to `auto`. */\n pressFeedback?: string;\n /** Motion grammar (Batch 1) — press-coupled haptic\n * (none|selection|light|medium|heavy|success|warning|error); fires with onTap.\n * Default none (opt-in). Web is a no-op. */\n haptic?: string;\n /** Motion grammar (Batch 1) — animate this container's size changes instead of\n * snapping (iOS `.animation(value:)`, Android `animateContentSize`, web height\n * transition). Curve from the active `motion` personality. */\n animateContentSize?: boolean;\n /** Motion grammar (Batch 1) — enter/exit transition; supersedes `appear` when\n * both are present. */\n transition?: Transition;\n /** Motion grammar (Batch 2, scroll-driven) — defer the `transition` enter until\n * the node first crosses into the scroll viewport (reveal-on-scroll). Gated by\n * reduced motion (shown immediately when reduced). */\n revealOnScroll?: boolean;\n /** Motion grammar (Batch 2, scroll-driven) — parallax factor: the node drifts\n * vertically at this fraction of the scroll delta as it moves through the\n * viewport. Gated by reduced motion. Generalizes CollapsingHeader parallaxFactor. */\n parallax?: number;\n /** Motion grammar (Batch 4, rich visual) — when a backgroundGradient is present,\n * continuously drift/rotate it (animated gradient). Gated by reduced motion. */\n animatedGradient?: boolean;\n /** Motion grammar (Batch 3, hero/nav) — shared-element key: a matching\n * transitionId on the source + destination screen morphs that element across\n * navigation (web View Transitions / iOS matchedTransitionSource / Android\n * SharedTransitionLayout). Gated by reduced motion. */\n transitionId?: string;\n /** Motion grammar (Batch 4, rich visual) — render the backgroundGradient as a\n * mesh (overlapping multi-point blend) rather than a linear sweep; combine with\n * animatedGradient to drift it. */\n meshGradient?: boolean;\n /** Motion grammar (Batch 4, celebration) — burst a one-shot confetti overlay\n * within the node on appear. Gated by reduced motion. */\n confetti?: boolean;\n /** Motion grammar (Batch 2, scroll-driven) — pin this node to the top of its\n * scroll container (sticky section header). Functional positioning; not gated\n * by reduced motion. */\n sticky?: boolean;\n /** Motion grammar (Batch 2, scroll-driven) — turn this thin node into a\n * reading-progress bar that fills left→right with scroll (scaleX). web reads\n * window scroll; native reads the host scroll fraction. Functional. */\n scrollProgress?: boolean;\n /** Motion grammar (Batch 4, rich vector) — a named built-in vector animation\n * (\"loader\" | \"success\" | \"pulse\"), dependency-free. Gated by reduced motion.\n * Arbitrary Lottie/Rive JSON is the lib-backed superset follow-up. */\n vectorAnim?: string;\n /** Motion grammar (Batch 1, choreography) — delay this node's ENTER transition\n * by N ms so a screen can SEQUENCE its entrance beyond list stagger (\"title\n * fades, subtitle slides in 100ms later\"). Adds to any list `staggerMs`. Gated\n * by reduced motion. 0/absent = no extra delay. */\n enterDelayMs?: number;\n /** Motion grammar (Phase-3, per-node MotionSpec) — overrides the PRESS-feedback\n * curve with a `motions`-registry token, mirroring `Transition.motion` for the\n * enter curve. Absent → press uses the global `motion` personality. */\n pressMotion?: TokenRef;\n /** Motion grammar (Phase-3, gesture-driven) — finger-tracked drag-to-dismiss; past\n * threshold along `direction` it animates off and fires `action`, else springs back. */\n swipeDismiss?: SwipeDismiss;\n /** Motion grammar (Phase-4, reduced-motion) — per-node escape from the reduced-motion\n * gate. Default true (respect). false = \"must move anyway\" (transition/press animate\n * even when reduced motion is on). */\n respectReducedMotion?: boolean;\n /** #1052 — uniform scale (1 = no change). */\n scale?: number;\n /** #1052 — rotation in DEGREES. */\n rotation?: number;\n /** #1052 — stacking order within a ZStack/overlay. */\n zIndex?: number;\n /** #1052 — gaussian blur radius (pt/dp/px). */\n blur?: number;\n /** #1052 — `false` = decorative (ignores taps, unlike `hidden` which stays hit-testable);\n * `true` = force hit-testable (a clear tap target). */\n hitTestable?: boolean;\n /** #1052 — explicit drop shadow (colour + radius + offset), for upward / multi-layer\n * shadows the `shadow` token can't express. */\n boxShadow?: Shadow;\n}",
884
+ "Modifiers": "export interface Modifiers {\n padding?: EdgeInsets;\n background?: TokenRef;\n cornerRadius?: number;\n /** Fixed intrinsic width (pt/dp/px). Honored by iOS (`FrameIfPresent`), Android\n * (frame) and web (`modifiers.ts`). With no `ScrollView` in the vocabulary and\n * regions clipped at `maxHeight`, `width`/`height`/`fillMaxWidth` are the main\n * sizing tools for embed/host content. */\n width?: number;\n /** Fixed intrinsic height (pt/dp/px). See `width`. */\n height?: number;\n /** Fill the available cross-axis width (keeps any fixed `height`). Mirrors\n * Compose `fillMaxWidth` / SwiftUI `.frame(maxWidth: .infinity)`. See `width`. */\n fillMaxWidth?: boolean;\n /** CAP the node's laid-out width at this many points. It CONSTRAINS, it\n * does not SET: a node narrower than the cap keeps its intrinsic width, so a\n * short chat bubble stays short while a long one stops at the cap — the\n * asymmetry `width` cannot express and `weight` cannot either (weights divide\n * the *available* space, so every child lands on the same fraction).\n * The cap covers the node's TOTAL width, padding + border included (CSS\n * `max-width` under `box-sizing: border-box`). `fillMaxWidth` grabs the axis but\n * THE CAP WINS — fill + cap is exactly `maxWidth` wide; a literal `width` larger\n * than the cap is clamped to it too. POINTS, not a fraction: a modifier is\n * resolved with no portable notion of \"the row\" on any renderer — compose a\n * percentage from a weighted sibling gap instead. `0`/absent = no cap. */\n maxWidth?: number;\n /** Flex weight along the parent stack's MAIN axis (HStack = width, VStack =\n * height). The stack measures its intrinsic (unweighted) children first, then\n * divides what is LEFT among the weighted ones in proportion to their weights:\n * two children at `1` split the leftover equally, `2` and `1` split it 2:1.\n * Absent (or <= 0) keeps the node at its intrinsic size. Use this — not\n * `fillMaxWidth` — when a node should claim the remaining space *beside* its\n * siblings; a full-bleed fill grabs the whole axis and starves them. Ignored\n * outside an HStack/VStack.\n * CAVEAT — row-sharing controls have NO intrinsic width to measure first: in an\n * HStack a `Button` and a `Toggle` are implicitly weighted, so they DIVIDE the\n * row with your weighted child instead of taking a width ahead of it —\n * `[VStack(weight:1), Button, Button]` is a three-way split, not \"the VStack gets\n * the remainder\". To opt a Button out on EVERY renderer set `fullWidth: false`\n * (an explicit flag wins in both directions; absent falls back to the style\n * default, which is \"share\" for every style but `tertiary`/`link`), or use\n * `iconOnly`. Otherwise give the actions their own row.\n * See `himi docs designing-himi-screens` §4. */\n weight?: number;\n /** RTL — pin the subtree's writing direction: `\"ltr\"` | `\"rtl\"` | `\"locale\"`\n * (inherit). The common RTL pattern is the inverse of `Text.textDirection`: pin a\n * container `ltr` (a logotype, an attachment-tile row, a composer button row)\n * inside an otherwise-RTL screen. Absent = inherit the ambient direction. #1060. */\n layoutDirection?: \"ltr\" | \"rtl\" | \"locale\";\n /** Absolute visual translation (pt/dp/px). Applied as the OUTERMOST transform so\n * it shifts the fully-styled node WITHOUT changing its layout footprint — lets a\n * ZStack place polar/absolute children (e.g. AgentOrbit). Negative allowed. */\n offsetX?: number;\n offsetY?: number;\n onTap?: Action;\n /** Double-tap gesture (e.g. Instagram double-tap-to-like). Distinct from onTap. */\n onDoubleTap?: Action;\n accessibilityLabel?: string;\n hidden?: boolean;\n /** Data-bound conditional visibility: a `\"$service:<svc>.<field>\"`\n * or `\"$item.<field>\"` sentinel resolving to a bool at render; when true the\n * subtree fully COLLAPSES (zero layout, like `hidden`), overriding the\n * literal `hidden`. */\n hiddenPath?: string;\n /** Long-press gesture modifier. */\n onLongPress?: Action;\n /** Fires once when the component first appears on screen. */\n onAppear?: Action;\n /** Scoped token overrides for the subtree. */\n theme?: ThemeOverrides;\n /** Names a token from the app's `shadows` registry. */\n shadow?: TokenRef;\n /** Names a token from the app's `gradients` registry (background fill). */\n backgroundGradient?: TokenRef;\n /** Value-driven gradient: a `$service:`/`$item.` path resolving to a gradient\n * TOKEN NAME used as the live background (e.g. weather sky day→night). */\n backgroundGradientPath?: string;\n /** Value-driven background COLOR: a `$service:`/`$item.` path resolving to a\n * color TOKEN NAME used as the live background. Honored on iOS + Android\n * (`BoundBackgroundIfPresent`); web render support is tracked in #1057. */\n backgroundColorPath?: string;\n /** Value-driven background IMAGE: a `$service:`/`$item.` path resolving to an\n * image URL used as the live background. Honored on iOS + Android; web render\n * support is tracked in #1057. */\n backgroundImagePath?: string;\n /** #1051 — MEASURE (publish): report this node's laid-out size into the renderer's\n * self-contained (worker-free) measure store under the named keys, so a SIBLING can\n * feed the number back into its own layout via a `$measure:<key>` binding. The SDUI\n * equivalent of SwiftUI `.onGeometryChange` / Compose `onGloballyPositioned`; the\n * canonical use is a pinned bottom bar whose measured height becomes a scroll view's\n * bottom inset (sticky-footer + scroll-inset, unrepresentable before — #1051). */\n onMeasure?: Measure;\n /** #1051 — bound height: a `\"$measure:<key>\"` (measure store) or\n * `\"$service:<svc>.<field>\"` / `\"$item.<field>\"` (worker/row state) sentinel resolving\n * to a NUMBER at render. Wins over the literal `height`. String path only, so a\n * binding can never blank the node (never overload the numeric `height`). */\n heightPath?: string;\n /** #1051 — bound width. See `heightPath`. Wins over the literal `width`. */\n widthPath?: string;\n /** #1051 — bound TOP inset (the header-height → top-inset idiom). A `$measure:`/\n * `$service:`/`$item.` sentinel resolving to a number; added as top padding. */\n paddingTopPath?: string;\n /** #1051 — bound BOTTOM inset (the sticky-footer-height → bottom-inset idiom). A\n * `$measure:`/`$service:`/`$item.` sentinel resolving to a number; added as bottom\n * padding so the last scroll row clears a pinned bottom bar. */\n paddingBottomPath?: string;\n /** #1053 — INLINE gradient background paint. Distinct from `backgroundGradient`\n * (which names a `gradients` registry token an embed host can't register): this\n * carries its stops/colours in the descriptor, so it works anywhere. Painted as\n * a surface (behind children), below `material`/`backgroundGradient` if those are\n * also set. */\n gradient?: Gradient;\n /** #1053 — gradient ALPHA MASK: the node shows through where the gradient is\n * opaque and fades where it's transparent (a transparent-token stop = fully\n * clear). The material-under-gradient-scrim / scroll-fade-out idiom. Mirrors iOS\n * `.mask()`, Android `BlendMode.DstIn`, web `mask-image`. */\n mask?: Gradient;\n /** Inline border/stroke. */\n border?: Border;\n /** Subtree alpha 0.0–1.0; absent = opaque. */\n opacity?: number;\n /** #433 — constrain the subtree to a width:height ratio (e.g. `1` = square,\n * `1.777` = 16:9). Width-driven: the height derives from the resolved width.\n * Lets a data-bound Grid cell / photo tile hold its shape at any width. */\n aspectRatio?: number;\n /** Names a token from the app's `motions` registry; the subtree\n * fades + slides up on first appearance. */\n appear?: TokenRef;\n /** Surface material token (flat/glass/frosted). */\n material?: TokenRef;\n /** Per-node construction-language override. */\n variant?: string;\n /** Per-node density / motion override. */\n density?: string;\n motion?: string;\n /** Motion grammar (Batch 1) — press/tap feedback style\n * (auto|none|scale|highlight|scaleHighlight|lift); timing comes from `motion`.\n * Applied only when the node is interactive. Unknown degrades to `auto`. */\n pressFeedback?: string;\n /** Motion grammar (Batch 1) — press-coupled haptic\n * (none|selection|light|medium|heavy|success|warning|error); fires with onTap.\n * Default none (opt-in). Web is a no-op. */\n haptic?: string;\n /** Motion grammar (Batch 1) — animate this container's size changes instead of\n * snapping (iOS `.animation(value:)`, Android `animateContentSize`, web height\n * transition). Curve from the active `motion` personality. */\n animateContentSize?: boolean;\n /** Motion grammar (Batch 1) — enter/exit transition; supersedes `appear` when\n * both are present. */\n transition?: Transition;\n /** Motion grammar (Batch 2, scroll-driven) — defer the `transition` enter until\n * the node first crosses into the scroll viewport (reveal-on-scroll). Gated by\n * reduced motion (shown immediately when reduced). */\n revealOnScroll?: boolean;\n /** Motion grammar (Batch 2, scroll-driven) — parallax factor: the node drifts\n * vertically at this fraction of the scroll delta as it moves through the\n * viewport. Gated by reduced motion. Generalizes CollapsingHeader parallaxFactor. */\n parallax?: number;\n /** Motion grammar (Batch 4, rich visual) — when a backgroundGradient is present,\n * continuously drift/rotate it (animated gradient). Gated by reduced motion. */\n animatedGradient?: boolean;\n /** Motion grammar (Batch 3, hero/nav) — shared-element key: a matching\n * transitionId on the source + destination screen morphs that element across\n * navigation (web View Transitions / iOS matchedTransitionSource / Android\n * SharedTransitionLayout). Gated by reduced motion. */\n transitionId?: string;\n /** Motion grammar (Batch 4, rich visual) — render the backgroundGradient as a\n * mesh (overlapping multi-point blend) rather than a linear sweep; combine with\n * animatedGradient to drift it. */\n meshGradient?: boolean;\n /** Motion grammar (Batch 4, celebration) — burst a one-shot confetti overlay\n * within the node on appear. Gated by reduced motion. */\n confetti?: boolean;\n /** Motion grammar (Batch 2, scroll-driven) — pin this node to the top of its\n * scroll container (sticky section header). Functional positioning; not gated\n * by reduced motion. */\n sticky?: boolean;\n /** Motion grammar (Batch 2, scroll-driven) — turn this thin node into a\n * reading-progress bar that fills left→right with scroll (scaleX). web reads\n * window scroll; native reads the host scroll fraction. Functional. */\n scrollProgress?: boolean;\n /** Motion grammar (Batch 4, rich vector) — a named built-in vector animation\n * (\"loader\" | \"success\" | \"pulse\"), dependency-free. Gated by reduced motion.\n * Arbitrary Lottie/Rive JSON is the lib-backed superset follow-up. */\n vectorAnim?: string;\n /** Motion grammar (Batch 1, choreography) — delay this node's ENTER transition\n * by N ms so a screen can SEQUENCE its entrance beyond list stagger (\"title\n * fades, subtitle slides in 100ms later\"). Adds to any list `staggerMs`. Gated\n * by reduced motion. 0/absent = no extra delay. */\n enterDelayMs?: number;\n /** Motion grammar (Phase-3, per-node MotionSpec) — overrides the PRESS-feedback\n * curve with a `motions`-registry token, mirroring `Transition.motion` for the\n * enter curve. Absent → press uses the global `motion` personality. */\n pressMotion?: TokenRef;\n /** Motion grammar (Phase-3, gesture-driven) — finger-tracked drag-to-dismiss; past\n * threshold along `direction` it animates off and fires `action`, else springs back. */\n swipeDismiss?: SwipeDismiss;\n /** Motion grammar (Phase-4, reduced-motion) — per-node escape from the reduced-motion\n * gate. Default true (respect). false = \"must move anyway\" (transition/press animate\n * even when reduced motion is on). */\n respectReducedMotion?: boolean;\n /** #1052 — uniform scale (1 = no change). */\n scale?: number;\n /** #1052 — rotation in DEGREES. */\n rotation?: number;\n /** #1052 — stacking order within a ZStack/overlay. */\n zIndex?: number;\n /** #1052 — gaussian blur radius (pt/dp/px). */\n blur?: number;\n /** #1052 — `false` = decorative (ignores taps, unlike `hidden` which stays hit-testable);\n * `true` = force hit-testable (a clear tap target). */\n hitTestable?: boolean;\n /** #1052 — explicit drop shadow (colour + radius + offset), for upward / multi-layer\n * shadows the `shadow` token can't express. */\n boxShadow?: Shadow;\n}",
885
885
  "TokenRef": "export interface TokenRef {\n token: TokenName;\n}",
886
886
  "Action": "export interface Action {\n actionId: string;\n /** Backwards-compatible string-only params. Prefer `args` for typed payloads. */\n params?: Record<string, string>;\n /** Typed action args; nested objects/arrays preserved end-to-end. */\n args?: Record<string, Value>;\n /** Navigation hint for the router; ignored for non-nav actions. */\n presentation?: Presentation;\n}",
887
887
  "Presentation": "export type Presentation =\n | \"push\"\n | \"sheet\"\n | \"replace\"\n | \"dismiss\"\n | \"back\"\n | \"full_screen\"\n | \"alert\"\n | \"snackbar\";",
@@ -2008,10 +2008,10 @@ export interface Modifiers {
2008
2008
  * HStack a `Button` and a `Toggle` are implicitly weighted, so they DIVIDE the
2009
2009
  * row with your weighted child instead of taking a width ahead of it —
2010
2010
  * `[VStack(weight:1), Button, Button]` is a three-way split, not "the VStack gets
2011
- * the remainder". The RELIABLE opt-outs are `iconOnly` and `tertiary`/`link` with
2012
- * `fullWidth` unset. `fullWidth: false` is NOT reliable here: only Android honors
2013
- * it in a weighted row (`wantsRowStretch`); iOS (`WeightedStack`) and web still
2014
- * hand the button a share. Otherwise give the actions their own row.
2011
+ * the remainder". To opt a Button out on EVERY renderer set `fullWidth: false`
2012
+ * (an explicit flag wins in both directions; absent falls back to the style
2013
+ * default, which is "share" for every style but `tertiary`/`link`), or use
2014
+ * `iconOnly`. Otherwise give the actions their own row.
2015
2015
  * See `himi docs designing-himi-screens` §4. */
2016
2016
  weight?: number;
2017
2017
  /** RTL — pin the subtree's writing direction: `"ltr"` | `"rtl"` | `"locale"`
@@ -152,43 +152,56 @@ The fourth marker, **width evidence**, is graded by **content, not by phrase**
152
152
  fact about what you rendered rather than a string, so there is nothing to paste. Satisfy it either
153
153
  way:
154
154
 
155
- - Name two pixel widths spanning compact → wider, within about 400 characters of each other so
156
- they read as one statement: one **below 600px** (compact) and one **at 600px or wider** (medium
157
- or expanded). `Rendered 320, 390 and 834 (home-320.png, home-390.png, home-834.png)` satisfies it
158
- — a single width does not. What this proves is that you rendered more than one size class, not
159
- which wider one: 600px is the `medium` breakpoint and `expanded` begins at 840px, so 834 counts
160
- as wider without being expanded. The two bands are contiguous, so every width you actually render
161
- counts as one or the other: `pixel` (412) and `iphone-max` (430) from `himi render --device` are
162
- compact.
163
- Each number must read as a **width**: give it a unit (`834px`), put it in a screenshot filename
164
- (`home-834.png`, `home-834-dark.png`, `home-834x1112.png`), or name it with the vocabulary of
165
- widths — `width`, `wide`, `pixel`, `viewport`, `breakpoint`, `compact`, `expanded`, `column`,
166
- `portrait`, `landscape`. One such word covers a whole list of widths from either end (`Rendered
167
- 320, 390 and 834 widths`, `Widths: 320, 390, 834` and `Rendered compact (320) and expanded (834)`
168
- all work), and the list may wrap across lines — but it does not reach across another phrase or
169
- into the next line unless that line continues the list: in `Rendered home at 834px; p95
170
- 250 ms` only `834` is a width, so that line names a single width and is still reported. In a
171
- `390x844` pair the first number is the width and the second is the height, so one portrait
172
- screenshot is one width — `home-390x844.png` on its own is not two. Bare
173
- digits count for nothing, which keeps a latency budget, a price or an issue number from standing
174
- in for the half you did not render. A number in the 1990-2099 range needs its **unit** to count
175
- as a width: `2048px` is taken at its word, while a bare `2026` and a year-shaped filename
176
- (`shot-2026.png`, `evidence-2026-09-18.png`) are not widths — `home-2048.png` cannot be told
177
- apart from `shot-2026.png`, so spell the unit when your render really is that wide.
178
- `render` is deliberately **not** one of the cues: an acceptance row says "renders in under
179
- 250 ms" far more often than it names a viewport. Neither is a pixel measurement of something
180
- that is not a viewport — `Row height 320px`, `Font size: 512px` and `844 tall` are ignored,
181
- because a unit says pixels, not width. Assets are judged by the SENTENCE they sit in rather than
182
- by word order: a sentence naming an image, logo, banner, hero, icon, media or artwork is a record
183
- of assets, so none of its numbers count — `Use 320px-wide mobile and 1024px-wide desktop hero
184
- images` records no render. A screenshot **filename** is the exception and outranks the sentence
185
- it appears in, because a file that was written is the strongest evidence this marker takes:
186
- `Screenshots: hero-320.png and hero-834.png` counts.
155
+ - Name two pixel widths spanning compact → wider, **each spelled as a width on itself**, within
156
+ about 400 characters of each other so they read as one statement: one **below 600px** (compact)
157
+ and one **at 600px or wider** (medium or expanded). A number is spelled as a width when it
158
+ carries a **unit** — `320px`, `320 px`, `320pt`, `320dp`, `320 pixels`, `320 wide` — or sits in a
159
+ **screenshot filename** — `home-320.png`, `home-834-dark.png`, `home-390x844.png`. That is the
160
+ whole rule; nothing is inferred from the words around the number. So
161
+ `Rendered 320px and 834px`, `Captured 834px down to 320px`, `Widths: 320px, 390px, 834px` and
162
+ `Rendered 320, 390 and 834 (home-320.png, home-390.png, home-834.png)` all satisfy it, and a
163
+ single width does not.
164
+
165
+ What this proves is that you rendered more than one size class, not which wider one: 600px is the
166
+ `medium` breakpoint and `expanded` begins at 840px, so 834 counts as wider without being expanded.
167
+ The two bands are contiguous, so every width you actually render counts as one or the other:
168
+ `pixel` (412) and `iphone-max` (430) from `himi render --device` are compact.
169
+
170
+ **A bare number never counts, however the sentence labels it.** `Widths: 320, 390, 834`,
171
+ `Rendered 320, 390 and 834 widths` and `Rendered compact (320) and expanded (834)` all name no
172
+ unit on any number, so none of them is width evidence — add one unit per width and they are. In
173
+ particular the word **`width` is a label, not a unit**: it says what you decided the number means,
174
+ so `320 widths` does nothing that `320` does not. `pixels` and `wide` *are* units and do count —
175
+ `320 pixels`, `320 wide`, `320 pixel widths`. This
176
+ is deliberate: reading the prose around a number is exactly what used to reject honest documents
177
+ (a comma and the word `illustration` were enough), so the gate no longer does it. The cost of
178
+ bare digits not counting is one character each; the cost of guessing from prose was a rejected
179
+ render you could not diagnose.
180
+
181
+ Three things still disqualify a number that *is* spelled as a width, and all three are about the
182
+ **axis** rather than about the subject: a **height or type/spacing size** (`Row height 320px`,
183
+ `Font size: 512px`, `844 tall`, `844 high`), the **second half of a `NxM` pair** (in
184
+ `home-390x844.png` the width is 390 and 844 is the height, so one portrait screenshot is one
185
+ width, not two), and a number **outside 200–4000px** (`64px` chips and a `9000px` export canvas
186
+ are not viewports). A number in the **1990–2099 range** needs its unit rather than a filename:
187
+ `2048px` and `2048 pixels` are taken at their word, while `home-2048.png` cannot be told apart
188
+ from `shot-2026.png`, so spell the unit when your render really is that wide.
189
+
190
+ **Accepted, deliberately:** an asset dimension written with a unit or in a filename now counts —
191
+ `Hero image is 320px wide; the banner is 1024px wide` and `Icon sizes: icon-320.png,
192
+ icon-834.png` pass. The marker is permissive there on purpose. A gate that rejects real work
193
+ costs build loops; a permissive one costs a line of prose, and a card or drawer width always
194
+ passed anyway.
187
195
  - Or keep the scaffold's own sentence, `compact (320/360)` followed later by `expanded`, which is
188
196
  still accepted as-is. The two **may wrap across lines** — the check is not line-based — but
189
197
  `expanded` must follow within about 400 characters, so keep them in the same sentence or bullet
190
198
  rather than in separate sections.
191
199
 
200
+ When the marker fails, the error names **which** of the four things went wrong — no compact width,
201
+ no wider width, two widths too far apart to be one statement, or no number spelled as a width — and
202
+ says so alongside the height-axis rule if that is what discarded your numbers. Read the branch it
203
+ gave you rather than pasting the example: the example is a form that works, not the only one.
204
+
192
205
  A `MOBILE-UX.md` that is **absent or empty** is not four missing markers. `himi ux audit` reports it
193
206
  once, as a missing contract; `himi validate` stays silent about it entirely. Asking for the document
194
207
  itself is the audit's job — most apps never adopt this contract, and a warning on every one of them
@@ -1509,6 +1509,12 @@
1509
1509
  "source": "core/sdk/local-db/src/index.ts",
1510
1510
  "description": "Tier-5 typed binding to the platform's local-database capability: a per-entity on-device store (EntityStore) + an offline WriteQueue, so a worker bundle can persist and query real data on device.",
1511
1511
  "api": [
1512
+ {
1513
+ "name": "availability",
1514
+ "kind": "function",
1515
+ "jsdoc": "/**\n * Whether this host actually backs the local database — the probe #2502 asked for.\n *\n * Without it, every read on a host that does not serve the capability is\n * INDISTINGUISHABLE from a successful read of an empty collection: `getAll` coalesces a\n * missing answer to `[]` and `get` to `null` just below, so an author sees \"0 rows\" and\n * cannot tell whether the store is empty or absent. That ambiguity cost three QA slots on\n * one run; one of them shipped a whole in-memory fallback that turned out to be dead code.\n *\n * `full` on iOS / Android / web and in the headless `himi render` host; `degraded` where the\n * store works but does not survive a reload (web without IndexedDB); `unavailable` when the\n * host does not back it. Branch on this rather than inferring absence from an empty result.\n *\n * NEVER THROWS, like `@himalaya/pdf`'s `isAvailable`, and that is load-bearing rather than\n * politeness: the hosts this probe exists to detect are exactly the ones that REJECT it.\n * Both native dispatchers throw on an unregistered id (`ActionError.unknown`), so on any app\n * binary shipped before this verb existed — or a shell that never calls\n * `LocalDatabaseCapability.register` — a rejecting probe would put an unhandled rejection in\n * the worker's `load()` of every author who wrote the documented\n * `if (await availability() === \"full\")`. A refusal to answer IS the answer here.\n */",
1516
+ "decl": "export async function availability(): Promise<Availability>;"
1517
+ },
1512
1518
  {
1513
1519
  "name": "DbEntity",
1514
1520
  "kind": "interface",
@@ -2613,6 +2619,12 @@
2613
2619
  "source": "core/sdk/secure-storage/src/index.ts",
2614
2620
  "description": "Tier-5 typed binding to the platform's secure-storage capability.",
2615
2621
  "api": [
2622
+ {
2623
+ "name": "availability",
2624
+ "kind": "function",
2625
+ "jsdoc": "/**\n * Whether this host actually backs secure storage — the probe #2502 asked for.\n *\n * Without it, `read` on a host that does not serve the capability is INDISTINGUISHABLE\n * from a key that was never written: both answer `null` (see `read` below, which coalesces\n * a missing answer). An author cannot tell \"unsupported here\" from \"genuinely empty\", which\n * is exactly what made a silent no-op look like a working-but-empty keychain.\n *\n * `full` on iOS (Keychain) / Android (Keystore-AES) / web and in the headless\n * `himi render` host; `unavailable` when the host does not back it — which on web includes\n * a context with no IndexedDB (a sandboxed iframe, a storage-blocked third-party frame),\n * where every `read`/`write` would reject.\n *\n * NEVER THROWS, like `@himalaya/pdf`'s `isAvailable`, and that is load-bearing rather than\n * politeness: the hosts this probe exists to detect are exactly the ones that REJECT it.\n * Both native dispatchers throw on an unregistered id (`ActionError.unknown`), so on any app\n * binary shipped before this verb existed — or a shell that never calls\n * `SecureStorageCapability.register` — a rejecting probe would put an unhandled rejection in\n * the worker's `load()` of every author who wrote the documented\n * `if (await availability() === \"full\")`. A refusal to answer IS the answer here.\n */",
2626
+ "decl": "export async function availability(): Promise<Availability>;"
2627
+ },
2616
2628
  {
2617
2629
  "name": "AccessControl",
2618
2630
  "kind": "type",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wix/himalaya-cli",
3
- "version": "0.850.0",
3
+ "version": "0.852.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": [
@@ -63,5 +63,5 @@
63
63
  "artifactId": "himalaya-cli"
64
64
  }
65
65
  },
66
- "falconPackageHash": "17dae723ff059af1c4e2d1d27c07a030f3551c1ab3228d66cc5b6795"
66
+ "falconPackageHash": "b9fc53632fccc6f967871e261d9617c8366fcd76449b1a66da8a470c"
67
67
  }
@@ -35,7 +35,7 @@ export default defineApp({
35
35
  designTokensPath: "./tokens.json",
36
36
  navigation: {
37
37
  "navKind": "responsive",
38
- "homeScreenId": "surf_home",
38
+ "scheme": "himalaya-__HIMI_APP_SLUG__",
39
39
  "tabBar": {
40
40
  "items": [
41
41
  {