@wix/himalaya-cli 0.981.0 → 0.982.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.
@@ -95,3 +95,34 @@ export declare function listDelivered(): Promise<DeliveredNotification[]>;
95
95
  export declare function clearDelivered(ids?: string[]): Promise<void>;
96
96
  /** Capability version, for bundles that gate on min binary version. */
97
97
  export declare const version = 1;
98
+ /** One notification, as a worker sees it. Every field is always present; an
99
+ * absent one reads as `""` rather than `undefined`, so a `$service:` binding at
100
+ * a field this payload omitted renders nothing instead of the text "undefined". */
101
+ export interface NotificationEvent {
102
+ /** Ping template that produced it, e.g. "stores.order_shipped". */
103
+ templateId: string;
104
+ /** Ping topic, e.g. "stores". */
105
+ topic: string;
106
+ /** The site the payload named, when it named one. */
107
+ metaSiteId: string;
108
+ /** Stable per-notification id — the key to deduplicate on. */
109
+ notificationId: string;
110
+ /** The link exactly as delivered, before any route translation. */
111
+ link: string;
112
+ /** What the route table translated `link` into, or `""` when no route
113
+ * matched. Empty is NOT an error: the notification still arrived, and a
114
+ * worker may want to count one it cannot open. */
115
+ resolvedLink: string;
116
+ /** True only when the user TAPPED. A foreground receipt is `false` and must
117
+ * not be treated as intent to navigate. */
118
+ tapped: boolean;
119
+ }
120
+ /**
121
+ * Read the params a dispatched `observerAction` receives.
122
+ *
123
+ * Runs on the untrusted edge of the bridge, so it never throws whatever arrives.
124
+ * `tapped` needs this reader more than the rest: the bridge stringifies every
125
+ * param, so a worker testing `params.tapped` truthily would read the string
126
+ * `"false"` as a tap.
127
+ */
128
+ export declare function readNotificationEvent(params: unknown): NotificationEvent;
@@ -80,6 +80,38 @@ export interface AppCommerceConfig {
80
80
  /** Copy-pack overrides for the paywall. */
81
81
  copy?: Record<string, string>;
82
82
  }
83
+ /**
84
+ * RFC 0059 §3.2 — the idiom policy: which named overlay from `tokens.json`'s `idiom` block a
85
+ * surface wears. `policy` is a tag, and each value owns its own sibling fields; the union
86
+ * makes the type checker reject the combinations the RFC rules out, not only the gate
87
+ * (`core/dev-server/tests/idiom-overlay-parity.test.ts`).
88
+ *
89
+ * - `follow-surface` (the default INSIDE a declared block): each surface wears the overlay
90
+ * for its own idiom — `ios` on the iOS renderer, `material` on Android, `desktop` on a
91
+ * desktop-class window. `surfaces` pins individual surfaces, keyed by catalog surface id
92
+ * like `NavigationConfig.surfaces`; `fallback` names the overlay for a surface with no
93
+ * idiom of its own. An overlay the app never authored resolves to base.
94
+ * - `pinned`: every surface wears the one overlay `pin` names. `surfaces` is not allowed —
95
+ * pinning one overlay and then overriding it per surface is `follow-surface`.
96
+ * - `none`: a declared opt-out. Same pixels as omitting the block, but on the record.
97
+ */
98
+ export type IdiomPolicy = {
99
+ policy?: "follow-surface";
100
+ /** Catalog surface id (`phone` | `tablet` | `desktop` | `mac` | …) → overlay name. */
101
+ surfaces?: Record<string, string>;
102
+ fallback?: string;
103
+ pin?: never;
104
+ } | {
105
+ policy: "pinned";
106
+ pin: string;
107
+ surfaces?: never;
108
+ fallback?: never;
109
+ } | {
110
+ policy: "none";
111
+ surfaces?: never;
112
+ fallback?: never;
113
+ pin?: never;
114
+ };
83
115
  export interface AppConfig {
84
116
  /** Reverse-DNS app id, e.g. "com.wix.owner". */
85
117
  id: string;
@@ -338,6 +370,14 @@ export interface AppConfig {
338
370
  workerSubtreeScreens?: string[];
339
371
  /** RFC 0010 — navigation structure + deep-link mapping, served OTA. */
340
372
  navigation?: NavigationConfig;
373
+ /**
374
+ * RFC 0059 — how each surface picks its overlay from `tokens.json`'s `idiom` block.
375
+ *
376
+ * Two defaults, deliberately different: OMIT this block and the policy is `none` (base
377
+ * tokens, byte-identical to an app that never heard of idiom); DECLARE it without a
378
+ * `policy` and you get `follow-surface`. See `IdiomPolicy`.
379
+ */
380
+ idiom?: IdiomPolicy;
341
381
  /**
342
382
  * App-declared Wix REST cache rules. The app controls per-endpoint opt-out
343
383
  * (`cacheable: false`) and TTL; the server renders the EFFECTIVE policy by
@@ -0,0 +1,59 @@
1
+ import type { IdiomPolicy } from "./define-app.js";
2
+ /** What a runtime tells the resolver about where it is drawing. */
3
+ export interface IdiomContext {
4
+ /**
5
+ * The overlays this runtime natively wears, most specific first: `["ios"]` on iOS,
6
+ * `["material"]` on Android, `["desktop", "ios"]` on macOS (RFC 0059 §7.1: a Mac wears
7
+ * `desktop`, falling back to `ios` when the app authored no `desktop` overlay). Empty ⇒ no
8
+ * native idiom (the web, today), so only `surfaces`, `fallback` or a pin can select one.
9
+ */
10
+ native?: readonly string[];
11
+ /** Catalog surface id this frame renders on (`phone`, `tablet`, `mac`, …); keys `surfaces`. */
12
+ surface?: string;
13
+ }
14
+ /** The token-document fields resolution reads. `idiomPolicy` is stamped from `config.ts`. */
15
+ export interface IdiomDocument {
16
+ idiom?: Record<string, unknown>;
17
+ idiomPolicy?: IdiomPolicy;
18
+ }
19
+ /** The runtime contexts, in one place so the web host, RN and the tests agree on the names. */
20
+ export declare const IDIOM_CONTEXTS: {
21
+ readonly ios: {
22
+ readonly native: readonly ["ios"];
23
+ };
24
+ readonly android: {
25
+ readonly native: readonly ["material"];
26
+ };
27
+ readonly mac: {
28
+ readonly native: readonly ["desktop", "ios"];
29
+ readonly surface: "mac";
30
+ };
31
+ };
32
+ /**
33
+ * The overlay a surface wears, or `undefined` for the base look. Never throws: an absent or
34
+ * malformed policy, or a name the app never authored, resolves to base (RFC 0059 §3.3).
35
+ *
36
+ * - no policy, or `none` → base
37
+ * - `pinned` → `pin`
38
+ * - `follow-surface` (the default inside a declared block), first AUTHORED of:
39
+ * `surfaces[ctx.surface]`, then each of `ctx.native`, then `fallback`.
40
+ */
41
+ export declare function selectIdiomOverlay(doc: IdiomDocument, ctx?: IdiomContext): string | undefined;
42
+ /**
43
+ * Deep-merge one overlay over a base token document: objects merge key by key, and arrays and
44
+ * scalars replace. One rule is colour-specific, because a colour token has two shapes: a
45
+ * per-appearance overlay (`{ "dark": "#…" }`) over a single-hex base keeps the base as its
46
+ * `light` arm, so restyling one appearance never erases the others.
47
+ */
48
+ export declare function mergeIdiomOverlay<T extends Record<string, unknown>>(base: T, overlay: Record<string, unknown>): T;
49
+ /**
50
+ * The token document a surface actually wears. Returns the SAME object when no overlay is
51
+ * selected, so an app that declares no idiom resolves byte-identically to before (the #2924
52
+ * opt-in guarantee).
53
+ *
54
+ * Resolution is FINAL: the merged result drops `idiomPolicy`, so resolving it again is a no-op
55
+ * under any context. That matters because resolved tokens get re-resolved — a scoped-theme
56
+ * subtree re-derives its resolvers from them, a native runtime may re-register `current` — and
57
+ * a second pass under a different context must not stack another overlay on the first.
58
+ */
59
+ export declare function resolveIdiom<T extends object>(tokens: T, ctx?: IdiomContext): T;
@@ -9,6 +9,7 @@ export * from "./validation.js";
9
9
  export * from "./widget-vocabulary.js";
10
10
  export * from "./preview-surfaces.js";
11
11
  export * from "./tokens.js";
12
+ export * from "./idiom.js";
12
13
  export * from "./tier-caps.js";
13
14
  export * from "./flags.js";
14
15
  export * from "./flags-engine.js";
@@ -1,3 +1,4 @@
1
+ import type { IdiomPolicy } from "./define-app.js";
1
2
  export type ColorScheme = "light" | "dark" | "highContrast";
2
3
  export type ColorValue = string | {
3
4
  light: string;
@@ -38,7 +39,28 @@ export interface DesignTokens {
38
39
  * a comfortable column and a tablet/desktop fills more without running edge to
39
40
  * edge. Absent ⇒ platform default (web 480/760, native fill). */
40
41
  contentMaxWidth?: ResponsiveSize;
42
+ /** RFC 0059 — named idiom overlays, keyed by IDIOM (`ios`, `material`, `desktop`), not by
43
+ * platform. Which one a surface wears is `config.ts`'s `idiom` policy; with no policy the
44
+ * block is inert and base tokens resolve exactly as before. */
45
+ idiom?: Record<string, IdiomOverlay>;
46
+ /** RFC 0059 — the app's `config.ts` `idiom` policy, STAMPED into the served document by the
47
+ * dev-server (and so into every release snapshot) so each runtime can select an overlay
48
+ * in `register` with no second fetch. Derived, never authored in `tokens.json`. */
49
+ idiomPolicy?: IdiomPolicy;
41
50
  }
51
+ /** Every field optional, at every depth: an idiom overlay restyles only what it names. */
52
+ export type DeepPartial<T> = T extends readonly (infer U)[] ? readonly DeepPartial<U>[] : T extends object ? {
53
+ [K in keyof T]?: DeepPartial<T[K]>;
54
+ } : T;
55
+ /**
56
+ * RFC 0059 §3.1 — one named idiom overlay: a partial token set deep-merged over the base,
57
+ * before appearance (`light`/`dark`/`highContrast`) resolves. It may only restyle tokens the
58
+ * base already declares — `core/dev-server/tests/idiom-overlay-parity.test.ts` enforces that.
59
+ *
60
+ * Not overlayable: `contentMaxWidth` (a Projection concern that RFC 0041 size classes own),
61
+ * `fontFaces` (the served font declaration, derived rather than authored) and `idiom` itself.
62
+ */
63
+ export type IdiomOverlay = DeepPartial<Omit<DesignTokens, "idiom" | "idiomPolicy" | "contentMaxWidth" | "fontFaces">>;
42
64
  /**
43
65
  * One face in a font declaration.
44
66
  *
@@ -9434,6 +9434,69 @@ function isComponentAllowed(surface, componentType) {
9434
9434
  return row[gate.family] ? { allowed: true } : { allowed: false, reason: `not in the ${gate.family} allowlist` };
9435
9435
  }
9436
9436
 
9437
+ // ../../core/ts/src/idiom.ts
9438
+ var IDIOM_CONTEXTS = {
9439
+ ios: { native: ["ios"] },
9440
+ android: { native: ["material"] },
9441
+ mac: { native: ["desktop", "ios"], surface: "mac" }
9442
+ };
9443
+ function authored(overlays, name) {
9444
+ return typeof name === "string" && overlays !== void 0 && Object.hasOwn(overlays, name) && overlays[name] !== null && typeof overlays[name] === "object" && !Array.isArray(overlays[name]);
9445
+ }
9446
+ function selectIdiomOverlay(doc, ctx = {}) {
9447
+ const policy = doc.idiomPolicy;
9448
+ const overlays = doc.idiom;
9449
+ const isRecord3 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
9450
+ if (!isRecord3(policy) || !isRecord3(overlays)) return void 0;
9451
+ const kind = policy.policy ?? "follow-surface";
9452
+ if (kind === "none") return void 0;
9453
+ if (kind === "pinned") return authored(overlays, policy.pin) ? policy.pin : void 0;
9454
+ if (kind !== "follow-surface") return void 0;
9455
+ const surfaces = policy.surfaces;
9456
+ const candidates = [
9457
+ ctx.surface !== void 0 && isRecord3(surfaces) && Object.hasOwn(surfaces, ctx.surface) ? surfaces[ctx.surface] : void 0,
9458
+ ...ctx.native ?? [],
9459
+ policy.fallback
9460
+ ];
9461
+ return candidates.find((c) => authored(overlays, c));
9462
+ }
9463
+ var UNSAFE_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
9464
+ function isPlainObject(v) {
9465
+ return v !== null && typeof v === "object" && !Array.isArray(v);
9466
+ }
9467
+ function deepMerge(base, overlay) {
9468
+ if (!isPlainObject(overlay) || !isPlainObject(base)) return overlay;
9469
+ const out = { ...base };
9470
+ for (const [key, value] of Object.entries(overlay)) {
9471
+ if (UNSAFE_KEYS.has(key)) continue;
9472
+ out[key] = Object.hasOwn(base, key) ? deepMerge(base[key], value) : value;
9473
+ }
9474
+ return out;
9475
+ }
9476
+ function mergeIdiomOverlay(base, overlay) {
9477
+ const merged = deepMerge(base, overlay);
9478
+ const baseColors = base.colors;
9479
+ const overlayColors = overlay.colors;
9480
+ if (isPlainObject(baseColors) && isPlainObject(overlayColors)) {
9481
+ const colors = { ...merged.colors };
9482
+ for (const [name, value] of Object.entries(overlayColors)) {
9483
+ if (UNSAFE_KEYS.has(name)) continue;
9484
+ const was = Object.hasOwn(baseColors, name) ? baseColors[name] : void 0;
9485
+ if (typeof was === "string" && isPlainObject(value)) colors[name] = { light: was, ...value };
9486
+ }
9487
+ merged.colors = colors;
9488
+ }
9489
+ return merged;
9490
+ }
9491
+ function resolveIdiom(tokens, ctx = {}) {
9492
+ const doc = tokens;
9493
+ const name = selectIdiomOverlay(doc, ctx);
9494
+ if (name === void 0) return tokens;
9495
+ const merged = mergeIdiomOverlay(tokens, doc.idiom[name]);
9496
+ const { idiomPolicy: _applied, ...final } = merged;
9497
+ return final;
9498
+ }
9499
+
9437
9500
  // ../../core/ts/src/tier-caps.ts
9438
9501
  function summarize(descriptor) {
9439
9502
  const rendererTier = descriptor.renderer === "sdui_intent" ? "tier1" : descriptor.renderer === "sdui_layout" ? "tier2" : descriptor.renderer === "webview_native_bridge" ? "tier3" : "tier4";
@@ -12825,6 +12888,7 @@ export {
12825
12888
  ICON_NORMALIZED_INDEX,
12826
12889
  ICON_STROKE_WIDTH,
12827
12890
  ICON_VIEW_BOX,
12891
+ IDIOM_CONTEXTS,
12828
12892
  LAUNCH_MANIFEST_SCHEMA,
12829
12893
  LAUNCH_NAMESPACE,
12830
12894
  LINK_MOUNT_PATHS,
@@ -12889,12 +12953,14 @@ export {
12889
12953
  isViewTarget,
12890
12954
  isWidgetFamilySurface,
12891
12955
  layoutDirection,
12956
+ mergeIdiomOverlay,
12892
12957
  parseNumeric,
12893
12958
  primarySubtag,
12894
12959
  resolveAppIdentity,
12895
12960
  resolveAppLocale,
12896
12961
  resolveColor,
12897
12962
  resolveIcon,
12963
+ resolveIdiom,
12898
12964
  resolveKeyboard,
12899
12965
  resolveMode,
12900
12966
  resolveOrientationEnvelope,
@@ -12904,6 +12970,7 @@ export {
12904
12970
  resolveSurfaceNav,
12905
12971
  resolveSurfaceViewKey,
12906
12972
  screenDescriptorSchema,
12973
+ selectIdiomOverlay,
12907
12974
  shouldRevealError,
12908
12975
  stripOriginParams,
12909
12976
  summarize,
@@ -70,6 +70,26 @@ function normalizePermission(s) {
70
70
  return s === "granted" || s === "denied" ? s : "undetermined";
71
71
  }
72
72
  var version = 1;
73
+ function readNotificationEvent(params) {
74
+ const p = params && typeof params === "object" ? params : {};
75
+ const str = (key) => {
76
+ const v = p[key];
77
+ if (v === void 0 || v === null) return "";
78
+ return typeof v === "string" ? v : String(v);
79
+ };
80
+ const raw = str("tapped").toLowerCase();
81
+ return {
82
+ templateId: str("templateId"),
83
+ topic: str("topic"),
84
+ metaSiteId: str("metaSiteId"),
85
+ notificationId: str("notificationId"),
86
+ link: str("link"),
87
+ resolvedLink: str("resolvedLink"),
88
+ // Absent or unrecognized ⇒ a RECEIPT. Guessing "tap" would navigate a user
89
+ // who never asked to go anywhere.
90
+ tapped: raw === "true" || raw === "1"
91
+ };
92
+ }
73
93
  export {
74
94
  cancelAllLocal,
75
95
  cancelLocal,
@@ -79,6 +99,7 @@ export {
79
99
  getDeviceToken,
80
100
  getPermissionStatus,
81
101
  listDelivered,
102
+ readNotificationEvent,
82
103
  requestPermission,
83
104
  scheduleLocal,
84
105
  setBadge,