@invarn/cibuild 2.0.9 → 2.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.cjs +86 -8
- package/dist/src/lib.d.ts +2 -0
- package/dist/src/lib.d.ts.map +1 -1
- package/dist/src/lib.js +2 -0
- package/dist/src/yaml/fidelity-manifest.d.ts +126 -0
- package/dist/src/yaml/fidelity-manifest.d.ts.map +1 -0
- package/dist/src/yaml/fidelity-manifest.js +134 -0
- package/dist/src/yaml/fidelity-manifest.test.d.ts +12 -0
- package/dist/src/yaml/fidelity-manifest.test.d.ts.map +1 -0
- package/dist/src/yaml/fidelity-manifest.test.js +251 -0
- package/dist/src/yaml/steps/fidelity-aspect-guard.d.ts +53 -0
- package/dist/src/yaml/steps/fidelity-aspect-guard.d.ts.map +1 -0
- package/dist/src/yaml/steps/fidelity-aspect-guard.js +72 -0
- package/dist/src/yaml/steps/fidelity-aspect-guard.test.d.ts +11 -0
- package/dist/src/yaml/steps/fidelity-aspect-guard.test.d.ts.map +1 -0
- package/dist/src/yaml/steps/fidelity-aspect-guard.test.js +50 -0
- package/dist/src/yaml/steps/fidelity-reference-provenance.d.ts +45 -0
- package/dist/src/yaml/steps/fidelity-reference-provenance.d.ts.map +1 -0
- package/dist/src/yaml/steps/fidelity-reference-provenance.js +69 -0
- package/dist/src/yaml/steps/fidelity-reference-provenance.test.d.ts +14 -0
- package/dist/src/yaml/steps/fidelity-reference-provenance.test.d.ts.map +1 -0
- package/dist/src/yaml/steps/fidelity-reference-provenance.test.js +54 -0
- package/dist/src/yaml/steps/render-post-processor.d.ts.map +1 -1
- package/dist/src/yaml/steps/render-post-processor.js +19 -6
- package/dist/src/yaml/steps/render-post-processor.test.js +46 -2
- package/dist/src/yaml/steps/ui-fidelity-preview-android.d.ts.map +1 -1
- package/dist/src/yaml/steps/ui-fidelity-preview-android.js +42 -16
- package/dist/src/yaml/steps/ui-fidelity-preview-android.test.js +53 -1
- package/dist/src/yaml/steps/ui-fidelity-preview.d.ts +26 -0
- package/dist/src/yaml/steps/ui-fidelity-preview.d.ts.map +1 -1
- package/dist/src/yaml/steps/ui-fidelity-preview.js +21 -9
- package/dist/src/yaml/steps/ui-fidelity-preview.test.js +31 -1
- package/dist/src/yaml/steps/ui-fidelity-render-android.d.ts +9 -0
- package/dist/src/yaml/steps/ui-fidelity-render-android.d.ts.map +1 -1
- package/dist/src/yaml/steps/ui-fidelity-render-android.js +88 -1
- package/dist/src/yaml/steps/ui-fidelity-render-android.test.js +98 -0
- package/package.json +1 -1
package/dist/src/lib.d.ts
CHANGED
|
@@ -16,6 +16,8 @@ import './yaml/steps/index.js';
|
|
|
16
16
|
export { PipelineRunner, StepExecutionError } from './runner.js';
|
|
17
17
|
export { loadConfig } from './config.js';
|
|
18
18
|
export { loadYAMLPipeline, getWorkflowNames, hasWorkflow, YAMLParseError, YAMLValidationError, } from './yaml/parser.js';
|
|
19
|
+
export { loadFidelityManifest, parseFidelityManifest, buildSurfaceDispatchInputs, FidelityManifestError, } from './yaml/fidelity-manifest.js';
|
|
20
|
+
export type { FidelityManifest, FidelitySurface, FidelityState, StateReference, ReferenceProvenance, BaselineEntry, SurfaceDispatchInputs, } from './yaml/fidelity-manifest.js';
|
|
19
21
|
export { convertYAMLToPipelineDef, getAvailableWorkflows, YAMLConversionError, } from './yaml/converter.js';
|
|
20
22
|
export type { ConversionResult } from './yaml/converter.js';
|
|
21
23
|
export { pipeline } from './pipeline.js';
|
package/dist/src/lib.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lib.d.ts","sourceRoot":"","sources":["../../src/lib.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAIH,OAAO,uBAAuB,CAAC;AAI/B,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAIjE,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAIzC,OAAO,EACL,gBAAgB,EAChB,gBAAgB,EAChB,WAAW,EACX,cAAc,EACd,mBAAmB,GACpB,MAAM,kBAAkB,CAAC;AAI1B,OAAO,EACL,wBAAwB,EACxB,qBAAqB,EACrB,mBAAmB,GACpB,MAAM,qBAAqB,CAAC;AAC7B,YAAY,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAI5D,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAIzC,OAAO,EAAE,uBAAuB,EAAE,MAAM,qCAAqC,CAAC;AAC9E,YAAY,EACV,wBAAwB,EACxB,uBAAuB,EACvB,uBAAuB,EACvB,6BAA6B,EAC7B,2BAA2B,GAC5B,MAAM,qCAAqC,CAAC;AAC7C,OAAO,EACL,8BAA8B,EAC9B,yBAAyB,GAC1B,MAAM,6CAA6C,CAAC;AACrD,YAAY,EAAE,+BAA+B,EAAE,MAAM,6CAA6C,CAAC;AAInG,OAAO,EACL,eAAe,EACf,OAAO,EACP,iBAAiB,EACjB,YAAY,EACZ,eAAe,GAChB,MAAM,0BAA0B,CAAC;AAIlC,YAAY,EACV,OAAO,EACP,WAAW,EACX,QAAQ,EACR,eAAe,EACf,eAAe,EACf,YAAY,GACb,MAAM,YAAY,CAAC;AAIpB,YAAY,EACV,YAAY,EACZ,YAAY,EACZ,YAAY,EACZ,oBAAoB,GACrB,MAAM,sBAAsB,CAAC;AAI9B,YAAY,EACV,YAAY,EACZ,YAAY,EACZ,QAAQ,EACR,cAAc,EACd,UAAU,EACV,OAAO,EACP,QAAQ,EACR,UAAU,EACV,YAAY,EACZ,QAAQ,EACR,WAAW,GACZ,MAAM,iBAAiB,CAAC;AAIzB,OAAO,EAAE,+BAA+B,EAAE,MAAM,wBAAwB,CAAC"}
|
|
1
|
+
{"version":3,"file":"lib.d.ts","sourceRoot":"","sources":["../../src/lib.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAIH,OAAO,uBAAuB,CAAC;AAI/B,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAIjE,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAIzC,OAAO,EACL,gBAAgB,EAChB,gBAAgB,EAChB,WAAW,EACX,cAAc,EACd,mBAAmB,GACpB,MAAM,kBAAkB,CAAC;AAI1B,OAAO,EACL,oBAAoB,EACpB,qBAAqB,EACrB,0BAA0B,EAC1B,qBAAqB,GACtB,MAAM,6BAA6B,CAAC;AACrC,YAAY,EACV,gBAAgB,EAChB,eAAe,EACf,aAAa,EACb,cAAc,EACd,mBAAmB,EACnB,aAAa,EACb,qBAAqB,GACtB,MAAM,6BAA6B,CAAC;AAIrC,OAAO,EACL,wBAAwB,EACxB,qBAAqB,EACrB,mBAAmB,GACpB,MAAM,qBAAqB,CAAC;AAC7B,YAAY,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAI5D,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAIzC,OAAO,EAAE,uBAAuB,EAAE,MAAM,qCAAqC,CAAC;AAC9E,YAAY,EACV,wBAAwB,EACxB,uBAAuB,EACvB,uBAAuB,EACvB,6BAA6B,EAC7B,2BAA2B,GAC5B,MAAM,qCAAqC,CAAC;AAC7C,OAAO,EACL,8BAA8B,EAC9B,yBAAyB,GAC1B,MAAM,6CAA6C,CAAC;AACrD,YAAY,EAAE,+BAA+B,EAAE,MAAM,6CAA6C,CAAC;AAInG,OAAO,EACL,eAAe,EACf,OAAO,EACP,iBAAiB,EACjB,YAAY,EACZ,eAAe,GAChB,MAAM,0BAA0B,CAAC;AAIlC,YAAY,EACV,OAAO,EACP,WAAW,EACX,QAAQ,EACR,eAAe,EACf,eAAe,EACf,YAAY,GACb,MAAM,YAAY,CAAC;AAIpB,YAAY,EACV,YAAY,EACZ,YAAY,EACZ,YAAY,EACZ,oBAAoB,GACrB,MAAM,sBAAsB,CAAC;AAI9B,YAAY,EACV,YAAY,EACZ,YAAY,EACZ,QAAQ,EACR,cAAc,EACd,UAAU,EACV,OAAO,EACP,QAAQ,EACR,UAAU,EACV,YAAY,EACZ,QAAQ,EACR,WAAW,GACZ,MAAM,iBAAiB,CAAC;AAIzB,OAAO,EAAE,+BAA+B,EAAE,MAAM,wBAAwB,CAAC"}
|
package/dist/src/lib.js
CHANGED
|
@@ -21,6 +21,8 @@ export { PipelineRunner, StepExecutionError } from './runner.js';
|
|
|
21
21
|
export { loadConfig } from './config.js';
|
|
22
22
|
// ─── YAML parser ────────────────────────────────────────────────────────
|
|
23
23
|
export { loadYAMLPipeline, getWorkflowNames, hasWorkflow, YAMLParseError, YAMLValidationError, } from './yaml/parser.js';
|
|
24
|
+
// ─── UI fidelity manifest ───────────────────────────────────────────────
|
|
25
|
+
export { loadFidelityManifest, parseFidelityManifest, buildSurfaceDispatchInputs, FidelityManifestError, } from './yaml/fidelity-manifest.js';
|
|
24
26
|
// ─── YAML converter (non-interactive) ───────────────────────────────────
|
|
25
27
|
export { convertYAMLToPipelineDef, getAvailableWorkflows, YAMLConversionError, } from './yaml/converter.js';
|
|
26
28
|
// ─── Pipeline DSL factory ───────────────────────────────────────────────
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Multi-state UI-fidelity manifest parser/validator.
|
|
3
|
+
*
|
|
4
|
+
* The manifest lives at the app-repo root as `.invarn/fidelity.yml` and is the
|
|
5
|
+
* single seam the android-ui-fidelity skill and any orchestrator share. It
|
|
6
|
+
* declares each **Surface** (one app screen) and that surface's first-class
|
|
7
|
+
* **States** (guest vs logged-in; empty vs progress vs complete). Each State is
|
|
8
|
+
* one named proof composable paired with one **Reference**, and the Reference
|
|
9
|
+
* carries export-time **provenance** so the gate can assert validity by
|
|
10
|
+
* metadata rather than by reading pixels.
|
|
11
|
+
*
|
|
12
|
+
* This module owns the schema (the TypeScript types below) and a validator that
|
|
13
|
+
* rejects malformed entries with a clear, distinct message — the shared
|
|
14
|
+
* contract the reference-provenance (UNVERIFIABLE) and baseline-filter slices
|
|
15
|
+
* key on.
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* Export-time provenance stamped onto a Reference. The gate checks this
|
|
19
|
+
* metadata, never the PNG's pixels: a missing/placeholder/wrong-locale
|
|
20
|
+
* reference is caught here instead of by OCR.
|
|
21
|
+
*/
|
|
22
|
+
export interface ReferenceProvenance {
|
|
23
|
+
/** The Figma node the reference was exported from (e.g. "12:345"). */
|
|
24
|
+
figmaNode: string;
|
|
25
|
+
/** The locale the reference was exported in (must match the render locale). */
|
|
26
|
+
locale: string;
|
|
27
|
+
/** False when copy placeholders (`{date}`/`{amount}`) are still unfilled. */
|
|
28
|
+
resolved: boolean;
|
|
29
|
+
/** ISO-8601 export timestamp. */
|
|
30
|
+
exportedAt: string;
|
|
31
|
+
}
|
|
32
|
+
/** A single-state design image plus its provenance. */
|
|
33
|
+
export interface StateReference {
|
|
34
|
+
/** Reference image path (repo-relative), or a Figma node URL. */
|
|
35
|
+
path: string;
|
|
36
|
+
/** Export-time provenance; absence makes the State UNVERIFIABLE. */
|
|
37
|
+
provenance: ReferenceProvenance;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* One allowlisted, product-owned divergence for a State. Keyed on
|
|
41
|
+
* structured/normalized fields only; `actual` is the pinned render value, so a
|
|
42
|
+
* changed value re-flags by construction.
|
|
43
|
+
*/
|
|
44
|
+
export interface BaselineEntry {
|
|
45
|
+
/** Divergence category (e.g. "copy", "geometry") — drives equality. */
|
|
46
|
+
category: string;
|
|
47
|
+
/** Stable element id from layout.json. Unpinnable divergences aren't here. */
|
|
48
|
+
element: string;
|
|
49
|
+
/** The reference/design value. */
|
|
50
|
+
expected: string;
|
|
51
|
+
/** The pinned actual render value — required. */
|
|
52
|
+
actual: string;
|
|
53
|
+
}
|
|
54
|
+
/** One addressable visual State of a Surface. */
|
|
55
|
+
export interface FidelityState {
|
|
56
|
+
/** State name (e.g. "Guest", "LoggedIn"); unique within the surface. */
|
|
57
|
+
name: string;
|
|
58
|
+
/** Named proof composable `<Surface><State>Proof`; 1:1 with a screens key. */
|
|
59
|
+
proof: string;
|
|
60
|
+
/** The single-state reference this State is paired against. */
|
|
61
|
+
reference: StateReference;
|
|
62
|
+
/** Per-state baseline of accepted divergences. */
|
|
63
|
+
accepted?: BaselineEntry[];
|
|
64
|
+
}
|
|
65
|
+
/** One app screen and its first-class States. One proof module per surface. */
|
|
66
|
+
export interface FidelitySurface {
|
|
67
|
+
/** Surface name. */
|
|
68
|
+
name: string;
|
|
69
|
+
/** Render platform (e.g. "android"). */
|
|
70
|
+
platform: string;
|
|
71
|
+
/** Proof module path (one module per surface, N composables inside). */
|
|
72
|
+
module?: string;
|
|
73
|
+
/** The real source files this surface proves. */
|
|
74
|
+
source?: string[];
|
|
75
|
+
/** Globs whose changes imply a re-gate. */
|
|
76
|
+
touchedBy?: string[];
|
|
77
|
+
/** The surface's States. */
|
|
78
|
+
states: FidelityState[];
|
|
79
|
+
}
|
|
80
|
+
/** The parsed `.invarn/fidelity.yml` manifest. */
|
|
81
|
+
export interface FidelityManifest {
|
|
82
|
+
version: number;
|
|
83
|
+
surfaces: FidelitySurface[];
|
|
84
|
+
}
|
|
85
|
+
/** Thrown when a manifest is absent, unparseable, or schema-invalid. */
|
|
86
|
+
export declare class FidelityManifestError extends Error {
|
|
87
|
+
readonly filePath?: string | undefined;
|
|
88
|
+
constructor(message: string, filePath?: string | undefined);
|
|
89
|
+
}
|
|
90
|
+
/** Reads and parses a manifest from disk. */
|
|
91
|
+
export declare function loadFidelityManifest(filePath: string): FidelityManifest;
|
|
92
|
+
/** Parses and validates manifest YAML text. */
|
|
93
|
+
export declare function parseFidelityManifest(text: string, filePath?: string): FidelityManifest;
|
|
94
|
+
/**
|
|
95
|
+
* The per-dispatch inputs for one surface, derived from its manifest entry.
|
|
96
|
+
* Shapes the `params` sidecar the render runtime and preflight already consume
|
|
97
|
+
* (`screens` + `render_locale` + `provenance`), the reference files to upload,
|
|
98
|
+
* and the per-proof `accepted[]` baseline the post-judgment filter reads.
|
|
99
|
+
*/
|
|
100
|
+
export interface SurfaceDispatchInputs {
|
|
101
|
+
/** params.screens: proof composable name → reference basename. */
|
|
102
|
+
screens: Record<string, string>;
|
|
103
|
+
/** params.render_locale: the single locale every reference is checked against. */
|
|
104
|
+
render_locale: string;
|
|
105
|
+
/** params.provenance: proof composable name → its reference provenance. */
|
|
106
|
+
provenance: Record<string, ReferenceProvenance>;
|
|
107
|
+
/** Reference files to upload, one per state, addressed by repo path. */
|
|
108
|
+
references: {
|
|
109
|
+
proof: string;
|
|
110
|
+
path: string;
|
|
111
|
+
}[];
|
|
112
|
+
/** Per-proof accepted[] baseline (only states that declare one). */
|
|
113
|
+
accepted: Record<string, BaselineEntry[]>;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Build the per-dispatch inputs for one surface: one run carries all of the
|
|
117
|
+
* surface's states, each paired with its own reference and provenance. The
|
|
118
|
+
* render locale defaults to the references' shared locale (they must agree);
|
|
119
|
+
* pass `renderLocale` to override (a deliberate mismatch then surfaces as
|
|
120
|
+
* UNVERIFIABLE at the gate). Reference basenames must be unique, since the
|
|
121
|
+
* upload layer addresses files by basename.
|
|
122
|
+
*/
|
|
123
|
+
export declare function buildSurfaceDispatchInputs(surface: FidelitySurface, opts?: {
|
|
124
|
+
renderLocale?: string;
|
|
125
|
+
}): SurfaceDispatchInputs;
|
|
126
|
+
//# sourceMappingURL=fidelity-manifest.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fidelity-manifest.d.ts","sourceRoot":"","sources":["../../../src/yaml/fidelity-manifest.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAKH;;;;GAIG;AACH,MAAM,WAAW,mBAAmB;IAClC,sEAAsE;IACtE,SAAS,EAAE,MAAM,CAAC;IAClB,+EAA+E;IAC/E,MAAM,EAAE,MAAM,CAAC;IACf,6EAA6E;IAC7E,QAAQ,EAAE,OAAO,CAAC;IAClB,iCAAiC;IACjC,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,uDAAuD;AACvD,MAAM,WAAW,cAAc;IAC7B,iEAAiE;IACjE,IAAI,EAAE,MAAM,CAAC;IACb,oEAAoE;IACpE,UAAU,EAAE,mBAAmB,CAAC;CACjC;AAED;;;;GAIG;AACH,MAAM,WAAW,aAAa;IAC5B,uEAAuE;IACvE,QAAQ,EAAE,MAAM,CAAC;IACjB,8EAA8E;IAC9E,OAAO,EAAE,MAAM,CAAC;IAChB,kCAAkC;IAClC,QAAQ,EAAE,MAAM,CAAC;IACjB,iDAAiD;IACjD,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,iDAAiD;AACjD,MAAM,WAAW,aAAa;IAC5B,wEAAwE;IACxE,IAAI,EAAE,MAAM,CAAC;IACb,8EAA8E;IAC9E,KAAK,EAAE,MAAM,CAAC;IACd,+DAA+D;IAC/D,SAAS,EAAE,cAAc,CAAC;IAC1B,kDAAkD;IAClD,QAAQ,CAAC,EAAE,aAAa,EAAE,CAAC;CAC5B;AAED,+EAA+E;AAC/E,MAAM,WAAW,eAAe;IAC9B,oBAAoB;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,wCAAwC;IACxC,QAAQ,EAAE,MAAM,CAAC;IACjB,wEAAwE;IACxE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,iDAAiD;IACjD,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,2CAA2C;IAC3C,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,4BAA4B;IAC5B,MAAM,EAAE,aAAa,EAAE,CAAC;CACzB;AAED,kDAAkD;AAClD,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,eAAe,EAAE,CAAC;CAC7B;AAED,wEAAwE;AACxE,qBAAa,qBAAsB,SAAQ,KAAK;aACD,QAAQ,CAAC,EAAE,MAAM;gBAAlD,OAAO,EAAE,MAAM,EAAkB,QAAQ,CAAC,EAAE,MAAM,YAAA;CAI/D;AAED,6CAA6C;AAC7C,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,GAAG,gBAAgB,CAQvE;AAED,+CAA+C;AAC/C,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,MAAM,EACZ,QAAQ,CAAC,EAAE,MAAM,GAChB,gBAAgB,CAoBlB;AAwDD;;;;;GAKG;AACH,MAAM,WAAW,qBAAqB;IACpC,kEAAkE;IAClE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,kFAAkF;IAClF,aAAa,EAAE,MAAM,CAAC;IACtB,2EAA2E;IAC3E,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC;IAChD,wEAAwE;IACxE,UAAU,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC9C,oEAAoE;IACpE,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,EAAE,CAAC,CAAC;CAC3C;AAQD;;;;;;;GAOG;AACH,wBAAgB,0BAA0B,CACxC,OAAO,EAAE,eAAe,EACxB,IAAI,GAAE;IAAE,YAAY,CAAC,EAAE,MAAM,CAAA;CAAO,GACnC,qBAAqB,CA+BvB"}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Multi-state UI-fidelity manifest parser/validator.
|
|
3
|
+
*
|
|
4
|
+
* The manifest lives at the app-repo root as `.invarn/fidelity.yml` and is the
|
|
5
|
+
* single seam the android-ui-fidelity skill and any orchestrator share. It
|
|
6
|
+
* declares each **Surface** (one app screen) and that surface's first-class
|
|
7
|
+
* **States** (guest vs logged-in; empty vs progress vs complete). Each State is
|
|
8
|
+
* one named proof composable paired with one **Reference**, and the Reference
|
|
9
|
+
* carries export-time **provenance** so the gate can assert validity by
|
|
10
|
+
* metadata rather than by reading pixels.
|
|
11
|
+
*
|
|
12
|
+
* This module owns the schema (the TypeScript types below) and a validator that
|
|
13
|
+
* rejects malformed entries with a clear, distinct message — the shared
|
|
14
|
+
* contract the reference-provenance (UNVERIFIABLE) and baseline-filter slices
|
|
15
|
+
* key on.
|
|
16
|
+
*/
|
|
17
|
+
import * as yaml from 'js-yaml';
|
|
18
|
+
import { readFileSync, existsSync } from 'fs';
|
|
19
|
+
/** Thrown when a manifest is absent, unparseable, or schema-invalid. */
|
|
20
|
+
export class FidelityManifestError extends Error {
|
|
21
|
+
filePath;
|
|
22
|
+
constructor(message, filePath) {
|
|
23
|
+
super(message);
|
|
24
|
+
this.filePath = filePath;
|
|
25
|
+
this.name = 'FidelityManifestError';
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/** Reads and parses a manifest from disk. */
|
|
29
|
+
export function loadFidelityManifest(filePath) {
|
|
30
|
+
if (!existsSync(filePath)) {
|
|
31
|
+
throw new FidelityManifestError(`Fidelity manifest not found: ${filePath}`, filePath);
|
|
32
|
+
}
|
|
33
|
+
return parseFidelityManifest(readFileSync(filePath, 'utf-8'), filePath);
|
|
34
|
+
}
|
|
35
|
+
/** Parses and validates manifest YAML text. */
|
|
36
|
+
export function parseFidelityManifest(text, filePath) {
|
|
37
|
+
let doc;
|
|
38
|
+
try {
|
|
39
|
+
doc = yaml.load(text);
|
|
40
|
+
}
|
|
41
|
+
catch (err) {
|
|
42
|
+
throw new FidelityManifestError(`Invalid YAML in fidelity manifest: ${err.message}`, filePath);
|
|
43
|
+
}
|
|
44
|
+
const root = doc;
|
|
45
|
+
const surfaces = root.surfaces ?? [];
|
|
46
|
+
validateSurfaces(surfaces, filePath);
|
|
47
|
+
return {
|
|
48
|
+
version: Number(root.version),
|
|
49
|
+
surfaces,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
/** The provenance fields every Reference must carry to be verifiable. */
|
|
53
|
+
const REQUIRED_PROVENANCE_FIELDS = [
|
|
54
|
+
'figmaNode',
|
|
55
|
+
'locale',
|
|
56
|
+
'resolved',
|
|
57
|
+
'exportedAt',
|
|
58
|
+
];
|
|
59
|
+
function validateSurfaces(surfaces, filePath) {
|
|
60
|
+
for (const surface of surfaces) {
|
|
61
|
+
const seenStateNames = new Set();
|
|
62
|
+
for (const state of surface.states ?? []) {
|
|
63
|
+
if (seenStateNames.has(state.name)) {
|
|
64
|
+
throw new FidelityManifestError(`surface "${surface.name}": duplicate state name "${state.name}" — state names must be unique within a surface.`, filePath);
|
|
65
|
+
}
|
|
66
|
+
seenStateNames.add(state.name);
|
|
67
|
+
const where = `surface "${surface.name}" state "${state.name}"`;
|
|
68
|
+
const provenance = state.reference?.provenance;
|
|
69
|
+
for (const field of REQUIRED_PROVENANCE_FIELDS) {
|
|
70
|
+
if (provenance == null || provenance[field] === undefined) {
|
|
71
|
+
throw new FidelityManifestError(`${where}: reference provenance is missing required field "${field}".`, filePath);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
for (const entry of state.accepted ?? []) {
|
|
75
|
+
if (entry.actual === undefined ||
|
|
76
|
+
entry.actual === null ||
|
|
77
|
+
entry.actual === '') {
|
|
78
|
+
throw new FidelityManifestError(`${where}: accepted[] entry for element "${entry.element}" is missing a pinned "actual" value.`, filePath);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
/** POSIX basename of a repo-relative path (manifest paths are POSIX). */
|
|
85
|
+
function posixBasename(p) {
|
|
86
|
+
const parts = p.split('/');
|
|
87
|
+
return parts[parts.length - 1];
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Build the per-dispatch inputs for one surface: one run carries all of the
|
|
91
|
+
* surface's states, each paired with its own reference and provenance. The
|
|
92
|
+
* render locale defaults to the references' shared locale (they must agree);
|
|
93
|
+
* pass `renderLocale` to override (a deliberate mismatch then surfaces as
|
|
94
|
+
* UNVERIFIABLE at the gate). Reference basenames must be unique, since the
|
|
95
|
+
* upload layer addresses files by basename.
|
|
96
|
+
*/
|
|
97
|
+
export function buildSurfaceDispatchInputs(surface, opts = {}) {
|
|
98
|
+
const screens = {};
|
|
99
|
+
const provenance = {};
|
|
100
|
+
const references = [];
|
|
101
|
+
const accepted = {};
|
|
102
|
+
const basenameToPath = new Map();
|
|
103
|
+
for (const state of surface.states ?? []) {
|
|
104
|
+
const path = state.reference.path;
|
|
105
|
+
const base = posixBasename(path);
|
|
106
|
+
const existing = basenameToPath.get(base);
|
|
107
|
+
if (existing !== undefined && existing !== path) {
|
|
108
|
+
throw new FidelityManifestError(`surface "${surface.name}": states resolve to the same reference basename "${base}" ` +
|
|
109
|
+
`(${existing} vs ${path}); references are uploaded by basename, so they must be unique.`);
|
|
110
|
+
}
|
|
111
|
+
basenameToPath.set(base, path);
|
|
112
|
+
screens[state.proof] = base;
|
|
113
|
+
provenance[state.proof] = state.reference.provenance;
|
|
114
|
+
references.push({ proof: state.proof, path });
|
|
115
|
+
if (state.accepted && state.accepted.length > 0) {
|
|
116
|
+
accepted[state.proof] = state.accepted;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
const renderLocale = opts.renderLocale ?? deriveRenderLocale(surface);
|
|
120
|
+
return { screens, render_locale: renderLocale, provenance, references, accepted };
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* The render locale a surface's references share. Throws when they disagree and
|
|
124
|
+
* no explicit locale was given — a coherent surface renders in one locale.
|
|
125
|
+
*/
|
|
126
|
+
function deriveRenderLocale(surface) {
|
|
127
|
+
const locales = new Set((surface.states ?? []).map((s) => s.reference.provenance.locale));
|
|
128
|
+
if (locales.size > 1) {
|
|
129
|
+
throw new FidelityManifestError(`surface "${surface.name}": references span multiple locales ` +
|
|
130
|
+
`(${[...locales].join(', ')}); pass an explicit renderLocale to disambiguate.`);
|
|
131
|
+
}
|
|
132
|
+
return locales.values().next().value ?? '';
|
|
133
|
+
}
|
|
134
|
+
//# sourceMappingURL=fidelity-manifest.js.map
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for the multi-state UI-fidelity manifest parser/validator.
|
|
3
|
+
*
|
|
4
|
+
* The manifest (`.invarn/fidelity.yml`) declares each Surface's States
|
|
5
|
+
* first-class: one named proof composable per State, each paired with its own
|
|
6
|
+
* Reference (carrying export-time provenance), plus a per-State `accepted[]`
|
|
7
|
+
* baseline of allowlisted divergences. These tests pin the schema and the
|
|
8
|
+
* validator's distinct rejections; fixtures are written to a temp dir and
|
|
9
|
+
* loaded by path, mirroring the YAML pipeline parser tests.
|
|
10
|
+
*/
|
|
11
|
+
export {};
|
|
12
|
+
//# sourceMappingURL=fidelity-manifest.test.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fidelity-manifest.test.d.ts","sourceRoot":"","sources":["../../../src/yaml/fidelity-manifest.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG"}
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for the multi-state UI-fidelity manifest parser/validator.
|
|
3
|
+
*
|
|
4
|
+
* The manifest (`.invarn/fidelity.yml`) declares each Surface's States
|
|
5
|
+
* first-class: one named proof composable per State, each paired with its own
|
|
6
|
+
* Reference (carrying export-time provenance), plus a per-State `accepted[]`
|
|
7
|
+
* baseline of allowlisted divergences. These tests pin the schema and the
|
|
8
|
+
* validator's distinct rejections; fixtures are written to a temp dir and
|
|
9
|
+
* loaded by path, mirroring the YAML pipeline parser tests.
|
|
10
|
+
*/
|
|
11
|
+
import { describe, test, expect, beforeEach, afterEach } from '@jest/globals';
|
|
12
|
+
import { writeFileSync, mkdtempSync, rmSync } from 'fs';
|
|
13
|
+
import { tmpdir } from 'os';
|
|
14
|
+
import { join } from 'path';
|
|
15
|
+
import { loadFidelityManifest, parseFidelityManifest, buildSurfaceDispatchInputs, FidelityManifestError, } from './fidelity-manifest.js';
|
|
16
|
+
describe('fidelity manifest — parser/validator', () => {
|
|
17
|
+
let workDir;
|
|
18
|
+
beforeEach(() => {
|
|
19
|
+
workDir = mkdtempSync(join(tmpdir(), 'cibuild-fidelity-manifest-'));
|
|
20
|
+
});
|
|
21
|
+
afterEach(() => {
|
|
22
|
+
rmSync(workDir, { recursive: true, force: true });
|
|
23
|
+
});
|
|
24
|
+
function writeManifest(content) {
|
|
25
|
+
const path = join(workDir, 'fidelity.yml');
|
|
26
|
+
writeFileSync(path, content, 'utf-8');
|
|
27
|
+
return path;
|
|
28
|
+
}
|
|
29
|
+
// A well-formed two-state surface modelling the canonical promo banner
|
|
30
|
+
// (guest + logged-in), with provenance on each reference and one accepted
|
|
31
|
+
// baseline entry on the logged-in state.
|
|
32
|
+
const TWO_STATE_BANNER = `version: 1
|
|
33
|
+
surfaces:
|
|
34
|
+
- name: PromoBanner
|
|
35
|
+
platform: android
|
|
36
|
+
module: fidelity/promo-banner
|
|
37
|
+
source:
|
|
38
|
+
- feature/hub/**/PromoBanner.kt
|
|
39
|
+
touchedBy:
|
|
40
|
+
- feature/hub/**/PromoBanner*.kt
|
|
41
|
+
states:
|
|
42
|
+
- name: Guest
|
|
43
|
+
proof: PromoBannerGuestProof
|
|
44
|
+
reference:
|
|
45
|
+
path: fidelity/refs/promo_banner_guest.png
|
|
46
|
+
provenance:
|
|
47
|
+
figmaNode: "12:345"
|
|
48
|
+
locale: en
|
|
49
|
+
resolved: true
|
|
50
|
+
exportedAt: "2026-06-18T10:00:00Z"
|
|
51
|
+
- name: LoggedIn
|
|
52
|
+
proof: PromoBannerLoggedInProof
|
|
53
|
+
reference:
|
|
54
|
+
path: fidelity/refs/promo_banner_logged_in.png
|
|
55
|
+
provenance:
|
|
56
|
+
figmaNode: "12:346"
|
|
57
|
+
locale: en
|
|
58
|
+
resolved: true
|
|
59
|
+
exportedAt: "2026-06-18T10:00:00Z"
|
|
60
|
+
accepted:
|
|
61
|
+
- category: copy
|
|
62
|
+
element: banner_subtitle
|
|
63
|
+
expected: "Get 5 off when you spend 20,00"
|
|
64
|
+
actual: "Get 5 off your first order placed by {date}"
|
|
65
|
+
`;
|
|
66
|
+
test('parses a well-formed two-state surface into addressable states', () => {
|
|
67
|
+
const manifest = loadFidelityManifest(writeManifest(TWO_STATE_BANNER));
|
|
68
|
+
expect(manifest.version).toBe(1);
|
|
69
|
+
expect(manifest.surfaces).toHaveLength(1);
|
|
70
|
+
const surface = manifest.surfaces[0];
|
|
71
|
+
expect(surface.name).toBe('PromoBanner');
|
|
72
|
+
expect(surface.platform).toBe('android');
|
|
73
|
+
expect(surface.states.map((s) => s.name)).toEqual(['Guest', 'LoggedIn']);
|
|
74
|
+
const [guest, loggedIn] = surface.states;
|
|
75
|
+
expect(guest.proof).toBe('PromoBannerGuestProof');
|
|
76
|
+
expect(guest.reference.path).toBe('fidelity/refs/promo_banner_guest.png');
|
|
77
|
+
expect(guest.reference.provenance).toEqual({
|
|
78
|
+
figmaNode: '12:345',
|
|
79
|
+
locale: 'en',
|
|
80
|
+
resolved: true,
|
|
81
|
+
exportedAt: '2026-06-18T10:00:00Z',
|
|
82
|
+
});
|
|
83
|
+
expect(loggedIn.proof).toBe('PromoBannerLoggedInProof');
|
|
84
|
+
expect(loggedIn.accepted).toEqual([
|
|
85
|
+
{
|
|
86
|
+
category: 'copy',
|
|
87
|
+
element: 'banner_subtitle',
|
|
88
|
+
expected: 'Get 5 off when you spend 20,00',
|
|
89
|
+
actual: 'Get 5 off your first order placed by {date}',
|
|
90
|
+
},
|
|
91
|
+
]);
|
|
92
|
+
});
|
|
93
|
+
test('rejects a reference missing a provenance field', () => {
|
|
94
|
+
// `exportedAt` omitted from the guest reference's provenance.
|
|
95
|
+
const yaml = `version: 1
|
|
96
|
+
surfaces:
|
|
97
|
+
- name: PromoBanner
|
|
98
|
+
platform: android
|
|
99
|
+
states:
|
|
100
|
+
- name: Guest
|
|
101
|
+
proof: PromoBannerGuestProof
|
|
102
|
+
reference:
|
|
103
|
+
path: fidelity/refs/promo_banner_guest.png
|
|
104
|
+
provenance:
|
|
105
|
+
figmaNode: "12:345"
|
|
106
|
+
locale: en
|
|
107
|
+
resolved: true
|
|
108
|
+
`;
|
|
109
|
+
expect(() => loadFidelityManifest(writeManifest(yaml))).toThrow(FidelityManifestError);
|
|
110
|
+
expect(() => loadFidelityManifest(writeManifest(yaml))).toThrow(/provenance.*exportedAt|exportedAt.*provenance/i);
|
|
111
|
+
});
|
|
112
|
+
test('rejects an accepted[] entry with no pinned actual', () => {
|
|
113
|
+
// The baseline entry omits `actual` — it cannot pin the render value, so a
|
|
114
|
+
// changed value could never re-flag. Reject loud.
|
|
115
|
+
const yaml = `version: 1
|
|
116
|
+
surfaces:
|
|
117
|
+
- name: PromoBanner
|
|
118
|
+
platform: android
|
|
119
|
+
states:
|
|
120
|
+
- name: LoggedIn
|
|
121
|
+
proof: PromoBannerLoggedInProof
|
|
122
|
+
reference:
|
|
123
|
+
path: fidelity/refs/promo_banner_logged_in.png
|
|
124
|
+
provenance:
|
|
125
|
+
figmaNode: "12:346"
|
|
126
|
+
locale: en
|
|
127
|
+
resolved: true
|
|
128
|
+
exportedAt: "2026-06-18T10:00:00Z"
|
|
129
|
+
accepted:
|
|
130
|
+
- category: copy
|
|
131
|
+
element: banner_subtitle
|
|
132
|
+
expected: "Get 5 off when you spend 20,00"
|
|
133
|
+
`;
|
|
134
|
+
expect(() => loadFidelityManifest(writeManifest(yaml))).toThrow(FidelityManifestError);
|
|
135
|
+
expect(() => loadFidelityManifest(writeManifest(yaml))).toThrow(/accepted.*actual|actual/i);
|
|
136
|
+
});
|
|
137
|
+
test('rejects duplicate state names within a surface', () => {
|
|
138
|
+
// Two states both named "Guest" — their proofs would collide on one
|
|
139
|
+
// params.screens key.
|
|
140
|
+
const yaml = `version: 1
|
|
141
|
+
surfaces:
|
|
142
|
+
- name: PromoBanner
|
|
143
|
+
platform: android
|
|
144
|
+
states:
|
|
145
|
+
- name: Guest
|
|
146
|
+
proof: PromoBannerGuestProof
|
|
147
|
+
reference:
|
|
148
|
+
path: fidelity/refs/promo_banner_guest.png
|
|
149
|
+
provenance:
|
|
150
|
+
figmaNode: "12:345"
|
|
151
|
+
locale: en
|
|
152
|
+
resolved: true
|
|
153
|
+
exportedAt: "2026-06-18T10:00:00Z"
|
|
154
|
+
- name: Guest
|
|
155
|
+
proof: PromoBannerGuestAltProof
|
|
156
|
+
reference:
|
|
157
|
+
path: fidelity/refs/promo_banner_guest_alt.png
|
|
158
|
+
provenance:
|
|
159
|
+
figmaNode: "12:347"
|
|
160
|
+
locale: en
|
|
161
|
+
resolved: true
|
|
162
|
+
exportedAt: "2026-06-18T10:00:00Z"
|
|
163
|
+
`;
|
|
164
|
+
expect(() => loadFidelityManifest(writeManifest(yaml))).toThrow(FidelityManifestError);
|
|
165
|
+
expect(() => loadFidelityManifest(writeManifest(yaml))).toThrow(/duplicate state.*Guest|state name "Guest"/i);
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
describe('buildSurfaceDispatchInputs', () => {
|
|
169
|
+
const BANNER = `version: 1
|
|
170
|
+
surfaces:
|
|
171
|
+
- name: PromoBanner
|
|
172
|
+
platform: android
|
|
173
|
+
module: fidelity/promo-banner
|
|
174
|
+
states:
|
|
175
|
+
- name: Guest
|
|
176
|
+
proof: PromoBannerGuestProof
|
|
177
|
+
reference:
|
|
178
|
+
path: fidelity/refs/promo_banner_guest.png
|
|
179
|
+
provenance:
|
|
180
|
+
figmaNode: "12:345"
|
|
181
|
+
locale: en
|
|
182
|
+
resolved: true
|
|
183
|
+
exportedAt: "2026-06-18T10:00:00Z"
|
|
184
|
+
- name: LoggedIn
|
|
185
|
+
proof: PromoBannerLoggedInProof
|
|
186
|
+
reference:
|
|
187
|
+
path: fidelity/refs/promo_banner_logged_in.png
|
|
188
|
+
provenance:
|
|
189
|
+
figmaNode: "12:346"
|
|
190
|
+
locale: en
|
|
191
|
+
resolved: true
|
|
192
|
+
exportedAt: "2026-06-18T10:00:00Z"
|
|
193
|
+
accepted:
|
|
194
|
+
- category: copy
|
|
195
|
+
element: banner_subtitle
|
|
196
|
+
expected: "A"
|
|
197
|
+
actual: "B"
|
|
198
|
+
`;
|
|
199
|
+
function bannerSurface(extra = '') {
|
|
200
|
+
return parseFidelityManifest(BANNER + extra).surfaces[0];
|
|
201
|
+
}
|
|
202
|
+
test('maps each state proof to its reference basename in screens', () => {
|
|
203
|
+
const inputs = buildSurfaceDispatchInputs(bannerSurface());
|
|
204
|
+
expect(inputs.screens).toEqual({
|
|
205
|
+
PromoBannerGuestProof: 'promo_banner_guest.png',
|
|
206
|
+
PromoBannerLoggedInProof: 'promo_banner_logged_in.png',
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
test('keys provenance by proof and derives the render locale from the references', () => {
|
|
210
|
+
const inputs = buildSurfaceDispatchInputs(bannerSurface());
|
|
211
|
+
expect(inputs.render_locale).toBe('en');
|
|
212
|
+
expect(inputs.provenance.PromoBannerGuestProof).toEqual({
|
|
213
|
+
figmaNode: '12:345',
|
|
214
|
+
locale: 'en',
|
|
215
|
+
resolved: true,
|
|
216
|
+
exportedAt: '2026-06-18T10:00:00Z',
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
test('lists the reference files to upload, one per state, by repo path', () => {
|
|
220
|
+
const inputs = buildSurfaceDispatchInputs(bannerSurface());
|
|
221
|
+
expect(inputs.references).toEqual([
|
|
222
|
+
{ proof: 'PromoBannerGuestProof', path: 'fidelity/refs/promo_banner_guest.png' },
|
|
223
|
+
{ proof: 'PromoBannerLoggedInProof', path: 'fidelity/refs/promo_banner_logged_in.png' },
|
|
224
|
+
]);
|
|
225
|
+
});
|
|
226
|
+
test('carries each state baseline keyed by proof for the post-judgment filter', () => {
|
|
227
|
+
const inputs = buildSurfaceDispatchInputs(bannerSurface());
|
|
228
|
+
expect(inputs.accepted.PromoBannerLoggedInProof).toEqual([
|
|
229
|
+
{ category: 'copy', element: 'banner_subtitle', expected: 'A', actual: 'B' },
|
|
230
|
+
]);
|
|
231
|
+
// States with no accepted[] don't appear.
|
|
232
|
+
expect(inputs.accepted.PromoBannerGuestProof).toBeUndefined();
|
|
233
|
+
});
|
|
234
|
+
test('honours an explicit render locale override', () => {
|
|
235
|
+
const inputs = buildSurfaceDispatchInputs(bannerSurface(), { renderLocale: 'it' });
|
|
236
|
+
expect(inputs.render_locale).toBe('it');
|
|
237
|
+
});
|
|
238
|
+
test('throws when references disagree on locale and none is given', () => {
|
|
239
|
+
// Flip the logged-in reference to Italian.
|
|
240
|
+
const surface = bannerSurface();
|
|
241
|
+
surface.states[1].reference.provenance.locale = 'it';
|
|
242
|
+
expect(() => buildSurfaceDispatchInputs(surface)).toThrow(/locale/i);
|
|
243
|
+
});
|
|
244
|
+
test('throws when two states resolve to the same reference basename', () => {
|
|
245
|
+
const surface = bannerSurface();
|
|
246
|
+
// Same basename, different dirs → upload-layer collision.
|
|
247
|
+
surface.states[1].reference.path = 'other/promo_banner_guest.png';
|
|
248
|
+
expect(() => buildSurfaceDispatchInputs(surface)).toThrow(/basename/i);
|
|
249
|
+
});
|
|
250
|
+
});
|
|
251
|
+
//# sourceMappingURL=fidelity-manifest.test.js.map
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fidelity aspect-ratio guard.
|
|
3
|
+
*
|
|
4
|
+
* One named composable per state is the authoring norm; the failure this
|
|
5
|
+
* catches is the opposite — several states stacked into one composable, which
|
|
6
|
+
* renders far taller (or wider) than the single-state reference it is paired
|
|
7
|
+
* against. That shows up as a sharp divergence between the rendered and
|
|
8
|
+
* reference aspect ratios, well before a human reads the diff.
|
|
9
|
+
*
|
|
10
|
+
* The guard is a pure comparison over the dimensions the render already
|
|
11
|
+
* reports (`rendered_px`, `reference_px`). It emits a WARNING when the
|
|
12
|
+
* divergence exceeds a documented fold factor — it never fails the render, and
|
|
13
|
+
* it stays silent when there is no reference to compare against.
|
|
14
|
+
*/
|
|
15
|
+
/** Pixel dimensions as `[width, height]`. */
|
|
16
|
+
export type PixelSize = [number, number];
|
|
17
|
+
/**
|
|
18
|
+
* Fold factor beyond which a rendered/reference aspect-ratio divergence is
|
|
19
|
+
* worth a warning. A faithful single-state wrapper sits near 1.0; trim and
|
|
20
|
+
* reference alignment nudge it a few percent; a re-stacked 2-up wrapper roughly
|
|
21
|
+
* doubles one dimension and lands near 2.0. 1.5 catches the stack while
|
|
22
|
+
* tolerating the wobble.
|
|
23
|
+
*/
|
|
24
|
+
export declare const ASPECT_RATIO_WARN_FACTOR = 1.5;
|
|
25
|
+
/** The outcome of comparing a rendered aspect ratio to its reference. */
|
|
26
|
+
export interface AspectRatioCheck {
|
|
27
|
+
/** True only when both sizes are present and strictly positive. */
|
|
28
|
+
comparable: boolean;
|
|
29
|
+
/** Rendered width/height, or null when not comparable. */
|
|
30
|
+
renderedAspect: number | null;
|
|
31
|
+
/** Reference width/height, or null when not comparable. */
|
|
32
|
+
referenceAspect: number | null;
|
|
33
|
+
/**
|
|
34
|
+
* Symmetric divergence `max(r/ref, ref/r)` (≥ 1), or null when not
|
|
35
|
+
* comparable. 1.0 means identical proportions.
|
|
36
|
+
*/
|
|
37
|
+
divergence: number | null;
|
|
38
|
+
/** True when comparable and the divergence exceeds the fold factor. */
|
|
39
|
+
warn: boolean;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Compares a rendered size against its reference size. Returns a
|
|
43
|
+
* non-warning, non-comparable result when either size is missing or
|
|
44
|
+
* non-positive — the guard only speaks when it has two real frames to compare.
|
|
45
|
+
*/
|
|
46
|
+
export declare function evaluateAspectRatio(renderedPx: PixelSize | null, referencePx: PixelSize | null, factor?: number): AspectRatioCheck;
|
|
47
|
+
/**
|
|
48
|
+
* Human/agent-readable warning line for a divergent screen, or null when the
|
|
49
|
+
* screen does not warrant one. The caller decides where to surface it (preview
|
|
50
|
+
* result, preflight output) — it is advisory, never a failure.
|
|
51
|
+
*/
|
|
52
|
+
export declare function formatAspectRatioWarning(screen: string, check: AspectRatioCheck): string | null;
|
|
53
|
+
//# sourceMappingURL=fidelity-aspect-guard.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fidelity-aspect-guard.d.ts","sourceRoot":"","sources":["../../../../src/yaml/steps/fidelity-aspect-guard.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,6CAA6C;AAC7C,MAAM,MAAM,SAAS,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAEzC;;;;;;GAMG;AACH,eAAO,MAAM,wBAAwB,MAAM,CAAC;AAE5C,yEAAyE;AACzE,MAAM,WAAW,gBAAgB;IAC/B,mEAAmE;IACnE,UAAU,EAAE,OAAO,CAAC;IACpB,0DAA0D;IAC1D,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,2DAA2D;IAC3D,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B;;;OAGG;IACH,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,uEAAuE;IACvE,IAAI,EAAE,OAAO,CAAC;CACf;AAaD;;;;GAIG;AACH,wBAAgB,mBAAmB,CACjC,UAAU,EAAE,SAAS,GAAG,IAAI,EAC5B,WAAW,EAAE,SAAS,GAAG,IAAI,EAC7B,MAAM,GAAE,MAAiC,GACxC,gBAAgB,CAyBlB;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,gBAAgB,GACtB,MAAM,GAAG,IAAI,CAUf"}
|