@wix/himalaya-cli 0.1018.0 → 0.1020.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/ts/src/tokens.d.ts +36 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/core.mjs +7 -0
- package/dist/cli.mjs +400 -29
- package/dist/preview-assets.generated.bin +0 -0
- package/guides/himi-test.md +16 -0
- package/guides/tokens.md +10 -0
- package/package.json +2 -2
package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/ts/src/tokens.d.ts
CHANGED
|
@@ -18,6 +18,8 @@ export interface DesignTokens {
|
|
|
18
18
|
motions?: Record<string, MotionToken>;
|
|
19
19
|
/** RFC 0021 — named surface materials (flat / glass / frosted). */
|
|
20
20
|
materials?: Record<string, MaterialToken>;
|
|
21
|
+
/** RFC 0059 / #2931 — named surface elevations, paint only. A `Card` wears `card`. */
|
|
22
|
+
elevations?: Record<string, ElevationToken>;
|
|
21
23
|
/** RFC 0021 (bottom-nav extension) — kit-driven tab bar styling. */
|
|
22
24
|
tabBar?: TabBarStyle;
|
|
23
25
|
/** RFC 0010/0021 — kit-driven slide-in drawer styling. */
|
|
@@ -264,6 +266,40 @@ export interface ShadowToken {
|
|
|
264
266
|
x?: number;
|
|
265
267
|
y?: number;
|
|
266
268
|
}
|
|
269
|
+
/**
|
|
270
|
+
* RFC 0059 / #2931 — how raised a surface is, as PAINT: nothing here moves a box, so the
|
|
271
|
+
* structural diff stays blind to it. A `Card` wears the `card` elevation; declare none and every
|
|
272
|
+
* Card stays flat, as before. Each field is drawn the same way on every renderer — what makes a
|
|
273
|
+
* Card read as Material on Android and as iOS on an iPhone is the idiom overlay choosing different
|
|
274
|
+
* fields for the one token:
|
|
275
|
+
*
|
|
276
|
+
* "elevations": { "card": {} },
|
|
277
|
+
* "idiom": {
|
|
278
|
+
* "material": { "elevations": { "card": { "level": 1 } } },
|
|
279
|
+
* "ios": { "elevations": { "card": { "border": { "color": "border" }, "shadow": "card" } } }
|
|
280
|
+
* }
|
|
281
|
+
*/
|
|
282
|
+
export interface ElevationToken {
|
|
283
|
+
/** Material 3 tonal elevation, 0–5: the surface is tinted with `tint` at M3's alpha for that
|
|
284
|
+
* level (1 → 5%, 2 → 8%, 3 → 11%, 4 → 12%, 5 → 14% — `TONAL_ELEVATION_ALPHA`). 0 = none. */
|
|
285
|
+
level?: number;
|
|
286
|
+
/** The tonal tint — a colour token, M3's surfaceTint. Default `accentPrimary`. */
|
|
287
|
+
tint?: string;
|
|
288
|
+
/** A `shadows` token drawn behind the surface, in the surface's own shape. */
|
|
289
|
+
shadow?: string;
|
|
290
|
+
/** A hairline drawn just inside the surface's edge, so it never grows the box. `width` in
|
|
291
|
+
* points, default 0.5; 0 = none. */
|
|
292
|
+
border?: {
|
|
293
|
+
color: string;
|
|
294
|
+
width?: number;
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
/** M3's tonal-elevation tint alpha by level (0–5), as `surfaceColorAtElevation` computes it for
|
|
298
|
+
* levels 1–5 (1, 3, 6, 8, 12 dp). Mirrored by Tokens.swift and Tokens.kt. */
|
|
299
|
+
export declare const TONAL_ELEVATION_ALPHA: readonly number[];
|
|
300
|
+
/** The tint alpha an `ElevationToken.level` asks for: rounded and clamped to 0–5; anything
|
|
301
|
+
* non-numeric is 0 (no tint). */
|
|
302
|
+
export declare function tonalElevationAlpha(level: number | undefined): number;
|
|
267
303
|
/** RFC 0020 — a linear gradient. `colors` are color-token NAMES (theme-aware);
|
|
268
304
|
* `angle` is degrees clockwise from top (0 = top→bottom, 90 = left→right).
|
|
269
305
|
* `locations` (0..1) optionally positions each stop. */
|
|
@@ -7796,6 +7796,11 @@ function resolveResponsiveSize(rs, width) {
|
|
|
7796
7796
|
return rs.base;
|
|
7797
7797
|
}
|
|
7798
7798
|
var SYSTEM_FONT_FAMILY = "$system";
|
|
7799
|
+
var TONAL_ELEVATION_ALPHA = [0, 0.05, 0.08, 0.11, 0.12, 0.14];
|
|
7800
|
+
function tonalElevationAlpha(level) {
|
|
7801
|
+
if (typeof level !== "number" || !Number.isFinite(level)) return 0;
|
|
7802
|
+
return TONAL_ELEVATION_ALPHA[Math.min(5, Math.max(0, Math.round(level)))];
|
|
7803
|
+
}
|
|
7799
7804
|
function buildTokenRegistry(tokens) {
|
|
7800
7805
|
return {
|
|
7801
7806
|
colors: new Set(Object.keys(tokens.colors ?? {})),
|
|
@@ -12975,6 +12980,7 @@ export {
|
|
|
12975
12980
|
SURFACE_GROUPS,
|
|
12976
12981
|
SURFACE_VIEW_LADDER,
|
|
12977
12982
|
SYSTEM_FONT_FAMILY,
|
|
12983
|
+
TONAL_ELEVATION_ALPHA,
|
|
12978
12984
|
UNIVERSAL_LAUNCH_NAMESPACES,
|
|
12979
12985
|
WIDGET_COMPONENTS,
|
|
12980
12986
|
WIDGET_FAMILIES,
|
|
@@ -13047,6 +13053,7 @@ export {
|
|
|
13047
13053
|
surfaceFrozenFamily,
|
|
13048
13054
|
surfaceRenderMode,
|
|
13049
13055
|
surfacesForApp,
|
|
13056
|
+
tonalElevationAlpha,
|
|
13050
13057
|
validate,
|
|
13051
13058
|
validateAll,
|
|
13052
13059
|
validateDistribution,
|
package/dist/cli.mjs
CHANGED
|
@@ -5293,6 +5293,10 @@ var init_quickjs_web_globals = __esm({
|
|
|
5293
5293
|
});
|
|
5294
5294
|
|
|
5295
5295
|
// ../../core/ts/src/tokens.ts
|
|
5296
|
+
function tonalElevationAlpha(level) {
|
|
5297
|
+
if (typeof level !== "number" || !Number.isFinite(level)) return 0;
|
|
5298
|
+
return TONAL_ELEVATION_ALPHA[Math.min(5, Math.max(0, Math.round(level)))];
|
|
5299
|
+
}
|
|
5296
5300
|
function buildTokenRegistry(tokens) {
|
|
5297
5301
|
return {
|
|
5298
5302
|
colors: new Set(Object.keys(tokens.colors ?? {})),
|
|
@@ -5342,10 +5346,12 @@ function walk(node, registry, path2, out) {
|
|
|
5342
5346
|
walk(value, registry, `${path2}.${key2}`, out);
|
|
5343
5347
|
}
|
|
5344
5348
|
}
|
|
5345
|
-
var SYSTEM_FONT_FAMILY;
|
|
5349
|
+
var SYSTEM_FONT_FAMILY, TONAL_ELEVATION_ALPHA;
|
|
5346
5350
|
var init_tokens = __esm({
|
|
5347
5351
|
"../../core/ts/src/tokens.ts"() {
|
|
5352
|
+
"use strict";
|
|
5348
5353
|
SYSTEM_FONT_FAMILY = "$system";
|
|
5354
|
+
TONAL_ELEVATION_ALPHA = [0, 0.05, 0.08, 0.11, 0.12, 0.14];
|
|
5349
5355
|
}
|
|
5350
5356
|
});
|
|
5351
5357
|
|
|
@@ -11078,6 +11084,29 @@ function tokenColorIssues(dir, tokensPathOverride) {
|
|
|
11078
11084
|
);
|
|
11079
11085
|
}
|
|
11080
11086
|
}
|
|
11087
|
+
const shadowNames = (layer) => {
|
|
11088
|
+
const sh = layer?.shadows;
|
|
11089
|
+
return new Set(sh !== null && typeof sh === "object" && !Array.isArray(sh) ? Object.keys(sh) : []);
|
|
11090
|
+
};
|
|
11091
|
+
const checkElevations = (layer, where, declared) => {
|
|
11092
|
+
const els = layer?.elevations;
|
|
11093
|
+
if (els === null || typeof els !== "object" || Array.isArray(els)) return;
|
|
11094
|
+
for (const [name, token] of Object.entries(els)) {
|
|
11095
|
+
const shadow = token?.shadow;
|
|
11096
|
+
if (shadow === void 0 || typeof shadow === "string" && declared.has(shadow)) continue;
|
|
11097
|
+
error(
|
|
11098
|
+
`${tokensPath}: ${where}elevations.${name}.shadow is ${JSON.stringify(shadow)}, which names no \`shadows\` token \u2014 every renderer would paint it with no shadow at all. Declare shadows.${typeof shadow === "string" ? shadow : "<name>"} or name one that exists.`
|
|
11099
|
+
);
|
|
11100
|
+
}
|
|
11101
|
+
};
|
|
11102
|
+
const baseShadows = shadowNames(raw);
|
|
11103
|
+
checkElevations(raw, "", baseShadows);
|
|
11104
|
+
const idiom = raw.idiom;
|
|
11105
|
+
if (idiom !== null && typeof idiom === "object" && !Array.isArray(idiom)) {
|
|
11106
|
+
for (const [overlay, layer] of Object.entries(idiom)) {
|
|
11107
|
+
checkElevations(layer, `idiom.${overlay}.`, /* @__PURE__ */ new Set([...baseShadows, ...shadowNames(layer)]));
|
|
11108
|
+
}
|
|
11109
|
+
}
|
|
11081
11110
|
const colors = raw.colors;
|
|
11082
11111
|
if (colors === void 0) return issues;
|
|
11083
11112
|
if (colors === null || typeof colors !== "object" || Array.isArray(colors)) {
|
|
@@ -12843,6 +12872,32 @@ function resolveSchemeColor(o, scheme) {
|
|
|
12843
12872
|
if (typeof o?.light === "string") return o.light;
|
|
12844
12873
|
return typeof o?.dark === "string" ? o.dark : void 0;
|
|
12845
12874
|
}
|
|
12875
|
+
function mixColor(base, over, alpha2) {
|
|
12876
|
+
const a = hexToRgba(base), b = hexToRgba(over);
|
|
12877
|
+
if (a && b) {
|
|
12878
|
+
const t = alpha2 * b[3];
|
|
12879
|
+
const hex2 = (v) => Math.round(v).toString(16).padStart(2, "0");
|
|
12880
|
+
const rgb = [0, 1, 2].map((i) => hex2(a[i] * (1 - t) + b[i] * t)).join("");
|
|
12881
|
+
return a[3] < 1 ? `#${rgb}${hex2(a[3] * 255)}` : `#${rgb}`;
|
|
12882
|
+
}
|
|
12883
|
+
return `color-mix(in srgb, ${over} ${+(alpha2 * 100).toFixed(2)}%, ${base})`;
|
|
12884
|
+
}
|
|
12885
|
+
function hexToRgba(hex2) {
|
|
12886
|
+
const h = hex2.trim().replace(/^#/, "");
|
|
12887
|
+
if (/^[\da-f]{8}$/i.test(h)) {
|
|
12888
|
+
const rgb2 = hexToRgb2(h.slice(0, 6));
|
|
12889
|
+
return rgb2 ? [...rgb2, parseInt(h.slice(6), 16) / 255] : null;
|
|
12890
|
+
}
|
|
12891
|
+
const rgb = hexToRgb2(h);
|
|
12892
|
+
return rgb ? [...rgb, 1] : null;
|
|
12893
|
+
}
|
|
12894
|
+
function elevationBoxShadow(e) {
|
|
12895
|
+
const parts = [
|
|
12896
|
+
...e.border && e.border.width > 0 ? [`inset 0 0 0 ${e.border.width}px ${e.border.color}`] : [],
|
|
12897
|
+
...e.shadow ? [e.shadow] : []
|
|
12898
|
+
];
|
|
12899
|
+
return parts.length ? parts.join(", ") : void 0;
|
|
12900
|
+
}
|
|
12846
12901
|
function makeTokenResolvers(source, scheme, idiom = WEB_IDIOM_CONTEXT) {
|
|
12847
12902
|
const tokens = resolveIdiom(source, idiom);
|
|
12848
12903
|
const color = (name, fallback = "#1a1a1a") => {
|
|
@@ -12933,6 +12988,17 @@ function makeTokenResolvers(source, scheme, idiom = WEB_IDIOM_CONTEXT) {
|
|
|
12933
12988
|
}
|
|
12934
12989
|
return tint ? { background: rgba(tint, m.opacity ?? 1) } : void 0;
|
|
12935
12990
|
};
|
|
12991
|
+
const elevation = (name) => {
|
|
12992
|
+
const e = name ? tokens.elevations?.[name] : void 0;
|
|
12993
|
+
if (!e) return void 0;
|
|
12994
|
+
const alpha2 = tonalElevationAlpha(e.level);
|
|
12995
|
+
const width = e.border ? typeof e.border.width === "number" && Number.isFinite(e.border.width) ? Math.max(0, e.border.width) : 0.5 : 0;
|
|
12996
|
+
return {
|
|
12997
|
+
...alpha2 > 0 ? { tint: { color: color(e.tint ?? "accentPrimary", "#6F3BFF"), alpha: alpha2 } } : {},
|
|
12998
|
+
...e.shadow && shadow(e.shadow) ? { shadow: shadow(e.shadow) } : {},
|
|
12999
|
+
...e.border && width > 0 ? { border: { color: color(e.border.color, "#E0E0E0"), width } } : {}
|
|
13000
|
+
};
|
|
13001
|
+
};
|
|
12936
13002
|
const densityScale = (density) => {
|
|
12937
13003
|
switch (density) {
|
|
12938
13004
|
case "compact":
|
|
@@ -12974,6 +13040,7 @@ function makeTokenResolvers(source, scheme, idiom = WEB_IDIOM_CONTEXT) {
|
|
|
12974
13040
|
gradient,
|
|
12975
13041
|
meshGradient,
|
|
12976
13042
|
material,
|
|
13043
|
+
elevation,
|
|
12977
13044
|
densityScale,
|
|
12978
13045
|
motionTransition,
|
|
12979
13046
|
appearMotion,
|
|
@@ -19815,6 +19882,7 @@ function shouldRevealError(args) {
|
|
|
19815
19882
|
var EMAIL_RE, URL_HOST_RE, INTEGER_RE, NUMBER_RE;
|
|
19816
19883
|
var init_validation = __esm({
|
|
19817
19884
|
"../../core/ts/src/validation.ts"() {
|
|
19885
|
+
"use strict";
|
|
19818
19886
|
EMAIL_RE = /^[A-Za-z0-9._%+-]+@[A-Za-z0-9-]+(\.[A-Za-z0-9-]+)*\.[A-Za-z]{2,}$/;
|
|
19819
19887
|
URL_HOST_RE = /^[A-Za-z0-9-]+(\.[A-Za-z0-9-]+)*(:[0-9]+)?$/;
|
|
19820
19888
|
INTEGER_RE = /^[+-]?[0-9]+$/;
|
|
@@ -23239,6 +23307,7 @@ function validateWorkerSubtree(raw, slotId, policy, actionCatalog) {
|
|
|
23239
23307
|
var WORKER_SUBTREE_CEILINGS, ID_PART, ACTION_ID;
|
|
23240
23308
|
var init_worker_subtree = __esm({
|
|
23241
23309
|
"../../core/ts/src/worker-subtree.ts"() {
|
|
23310
|
+
"use strict";
|
|
23242
23311
|
init_worker_subtree_safe_types_generated();
|
|
23243
23312
|
WORKER_SUBTREE_CEILINGS = Object.freeze({
|
|
23244
23313
|
maxEncodedBytes: 65536,
|
|
@@ -24956,8 +25025,17 @@ function renderInner(node, ctx) {
|
|
|
24956
25025
|
}
|
|
24957
25026
|
case "Grid":
|
|
24958
25027
|
return /* @__PURE__ */ jsx6(GridNode, { props, ctx, wrap, gap });
|
|
24959
|
-
case "Card":
|
|
24960
|
-
|
|
25028
|
+
case "Card": {
|
|
25029
|
+
const m = modifiers;
|
|
25030
|
+
const ownSurface = !!(m?.backgroundGradient ?? m?.background_gradient ?? m?.material) || m?.animatedGradient === true || m?.meshGradient === true;
|
|
25031
|
+
const fill = C(props.background ?? "surfaceSecondary", "#f5f7fa");
|
|
25032
|
+
const card = ctx.elevation("card");
|
|
25033
|
+
const elevation = card && ownSurface ? { border: card.border } : card;
|
|
25034
|
+
const wrapShadow = wrap.boxShadow;
|
|
25035
|
+
const explicit = m?.shadow != null || m?.boxShadow != null || m?.box_shadow != null;
|
|
25036
|
+
const elevationShadow = !elevation ? wrapShadow : explicit ? elevationBoxShadow({ border: elevation.border, shadow: wrapShadow }) : [elevationBoxShadow(elevation), wrapShadow].filter(Boolean).join(", ") || void 0;
|
|
25037
|
+
return /* @__PURE__ */ jsx6("div", { style: { display: "flex", flexDirection: "column", gap: props.spacing == null ? 8 : ctx.sp(props.spacing), padding: props.padding == null ? 12 : ctx.sp(props.padding), background: elevation?.tint ? mixColor(fill, elevation.tint.color, elevation.tint.alpha) : fill, borderRadius: ctx.radius(props.cornerRadius ?? "md", 10), overflow: "hidden", alignSelf: "stretch", ...props.alignment ? { alignItems: props.alignment === "center" ? "center" : props.alignment === "trailing" ? "flex-end" : "flex-start" } : {}, ...wrap, ...elevationShadow ? { boxShadow: elevationShadow } : {} }, children: kids(props.children, ctx) });
|
|
25038
|
+
}
|
|
24961
25039
|
case "SplitView":
|
|
24962
25040
|
return /* @__PURE__ */ jsx6(SplitViewNode, { props, ctx, wrap });
|
|
24963
25041
|
case "Divider":
|
|
@@ -28891,6 +28969,7 @@ __export(render_descriptor_exports, {
|
|
|
28891
28969
|
clientEnvFor: () => clientEnvFor,
|
|
28892
28970
|
collectNativeOnly: () => collectNativeOnly,
|
|
28893
28971
|
isMissingPlaywrightExecutable: () => isMissingPlaywrightExecutable,
|
|
28972
|
+
nodeHidden: () => nodeHidden,
|
|
28894
28973
|
pngAvailable: () => pngAvailable,
|
|
28895
28974
|
pngPageHtml: () => pngPageHtml,
|
|
28896
28975
|
renderDescriptorWeb: () => renderDescriptorWeb,
|
|
@@ -35061,6 +35140,7 @@ __export(screen_crawl_exports, {
|
|
|
35061
35140
|
netTransport: () => netTransport,
|
|
35062
35141
|
parseWorkerAction: () => parseWorkerAction,
|
|
35063
35142
|
recordScreenTraffic: () => recordScreenTraffic,
|
|
35143
|
+
renderedNameGaps: () => renderedNameGaps,
|
|
35064
35144
|
rootOnAppear: () => rootOnAppear,
|
|
35065
35145
|
snapshot: () => snapshot,
|
|
35066
35146
|
structuralSignature: () => structuralSignature,
|
|
@@ -35490,6 +35570,185 @@ function scanTreeValues(node, hit) {
|
|
|
35490
35570
|
}
|
|
35491
35571
|
node.children?.forEach((child) => scanTreeValues(child, hit));
|
|
35492
35572
|
}
|
|
35573
|
+
function joinPath(base, rel) {
|
|
35574
|
+
if (!base) return rel;
|
|
35575
|
+
if (!rel) return base;
|
|
35576
|
+
return rel.startsWith("[") ? `${base}${rel}` : `${base}.${rel}`;
|
|
35577
|
+
}
|
|
35578
|
+
function withinPath(p, root) {
|
|
35579
|
+
return !root || p === root || p.startsWith(`${root}.`) || p.startsWith(`${root}[`);
|
|
35580
|
+
}
|
|
35581
|
+
function resolveNameBinding(v, ctx) {
|
|
35582
|
+
if (v.startsWith("$service:")) return ctx.provider.value(v);
|
|
35583
|
+
if (v.startsWith("$item") && ctx.scope) {
|
|
35584
|
+
const key2 = v.replace(/^\$item\.?/, "");
|
|
35585
|
+
return key2 ? pathValue(ctx.scope, key2) : void 0;
|
|
35586
|
+
}
|
|
35587
|
+
return void 0;
|
|
35588
|
+
}
|
|
35589
|
+
function collapsesWhenEmpty(n, path2, ctx) {
|
|
35590
|
+
const wire = !!n.body && typeof n.body === "object" && !Array.isArray(n.body);
|
|
35591
|
+
const body = wire ? n.body : n;
|
|
35592
|
+
if (body.type !== "Text") return false;
|
|
35593
|
+
const props = body.props;
|
|
35594
|
+
if (!props || props.collapseWhenEmpty !== true) return false;
|
|
35595
|
+
const raw = props.text;
|
|
35596
|
+
const bound = typeof raw === "string" && (raw.startsWith("$service:") || raw.startsWith("$item"));
|
|
35597
|
+
if (bound && raw.startsWith("$item") && !ctx.scope) return false;
|
|
35598
|
+
const eff = bound ? resolveNameBinding(raw, ctx) : raw;
|
|
35599
|
+
const s = eff == null ? "" : String(eff).trim();
|
|
35600
|
+
if (s !== "" && s !== "\u2014") return false;
|
|
35601
|
+
if (bound && typeof eff === "string" && s === "") {
|
|
35602
|
+
ctx.emptied.push({ at: joinPath(path2, wire ? "body.props" : "props"), field: "text", binding: raw });
|
|
35603
|
+
}
|
|
35604
|
+
return true;
|
|
35605
|
+
}
|
|
35606
|
+
function resolveGateValue(raw, ctx) {
|
|
35607
|
+
return typeof raw === "string" && (raw.startsWith("$service:") || raw.startsWith("$item")) ? resolveNameBinding(raw, ctx) : raw;
|
|
35608
|
+
}
|
|
35609
|
+
function sheetNotPresented(n, ctx) {
|
|
35610
|
+
const body = n.body && typeof n.body === "object" && !Array.isArray(n.body) ? n.body : n;
|
|
35611
|
+
if (body.type !== "BottomSheet") return false;
|
|
35612
|
+
const props = body.props;
|
|
35613
|
+
if (!props || props.presentedPath == null && props.presented == null) return false;
|
|
35614
|
+
const presented = resolveGateValue(props.presentedPath != null ? props.presentedPath : props.presented, ctx);
|
|
35615
|
+
return presented !== true && presented !== "true";
|
|
35616
|
+
}
|
|
35617
|
+
function selectedFragmentCase(props, ctx) {
|
|
35618
|
+
const cases = Array.isArray(props.cases) ? props.cases : [];
|
|
35619
|
+
if (!cases.length) return null;
|
|
35620
|
+
const casePath = props.casePath ?? props.case_path;
|
|
35621
|
+
if (typeof casePath === "string" && casePath.startsWith("$") && !casePath.startsWith("$service:") && !casePath.startsWith("$item")) return null;
|
|
35622
|
+
const disc = resolveGateValue(casePath, ctx);
|
|
35623
|
+
const wanted = typeof disc === "string" ? disc : void 0;
|
|
35624
|
+
const idOf = (c) => c && typeof c === "object" ? c.id : void 0;
|
|
35625
|
+
let i = cases.findIndex((c) => idOf(c) === wanted);
|
|
35626
|
+
if (i < 0) {
|
|
35627
|
+
const fallback = props.defaultCase ?? props.default_case;
|
|
35628
|
+
i = cases.findIndex((c) => idOf(c) === fallback);
|
|
35629
|
+
}
|
|
35630
|
+
return i < 0 ? 0 : i;
|
|
35631
|
+
}
|
|
35632
|
+
function projectRenderedNames(node, path2, key2, ctx) {
|
|
35633
|
+
if (!node || typeof node !== "object") return node;
|
|
35634
|
+
if (Array.isArray(node)) return node.map((v, i) => projectRenderedNames(v, `${path2}[${i}]`, key2, ctx));
|
|
35635
|
+
if (isActionEnvelope(node) || key2 === "args") return node;
|
|
35636
|
+
const n = node;
|
|
35637
|
+
const rawMods = n.modifiers;
|
|
35638
|
+
const mods = rawMods && typeof rawMods === "object" && !Array.isArray(rawMods) ? rawMods : {};
|
|
35639
|
+
if (mods.accessibilityHidden === true) return node;
|
|
35640
|
+
if (ctx.unrendered.has(path2) || nodeHidden(mods, ctx.provider, ctx.scope, null) || collapsesWhenEmpty(n, path2, ctx) || sheetNotPresented(n, ctx)) {
|
|
35641
|
+
return { ...n, modifiers: { ...mods, accessibilityHidden: true } };
|
|
35642
|
+
}
|
|
35643
|
+
const props = n.props && typeof n.props === "object" && !Array.isArray(n.props) ? n.props : null;
|
|
35644
|
+
if (props && n.type === "FragmentSlot") {
|
|
35645
|
+
const selected = selectedFragmentCase(props, ctx);
|
|
35646
|
+
if (selected !== null) {
|
|
35647
|
+
props.cases.forEach((_, i) => {
|
|
35648
|
+
if (i !== selected) ctx.unrendered.add(joinPath(path2, `props.cases[${i}]`));
|
|
35649
|
+
});
|
|
35650
|
+
}
|
|
35651
|
+
}
|
|
35652
|
+
let boundTemplate = false;
|
|
35653
|
+
if (props && (n.type === "LazyList" || n.type === "Grid")) {
|
|
35654
|
+
const itemsPath = props.itemsPath ?? props.items_path;
|
|
35655
|
+
const template = Array.isArray(props.items) ? props.items[0] : void 0;
|
|
35656
|
+
if (typeof itemsPath === "string" && template !== void 0) {
|
|
35657
|
+
boundTemplate = true;
|
|
35658
|
+
const local = itemsPath.startsWith("$item.");
|
|
35659
|
+
const rows = local ? ctx.scope ? pathValue(ctx.scope, itemsPath.slice("$item.".length)) : void 0 : itemsPath.startsWith("$service:") ? ctx.provider.value(itemsPath) : void 0;
|
|
35660
|
+
if (Array.isArray(rows) || local) {
|
|
35661
|
+
const paintsRows = Array.isArray(rows) && rows.length > 0;
|
|
35662
|
+
props.items.forEach((_, i) => {
|
|
35663
|
+
const at = joinPath(path2, `props.items[${i}]`);
|
|
35664
|
+
if (i === 0 && paintsRows) ctx.rowTemplates.push(at);
|
|
35665
|
+
else ctx.unrendered.add(at);
|
|
35666
|
+
});
|
|
35667
|
+
}
|
|
35668
|
+
if (Array.isArray(rows) && rows.length) {
|
|
35669
|
+
ctx.lists.push({
|
|
35670
|
+
templatePath: joinPath(path2, "props.items[0]"),
|
|
35671
|
+
template,
|
|
35672
|
+
rows: rows.slice(0, A11Y_ROWS_PER_LIST)
|
|
35673
|
+
});
|
|
35674
|
+
}
|
|
35675
|
+
}
|
|
35676
|
+
}
|
|
35677
|
+
const out = {};
|
|
35678
|
+
for (const [k, v] of Object.entries(n)) {
|
|
35679
|
+
if (NAME_KEYS.has(k) && typeof v === "string" && (v.startsWith("$service:") || v.startsWith("$item"))) {
|
|
35680
|
+
const r = resolveNameBinding(v, ctx);
|
|
35681
|
+
if (typeof r === "string") {
|
|
35682
|
+
if (r.trim() === "") ctx.emptied.push({ at: path2, field: k, binding: v });
|
|
35683
|
+
out[k] = r;
|
|
35684
|
+
} else if (typeof r === "number" || typeof r === "boolean") {
|
|
35685
|
+
out[k] = String(r);
|
|
35686
|
+
} else {
|
|
35687
|
+
out[k] = v;
|
|
35688
|
+
}
|
|
35689
|
+
continue;
|
|
35690
|
+
}
|
|
35691
|
+
if (k === "props" && boundTemplate && props) {
|
|
35692
|
+
const p = {};
|
|
35693
|
+
for (const [pk, pv] of Object.entries(props)) {
|
|
35694
|
+
p[pk] = projectRenderedNames(
|
|
35695
|
+
pv,
|
|
35696
|
+
joinPath(path2, `props.${pk}`),
|
|
35697
|
+
pk,
|
|
35698
|
+
pk === "items" ? { ...ctx, scope: void 0 } : ctx
|
|
35699
|
+
);
|
|
35700
|
+
}
|
|
35701
|
+
out[k] = p;
|
|
35702
|
+
continue;
|
|
35703
|
+
}
|
|
35704
|
+
out[k] = projectRenderedNames(v, joinPath(path2, k), k, ctx);
|
|
35705
|
+
}
|
|
35706
|
+
return out;
|
|
35707
|
+
}
|
|
35708
|
+
function renderedNameGaps(descriptor, state) {
|
|
35709
|
+
const provider = new FakeServiceProvider(state);
|
|
35710
|
+
const found = /* @__PURE__ */ new Map();
|
|
35711
|
+
const rowsJudged = /* @__PURE__ */ new Map();
|
|
35712
|
+
const rowTemplateOf = /* @__PURE__ */ new Map();
|
|
35713
|
+
const judge = (root, base, scope, depth, inRow) => {
|
|
35714
|
+
if (inRow) rowsJudged.set(base, (rowsJudged.get(base) ?? 0) + 1);
|
|
35715
|
+
const ctx = { provider, scope, emptied: [], lists: [], unrendered: /* @__PURE__ */ new Set(), rowTemplates: [] };
|
|
35716
|
+
const projected = projectRenderedNames(root, "", "", ctx);
|
|
35717
|
+
for (const g of accessibleNameGaps(projected)) {
|
|
35718
|
+
const rel = g.at === "root" ? "" : g.at;
|
|
35719
|
+
const at = joinPath(base, rel) || "root";
|
|
35720
|
+
if (g.reason !== "interactive" && !CONTROL_ROLES.has(g.role)) continue;
|
|
35721
|
+
if (ctx.rowTemplates.some((t) => withinPath(rel, t))) continue;
|
|
35722
|
+
const cause = ctx.emptied.find((e) => withinPath(e.at, rel));
|
|
35723
|
+
if (!cause) continue;
|
|
35724
|
+
const prev = found.get(at);
|
|
35725
|
+
if (prev) {
|
|
35726
|
+
if (prev.rows && inRow) prev.rows.hit += 1;
|
|
35727
|
+
continue;
|
|
35728
|
+
}
|
|
35729
|
+
if (inRow) rowTemplateOf.set(at, base);
|
|
35730
|
+
found.set(at, {
|
|
35731
|
+
gap: { ...g, at },
|
|
35732
|
+
binding: cause.binding,
|
|
35733
|
+
field: cause.field,
|
|
35734
|
+
...inRow ? { rows: { hit: 1, of: 0 } } : {}
|
|
35735
|
+
});
|
|
35736
|
+
}
|
|
35737
|
+
if (depth >= A11Y_LIST_DEPTH) return;
|
|
35738
|
+
for (const list of ctx.lists) {
|
|
35739
|
+
list.rows.forEach((el, i) => {
|
|
35740
|
+
const rowScope = el && typeof el === "object" && !Array.isArray(el) ? { ...el, __index: i } : { value: el, __index: i };
|
|
35741
|
+
judge(list.template, joinPath(base, list.templatePath), rowScope, depth + 1, true);
|
|
35742
|
+
});
|
|
35743
|
+
}
|
|
35744
|
+
};
|
|
35745
|
+
judge(descriptor, "", void 0, 0, false);
|
|
35746
|
+
for (const [at, template] of rowTemplateOf) {
|
|
35747
|
+
const rows = found.get(at)?.rows;
|
|
35748
|
+
if (rows) rows.of = rowsJudged.get(template) ?? rows.hit;
|
|
35749
|
+
}
|
|
35750
|
+
return [...found.values()];
|
|
35751
|
+
}
|
|
35493
35752
|
function collectObservations(descriptorOrPayload, state) {
|
|
35494
35753
|
const out = [];
|
|
35495
35754
|
const walk2 = (node) => {
|
|
@@ -35919,6 +36178,21 @@ async function crawlScreenInner(opts) {
|
|
|
35919
36178
|
});
|
|
35920
36179
|
});
|
|
35921
36180
|
}
|
|
36181
|
+
for (const r of renderedNameGaps(descriptor, state)) {
|
|
36182
|
+
findings.push({
|
|
36183
|
+
screen: screenId,
|
|
36184
|
+
trigger,
|
|
36185
|
+
invariant: "unnamed-control",
|
|
36186
|
+
severity: "error",
|
|
36187
|
+
// Stable across states on purpose: the row count lives in `actual`, so the same control
|
|
36188
|
+
// seen empty after boot and after a tap dedupes to one finding.
|
|
36189
|
+
message: `${r.gap.type} renders no accessible name: its only name, ${r.binding}, resolved to "" \u2014 a screen reader announces an unnamed \`${r.gap.role}\``,
|
|
36190
|
+
at: r.gap.at,
|
|
36191
|
+
field: r.field,
|
|
36192
|
+
actual: r.rows ? `"" on ${r.rows.hit} of ${r.rows.of} row(s)` : '""',
|
|
36193
|
+
hint: "`himi validate` counts a bound name as a name \u2014 it becomes copy on device \u2014 so only the crawl can see it come out empty. Give the control a name that cannot be empty: a literal `modifiers.accessibilityLabel` (an icon-only control can use `Icon.contentDescription`), or have the worker fall back to real copy instead of an empty string"
|
|
36194
|
+
});
|
|
36195
|
+
}
|
|
35922
36196
|
for (const ref of collectServiceBindings(descriptor)) {
|
|
35923
36197
|
if (!(ref.svc in state)) {
|
|
35924
36198
|
findings.push({
|
|
@@ -36700,11 +36974,12 @@ async function crawlScreenRelations(opts) {
|
|
|
36700
36974
|
return { findings: dedupe(findings), checked, byKind };
|
|
36701
36975
|
});
|
|
36702
36976
|
}
|
|
36703
|
-
var ACTION_KEYS, UNTRACKED_ITEMS, NESTED_ITEMS_SEP, REJECT_ALL_MESSAGE, VLQ, WIX_DATA_QUERY, WIX_DATA_ANY, CAPABILITY_ACTION, detached, CRAWL_CLOCK, MEMBER_IDENTITY_ACTION, BLANK_WHEN_BOUND, STALE_SNAPSHOT_HINT, UNBINDABLE_ROW;
|
|
36977
|
+
var ACTION_KEYS, UNTRACKED_ITEMS, NESTED_ITEMS_SEP, REJECT_ALL_MESSAGE, VLQ, WIX_DATA_QUERY, WIX_DATA_ANY, CAPABILITY_ACTION, detached, CRAWL_CLOCK, MEMBER_IDENTITY_ACTION, BLANK_WHEN_BOUND, CONTROL_ROLES, NAME_KEYS, A11Y_ROWS_PER_LIST, A11Y_LIST_DEPTH, STALE_SNAPSHOT_HINT, UNBINDABLE_ROW;
|
|
36704
36978
|
var init_screen_crawl = __esm({
|
|
36705
36979
|
"../../core/dev-server/src/screen-crawl.ts"() {
|
|
36706
36980
|
init_validate_descriptor_core();
|
|
36707
36981
|
init_render_descriptor();
|
|
36982
|
+
init_a11y_contract();
|
|
36708
36983
|
init_net_scenario();
|
|
36709
36984
|
init_media_value();
|
|
36710
36985
|
init_FakeServiceProvider();
|
|
@@ -36740,6 +37015,24 @@ var init_screen_crawl = __esm({
|
|
|
36740
37015
|
Marquee: ["items", "itemsPath", "text"],
|
|
36741
37016
|
MediaPlayer: ["items", "itemsPath"]
|
|
36742
37017
|
};
|
|
37018
|
+
CONTROL_ROLES = /* @__PURE__ */ new Set([
|
|
37019
|
+
"button",
|
|
37020
|
+
"link",
|
|
37021
|
+
"toggle",
|
|
37022
|
+
"checkbox",
|
|
37023
|
+
"radio",
|
|
37024
|
+
"tab",
|
|
37025
|
+
"tablist",
|
|
37026
|
+
"adjustable",
|
|
37027
|
+
"search",
|
|
37028
|
+
"textfield",
|
|
37029
|
+
"combobox",
|
|
37030
|
+
"menu",
|
|
37031
|
+
"menuitem"
|
|
37032
|
+
]);
|
|
37033
|
+
NAME_KEYS = new Set(ACCESSIBLE_NAME_KEYS);
|
|
37034
|
+
A11Y_ROWS_PER_LIST = 20;
|
|
37035
|
+
A11Y_LIST_DEPTH = 3;
|
|
36743
37036
|
STALE_SNAPSHOT_HINT = 'if every path already does, look for a stale-snapshot write-back: `return s` (or `{ ...s }`) after an await or from an early-return guard writes back the state as it was BEFORE the await, loading flag included \u2014 to change nothing, return `undefined` instead (not `null` or `{}`); see `himi docs tier-5-add-a-screen`, "Should this result be committed at all?"';
|
|
36744
37037
|
UNBINDABLE_ROW = Symbol("unbindable-row");
|
|
36745
37038
|
}
|
|
@@ -46100,6 +46393,16 @@ function assembleLedger(r, options = {}) {
|
|
|
46100
46393
|
remedy: "renders on device only \u2014 himi push a preview and check by eye"
|
|
46101
46394
|
});
|
|
46102
46395
|
}
|
|
46396
|
+
if ((r.crawled ?? []).length) {
|
|
46397
|
+
const found = [...r.failures, ...r.warnings].filter((f) => f.invariant === "unnamed-control").length;
|
|
46398
|
+
out.push({
|
|
46399
|
+
key: "a11y|app",
|
|
46400
|
+
kind: "a11y",
|
|
46401
|
+
screen: null,
|
|
46402
|
+
gap: `accessibility is judged for ONE property only \u2014 an operable control whose bound name rendered empty (\`unnamed-control\`, ${found} this run). NOT judged: names authored statically in the descriptor, role/state announcement, focus order, contrast, touch-target size \u2014 a clean run is not an accessibility pass`,
|
|
46403
|
+
remedy: "run `himi validate` for statically authored names (its a11y pass), then `himi push` a preview and walk every screen with VoiceOver and TalkBack"
|
|
46404
|
+
});
|
|
46405
|
+
}
|
|
46103
46406
|
if (!/(^|\+ )mocked/.test(r.transport) && !r.transport.startsWith("offline")) {
|
|
46104
46407
|
out.push({
|
|
46105
46408
|
key: "no-user-mocks|app",
|
|
@@ -46474,7 +46777,7 @@ var init_assets = __esm({
|
|
|
46474
46777
|
async function checkPinnedRead(input) {
|
|
46475
46778
|
const { secret, token, app, releaseId, screenId, authorizePreview } = input;
|
|
46476
46779
|
if (!secret) return authorizePreview ? { status: 503, body: { error: "preview_token_unavailable" } } : null;
|
|
46477
|
-
if (!token) return await input.
|
|
46780
|
+
if (!token) return await input.isPublic() ? null : { status: 401, body: { error: "preview_token_required", app, releaseId } };
|
|
46478
46781
|
const verified = await verifyPreviewToken({ token, secret, app, releaseId, screenId });
|
|
46479
46782
|
if (verified.ok) {
|
|
46480
46783
|
if (authorizePreview ? !await authorizePreview(verified.claims) : !!verified.claims.siteLease) {
|
|
@@ -46756,9 +47059,30 @@ function makeServeHandler(opts) {
|
|
|
46756
47059
|
function resolvePin(request, url) {
|
|
46757
47060
|
return request.headers.get("x-himalaya-release") || url.searchParams.get("release") || null;
|
|
46758
47061
|
}
|
|
47062
|
+
const RELEASE_SOURCE_HEADER = "x-himalaya-release-source";
|
|
46759
47063
|
function resolvePreviewToken(request, url) {
|
|
46760
47064
|
return request.headers.get("x-himalaya-preview-token") || url.searchParams.get("pvt") || null;
|
|
46761
47065
|
}
|
|
47066
|
+
const RING_CACHE_CAP = 500;
|
|
47067
|
+
const ringCache = /* @__PURE__ */ new Map();
|
|
47068
|
+
async function isRingRelease(app, releaseId) {
|
|
47069
|
+
if (!opts.ringReleases) return false;
|
|
47070
|
+
const hit = ringCache.get(app);
|
|
47071
|
+
let ids = hit && Date.now() - hit.at < manifestCacheTtlMs ? hit.ids : void 0;
|
|
47072
|
+
if (!ids) {
|
|
47073
|
+
try {
|
|
47074
|
+
ids = new Set(await opts.ringReleases(app));
|
|
47075
|
+
} catch {
|
|
47076
|
+
ids = /* @__PURE__ */ new Set();
|
|
47077
|
+
}
|
|
47078
|
+
if (!ringCache.has(app) && ringCache.size >= RING_CACHE_CAP) {
|
|
47079
|
+
const oldest = ringCache.keys().next().value;
|
|
47080
|
+
if (oldest !== void 0) ringCache.delete(oldest);
|
|
47081
|
+
}
|
|
47082
|
+
ringCache.set(app, { ids, at: Date.now() });
|
|
47083
|
+
}
|
|
47084
|
+
return ids.has(releaseId);
|
|
47085
|
+
}
|
|
46762
47086
|
async function pinnedManifest(app, releaseId) {
|
|
46763
47087
|
const key2 = `${app}
|
|
46764
47088
|
${releaseId}`;
|
|
@@ -46843,11 +47167,40 @@ ${releaseId}`;
|
|
|
46843
47167
|
app,
|
|
46844
47168
|
releaseId,
|
|
46845
47169
|
screenId,
|
|
46846
|
-
|
|
47170
|
+
isPublic: async () => (await manifest(app))?.releaseId === releaseId || await isRingRelease(app, releaseId),
|
|
46847
47171
|
authorizePreview: opts.authorizePreview
|
|
46848
47172
|
});
|
|
46849
47173
|
return denied && json3(denied.status, denied.body);
|
|
46850
47174
|
}
|
|
47175
|
+
async function honourRingPin(request) {
|
|
47176
|
+
const kept = { request, dropped: false };
|
|
47177
|
+
if (request.method !== "GET" && request.method !== "HEAD") return kept;
|
|
47178
|
+
if (request.headers.get(RELEASE_SOURCE_HEADER) !== "ring") return kept;
|
|
47179
|
+
const pin = request.headers.get("x-himalaya-release");
|
|
47180
|
+
if (!pin) return kept;
|
|
47181
|
+
const url = new URL(request.url);
|
|
47182
|
+
const app = /^\/v1\/app-config\/([^/]+)$/.exec(url.pathname)?.[1] ?? resolveApp(request, url);
|
|
47183
|
+
if (!app) return kept;
|
|
47184
|
+
const drop = () => {
|
|
47185
|
+
const headers2 = new Headers(request.headers);
|
|
47186
|
+
headers2.delete("x-himalaya-release");
|
|
47187
|
+
headers2.delete(RELEASE_SOURCE_HEADER);
|
|
47188
|
+
return { request: new Request(request, { headers: headers2 }), dropped: true };
|
|
47189
|
+
};
|
|
47190
|
+
if (/^\/v1\/app-config\/[^/]+$/.test(url.pathname)) return drop();
|
|
47191
|
+
let servable;
|
|
47192
|
+
try {
|
|
47193
|
+
const live2 = await manifest(app);
|
|
47194
|
+
const eligible = live2?.releaseId === pin || await isRingRelease(app, pin);
|
|
47195
|
+
const pinned = eligible ? await pinnedManifest(app, pin) : null;
|
|
47196
|
+
servable = pinned !== null;
|
|
47197
|
+
const screenId = /^\/v1\/screen\/([^/]+)$/.exec(url.pathname)?.[1];
|
|
47198
|
+
if (servable && screenId && !pinned?.screens[screenId]) servable = false;
|
|
47199
|
+
} catch {
|
|
47200
|
+
return kept;
|
|
47201
|
+
}
|
|
47202
|
+
return servable ? kept : drop();
|
|
47203
|
+
}
|
|
46851
47204
|
const corsHeaders = (request) => {
|
|
46852
47205
|
const origin = request.headers.get("origin");
|
|
46853
47206
|
if (!origin) return {};
|
|
@@ -46856,7 +47209,7 @@ ${releaseId}`;
|
|
|
46856
47209
|
"access-control-allow-origin": origin,
|
|
46857
47210
|
vary: "Origin",
|
|
46858
47211
|
"access-control-allow-methods": "GET, HEAD, POST, PUT, DELETE, OPTIONS",
|
|
46859
|
-
"access-control-allow-headers": "Content-Type, If-None-Match, X-Himalaya-Schema-Versions, Accept-Language, X-Himalaya-Session, X-Himalaya-User, X-Himalaya-Platform, X-Himalaya-App-Version, X-Himalaya-App, X-Himalaya-Release, X-Himalaya-Preview-Token",
|
|
47212
|
+
"access-control-allow-headers": "Content-Type, If-None-Match, X-Himalaya-Schema-Versions, Accept-Language, X-Himalaya-Session, X-Himalaya-User, X-Himalaya-Platform, X-Himalaya-App-Version, X-Himalaya-App, X-Himalaya-Release, X-Himalaya-Release-Source, X-Himalaya-Preview-Token",
|
|
46860
47213
|
"access-control-expose-headers": "ETag, X-Himalaya-Bundle-Integrity, X-Himalaya-App",
|
|
46861
47214
|
"access-control-allow-credentials": "true"
|
|
46862
47215
|
};
|
|
@@ -47396,7 +47749,8 @@ ${releaseId}`;
|
|
|
47396
47749
|
}
|
|
47397
47750
|
const mm = m;
|
|
47398
47751
|
const readDoc = (hash, live2) => pin ? hash ? pinnedBody(app, hash, live2) : Promise.resolve(null) : live2();
|
|
47399
|
-
const
|
|
47752
|
+
const ringPinned = pin !== null && request.headers.get(RELEASE_SOURCE_HEADER) === "ring";
|
|
47753
|
+
const cfgBytes = ringPinned ? await store.getAppConfig(app) : await readDoc(mm.appConfigHash, () => store.getAppConfig(app));
|
|
47400
47754
|
if (!cfgBytes) return json3(404, { error: "app_config_not_found", app });
|
|
47401
47755
|
let appConfig;
|
|
47402
47756
|
try {
|
|
@@ -47404,7 +47758,7 @@ ${releaseId}`;
|
|
|
47404
47758
|
} catch {
|
|
47405
47759
|
return json3(500, { error: "app_config_invalid", app });
|
|
47406
47760
|
}
|
|
47407
|
-
const bootOverlay = !pin && opts.devControl ? devControlOverlay.get(app) : void 0;
|
|
47761
|
+
const bootOverlay = (!pin || ringPinned) && opts.devControl ? devControlOverlay.get(app) : void 0;
|
|
47408
47762
|
if (bootOverlay) appConfig.control = { ...appConfig.control ?? {}, ...bootOverlay };
|
|
47409
47763
|
const tokensBytes = await readDoc(mm.tokensHash, () => store.getTokens(app));
|
|
47410
47764
|
let tokens = null;
|
|
@@ -47421,9 +47775,18 @@ ${releaseId}`;
|
|
|
47421
47775
|
let screen = null;
|
|
47422
47776
|
let screenHash = "";
|
|
47423
47777
|
let homeBundle = null;
|
|
47424
|
-
|
|
47778
|
+
let homeFrom = mm;
|
|
47779
|
+
let readHome = readDoc;
|
|
47780
|
+
if (ringPinned && homeScreenId && !mm.screens[homeScreenId]) {
|
|
47781
|
+
const live2 = await manifest(app);
|
|
47782
|
+
if (live2?.screens[homeScreenId]) {
|
|
47783
|
+
homeFrom = live2;
|
|
47784
|
+
readHome = (_hash, liveSlot) => liveSlot();
|
|
47785
|
+
}
|
|
47786
|
+
}
|
|
47787
|
+
const homeMeta = homeScreenId ? homeFrom.screens[homeScreenId] : void 0;
|
|
47425
47788
|
if (homeScreenId && homeMeta) {
|
|
47426
|
-
const body = await
|
|
47789
|
+
const body = await readHome(homeMeta.descriptorHash, () => store.getScreen(app, homeScreenId));
|
|
47427
47790
|
if (body) {
|
|
47428
47791
|
try {
|
|
47429
47792
|
screen = JSON.parse(utf8Decode(body));
|
|
@@ -47433,7 +47796,7 @@ ${releaseId}`;
|
|
|
47433
47796
|
}
|
|
47434
47797
|
}
|
|
47435
47798
|
if (homeMeta.bundle) {
|
|
47436
|
-
const entry =
|
|
47799
|
+
const entry = homeFrom.bundles[homeMeta.bundle];
|
|
47437
47800
|
if (entry?.sha256 && entry?.sri) {
|
|
47438
47801
|
homeBundle = {
|
|
47439
47802
|
sha256: entry.sha256,
|
|
@@ -47443,9 +47806,10 @@ ${releaseId}`;
|
|
|
47443
47806
|
}
|
|
47444
47807
|
}
|
|
47445
47808
|
}
|
|
47446
|
-
const
|
|
47447
|
-
const
|
|
47448
|
-
const
|
|
47809
|
+
const cfgTag = ringPinned ? sha256hex(JSON.stringify(appConfig)) : mm.appConfigHash ?? "";
|
|
47810
|
+
const etag = strongEtag(`${mm.releaseId}:${cfgTag}:${mm.tokensHash ?? ""}:${screenHash}:${homeBundle?.sha256 ?? ""}`);
|
|
47811
|
+
const cache = pin && !ringPinned ? PINNED_CACHE_CONTROL : DOC_CACHE_CONTROL;
|
|
47812
|
+
const vary = pin ? { vary: ringPinned ? `x-himalaya-release, ${RELEASE_SOURCE_HEADER}` : "x-himalaya-release" } : {};
|
|
47449
47813
|
if (request.headers.get("if-none-match") === etag) {
|
|
47450
47814
|
return new Response(null, { status: 304, headers: { etag, "cache-control": cache, ...vary } });
|
|
47451
47815
|
}
|
|
@@ -47626,7 +47990,14 @@ ${releaseId}`;
|
|
|
47626
47990
|
if (request.method === "OPTIONS") {
|
|
47627
47991
|
return new Response(null, { status: 204, headers: { ...corsHeaders(request), "content-length": "0" } });
|
|
47628
47992
|
}
|
|
47629
|
-
const
|
|
47993
|
+
const ring = await honourRingPin(request);
|
|
47994
|
+
const response = await handle(ring.request);
|
|
47995
|
+
if (ring.dropped) {
|
|
47996
|
+
const parts = new Set(
|
|
47997
|
+
[...response.headers.get("vary")?.split(",") ?? [], "x-himalaya-release", RELEASE_SOURCE_HEADER].map((s) => s.trim()).filter(Boolean)
|
|
47998
|
+
);
|
|
47999
|
+
response.headers.set("vary", [...parts].join(", "));
|
|
48000
|
+
}
|
|
47630
48001
|
if (resolvePreviewToken(request, new URL(request.url))) {
|
|
47631
48002
|
response.headers.set("cache-control", "private, no-store");
|
|
47632
48003
|
response.headers.set("vary", "x-himalaya-preview-token, x-himalaya-release");
|
|
@@ -47996,19 +48367,19 @@ var init_preview_assets_generated = __esm({
|
|
|
47996
48367
|
"../../packages/serve/src/preview-assets.generated.ts"() {
|
|
47997
48368
|
init_preview_assets_format();
|
|
47998
48369
|
PREVIEW_ASSET_MANIFEST = [
|
|
47999
|
-
{ key: "assets/index-
|
|
48000
|
-
{ key: "fonts/WixMadeforDisplay-Bold.ttf", contentType: "font/ttf", offset:
|
|
48001
|
-
{ key: "fonts/WixMadeforDisplay-ExtraBold.ttf", contentType: "font/ttf", offset:
|
|
48002
|
-
{ key: "fonts/WixMadeforDisplay-Medium.ttf", contentType: "font/ttf", offset:
|
|
48003
|
-
{ key: "fonts/WixMadeforDisplay-Regular.ttf", contentType: "font/ttf", offset:
|
|
48004
|
-
{ key: "fonts/WixMadeforDisplay-SemiBold.ttf", contentType: "font/ttf", offset:
|
|
48005
|
-
{ key: "fonts/WixMadeforText-Bold.ttf", contentType: "font/ttf", offset:
|
|
48006
|
-
{ key: "fonts/WixMadeforText-ExtraBold.ttf", contentType: "font/ttf", offset:
|
|
48007
|
-
{ key: "fonts/WixMadeforText-Medium.ttf", contentType: "font/ttf", offset:
|
|
48008
|
-
{ key: "fonts/WixMadeforText-Regular.ttf", contentType: "font/ttf", offset:
|
|
48009
|
-
{ key: "fonts/WixMadeforText-SemiBold.ttf", contentType: "font/ttf", offset:
|
|
48010
|
-
{ key: "index.html", contentType: "text/html; charset=utf-8", offset:
|
|
48011
|
-
{ key: "t5-frame.html", contentType: "text/html; charset=utf-8", offset:
|
|
48370
|
+
{ key: "assets/index-DkRimsxm.js", contentType: "application/javascript; charset=utf-8", offset: 8, length: 1020087, sha256: "4cb472d9d6d952d85166c025544c8bae669215c1a5eafbb800cec32d199bad14" },
|
|
48371
|
+
{ key: "fonts/WixMadeforDisplay-Bold.ttf", contentType: "font/ttf", offset: 1020095, length: 90924, sha256: "ec4c7d63772d980d6e147b2df6215fd2737e4b04cebe0cf7e11dc695fb63cdcf" },
|
|
48372
|
+
{ key: "fonts/WixMadeforDisplay-ExtraBold.ttf", contentType: "font/ttf", offset: 1111019, length: 90984, sha256: "bf4c16a2774edd38a921edf3099469866cd74c094fd596f3fa4e81f54a04dfaa" },
|
|
48373
|
+
{ key: "fonts/WixMadeforDisplay-Medium.ttf", contentType: "font/ttf", offset: 1202003, length: 90980, sha256: "368faca44fb9f7207e4af784e94750b1ee59c1098b8689e0f4a918926f244fb5" },
|
|
48374
|
+
{ key: "fonts/WixMadeforDisplay-Regular.ttf", contentType: "font/ttf", offset: 1292983, length: 90872, sha256: "97ed8f1a6772abe26e3d8af5cecfc4b02faa94497d585b0398b5053b739a70c1" },
|
|
48375
|
+
{ key: "fonts/WixMadeforDisplay-SemiBold.ttf", contentType: "font/ttf", offset: 1383855, length: 90980, sha256: "642feab8a48ddf6c7a89d7c2a7c0edfa0e6e773f671287e460e222703563191e" },
|
|
48376
|
+
{ key: "fonts/WixMadeforText-Bold.ttf", contentType: "font/ttf", offset: 1474835, length: 108636, sha256: "37c179b4fefb63ea9637322b84583a2df53aa226c150294eea1675c14b1b8e29" },
|
|
48377
|
+
{ key: "fonts/WixMadeforText-ExtraBold.ttf", contentType: "font/ttf", offset: 1583471, length: 109084, sha256: "62e2fe4718129f36ecdb0ae4de385bd6b81ecc876146447167c6c140fad699a9" },
|
|
48378
|
+
{ key: "fonts/WixMadeforText-Medium.ttf", contentType: "font/ttf", offset: 1692555, length: 109048, sha256: "4ecf270136af928612c76fe4ebade91d8156975e5ea4c0cf9225501dd1788e02" },
|
|
48379
|
+
{ key: "fonts/WixMadeforText-Regular.ttf", contentType: "font/ttf", offset: 1801603, length: 107700, sha256: "dee56f09fd3efc127c1ea95557edc74110c1460a46287da36e0af25efd341ef5" },
|
|
48380
|
+
{ key: "fonts/WixMadeforText-SemiBold.ttf", contentType: "font/ttf", offset: 1909303, length: 88108, sha256: "dced97a77624bc5f8b1ae65850360908297f4588e09feb6f22792d29dc7e765b" },
|
|
48381
|
+
{ key: "index.html", contentType: "text/html; charset=utf-8", offset: 1997411, length: 4448, sha256: "22fbd62116f9571c6b01c1960990e392cce62198610eea2f661cb4d97bc05787" },
|
|
48382
|
+
{ key: "t5-frame.html", contentType: "text/html; charset=utf-8", offset: 2001859, length: 17209, sha256: "dffc9718e2faa63ae2cc919e0a842f585ed565aaaa7c53778e90379442c731b6" }
|
|
48012
48383
|
];
|
|
48013
48384
|
}
|
|
48014
48385
|
});
|
|
Binary file
|
package/guides/himi-test.md
CHANGED
|
@@ -120,6 +120,22 @@ Only **unambiguous** defects, so a green run stays worth trusting:
|
|
|
120
120
|
| `list-shrank-on-load-more` | **warning** | A "load more" returned FEWER rows than before it ran: the new page replaced the list instead of extending it. |
|
|
121
121
|
| `list-grew-on-filter` | **warning** | A filter, search or sort made a list longer. Narrowing can reorder or remove, never add. |
|
|
122
122
|
| `typed-value-ignored` | **warning** | The crawl typed the value a control really carries — the payload key every renderer injects for that control — and the dispatch changed **nothing**: no state, no navigation, no request. The value is read off the app itself (an id from the control's own options, or a word from the worker's seeded state) and is always one the control is *not* already showing, so a reducer that ignores it cannot work on device. A warning because the input is the crawl's: a reducer that legitimately no-ops on a value is possible, and `himi-test.json` `skip[]` takes the invariant. |
|
|
123
|
+
| `unnamed-control` | error | **Accessibility.** An operable control — a `Button`, `Toggle`, `TextField`, `Chip`, … or any node an `onTap` promotes to a button — whose *only* accessible name is a binding the worker resolved to an empty string, so a screen reader announces an unnamed control. `himi validate`'s a11y pass cannot see this: it counts `$service:` / `$item` as a name, because on device it becomes copy. Only the crawl has run the worker. Judged per real row inside a bound list, and reported once at the row template. Silent on a name authored empty (that is `himi validate`'s finding, not repeated here), on `null` / absent values (other invariants'), on content that is not a control (a bound `Text` that came out empty), and on nodes collapsed in that state. |
|
|
124
|
+
|
|
125
|
+
**Accessibility: what a clean run does NOT mean.** `unnamed-control` is the only
|
|
126
|
+
accessibility property the crawl judges, and it is deliberately the narrow one: the name a
|
|
127
|
+
worker actually produced for a control. It does not judge names authored in the descriptor
|
|
128
|
+
(`himi validate` does — its `a11y` pass is on by default, and the crawl never repeats what it
|
|
129
|
+
reports), role or state announcement, focus order, contrast, or touch-target size. The gap
|
|
130
|
+
ledger says so on every run with an `a11y` entry, because a headless crawl cannot close it —
|
|
131
|
+
before that entry existed, a clean crawl with an empty ledger read as an accessibility pass.
|
|
132
|
+
It ships at `error`: a control a screen reader cannot name is unusable, not a matter of taste.
|
|
133
|
+
Swept before it did, over 53 apps and templates: one false positive (a tap-to-dismiss `Text`
|
|
134
|
+
that `collapseWhenEmpty` removes, now honoured) and one real defect — a blank download button
|
|
135
|
+
on a lesson screen whose id resolves to no lesson. That last one is also why an input-less boot's
|
|
136
|
+
finding is **not** retired the way `no-request-issued` is: a detail screen reached with a stale
|
|
137
|
+
or unknown id renders exactly the state the crawl's input-less boot does, so a label that goes
|
|
138
|
+
blank there goes blank for a real user too.
|
|
123
139
|
|
|
124
140
|
**Deliberately never reported:** an empty collection · an `error` state (often the correct
|
|
125
141
|
answer to empty input) · a `null` optional.
|
package/guides/tokens.md
CHANGED
|
@@ -20,6 +20,7 @@ one registry, mirrored byte-for-byte by the iOS + Android decoders.
|
|
|
20
20
|
| `tabBar` / `drawer` / `sidebar` | nav-chrome style blocks | optional |
|
|
21
21
|
| `kit` | { material?, variant?, density?, motion? } | optional — the active design kit |
|
|
22
22
|
| `contentMaxWidth` | { base, medium?, expanded? } | optional |
|
|
23
|
+
| `elevations` | name → { level?, tint?, shadow?, border? } | optional — a Card wears `card` |
|
|
23
24
|
|
|
24
25
|
## ⚠️ Typography requires `size`, `weight`, AND `line`
|
|
25
26
|
|
|
@@ -86,3 +87,12 @@ Alternative full token sets ship with the CLI as design kits (material, shadcn,
|
|
|
86
87
|
liquid-glass, brutalist, dark-luxe, neumorphic, skeuomorphic, minimal-editorial). Adopt a kit
|
|
87
88
|
by copying its `tokens` into tokens.json; the active kit's app-wide axes
|
|
88
89
|
(material/variant/density/motion) live in the top-level `kit` block.
|
|
90
|
+
|
|
91
|
+
## Elevation
|
|
92
|
+
|
|
93
|
+
`elevations.card` is the depth every `Card` wears — paint only, never a layout change:
|
|
94
|
+
`level` 0–5 is Material 3 tonal elevation (the fill tinted with `tint`, default `accentPrimary`),
|
|
95
|
+
`shadow` names a `shadows` token cast in the Card's own shape, and `border: { color, width? }`
|
|
96
|
+
is a hairline inside the edge (default 0.5). Declare none and Cards stay flat. To look native on
|
|
97
|
+
each platform, declare `"card": {}` and let the idiom overlays fill it: `material` a `level`,
|
|
98
|
+
`ios` a `border` + `shadow`.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wix/himalaya-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.1020.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": [
|
|
@@ -65,5 +65,5 @@
|
|
|
65
65
|
"artifactId": "himalaya-cli"
|
|
66
66
|
}
|
|
67
67
|
},
|
|
68
|
-
"falconPackageHash": "
|
|
68
|
+
"falconPackageHash": "74688239abb5b83cdc592a78513066472b921f1273d54470d9796cab"
|
|
69
69
|
}
|