@invarn/cibuild 2.1.0 → 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.
Files changed (33) hide show
  1. package/dist/cli.cjs +86 -8
  2. package/dist/src/lib.d.ts +2 -0
  3. package/dist/src/lib.d.ts.map +1 -1
  4. package/dist/src/lib.js +2 -0
  5. package/dist/src/yaml/fidelity-manifest.d.ts +126 -0
  6. package/dist/src/yaml/fidelity-manifest.d.ts.map +1 -0
  7. package/dist/src/yaml/fidelity-manifest.js +134 -0
  8. package/dist/src/yaml/fidelity-manifest.test.d.ts +12 -0
  9. package/dist/src/yaml/fidelity-manifest.test.d.ts.map +1 -0
  10. package/dist/src/yaml/fidelity-manifest.test.js +251 -0
  11. package/dist/src/yaml/steps/fidelity-aspect-guard.d.ts +53 -0
  12. package/dist/src/yaml/steps/fidelity-aspect-guard.d.ts.map +1 -0
  13. package/dist/src/yaml/steps/fidelity-aspect-guard.js +72 -0
  14. package/dist/src/yaml/steps/fidelity-aspect-guard.test.d.ts +11 -0
  15. package/dist/src/yaml/steps/fidelity-aspect-guard.test.d.ts.map +1 -0
  16. package/dist/src/yaml/steps/fidelity-aspect-guard.test.js +50 -0
  17. package/dist/src/yaml/steps/fidelity-reference-provenance.d.ts +45 -0
  18. package/dist/src/yaml/steps/fidelity-reference-provenance.d.ts.map +1 -0
  19. package/dist/src/yaml/steps/fidelity-reference-provenance.js +69 -0
  20. package/dist/src/yaml/steps/fidelity-reference-provenance.test.d.ts +14 -0
  21. package/dist/src/yaml/steps/fidelity-reference-provenance.test.d.ts.map +1 -0
  22. package/dist/src/yaml/steps/fidelity-reference-provenance.test.js +54 -0
  23. package/dist/src/yaml/steps/ui-fidelity-preview-android.d.ts.map +1 -1
  24. package/dist/src/yaml/steps/ui-fidelity-preview-android.js +2 -9
  25. package/dist/src/yaml/steps/ui-fidelity-preview.d.ts +26 -0
  26. package/dist/src/yaml/steps/ui-fidelity-preview.d.ts.map +1 -1
  27. package/dist/src/yaml/steps/ui-fidelity-preview.js +21 -9
  28. package/dist/src/yaml/steps/ui-fidelity-preview.test.js +31 -1
  29. package/dist/src/yaml/steps/ui-fidelity-render-android.d.ts +9 -0
  30. package/dist/src/yaml/steps/ui-fidelity-render-android.d.ts.map +1 -1
  31. package/dist/src/yaml/steps/ui-fidelity-render-android.js +88 -1
  32. package/dist/src/yaml/steps/ui-fidelity-render-android.test.js +98 -0
  33. package/package.json +1 -1
@@ -0,0 +1,72 @@
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
+ /**
16
+ * Fold factor beyond which a rendered/reference aspect-ratio divergence is
17
+ * worth a warning. A faithful single-state wrapper sits near 1.0; trim and
18
+ * reference alignment nudge it a few percent; a re-stacked 2-up wrapper roughly
19
+ * doubles one dimension and lands near 2.0. 1.5 catches the stack while
20
+ * tolerating the wobble.
21
+ */
22
+ export const ASPECT_RATIO_WARN_FACTOR = 1.5;
23
+ function isPositiveSize(size) {
24
+ return (Array.isArray(size) &&
25
+ size.length === 2 &&
26
+ Number.isFinite(size[0]) &&
27
+ Number.isFinite(size[1]) &&
28
+ size[0] > 0 &&
29
+ size[1] > 0);
30
+ }
31
+ /**
32
+ * Compares a rendered size against its reference size. Returns a
33
+ * non-warning, non-comparable result when either size is missing or
34
+ * non-positive — the guard only speaks when it has two real frames to compare.
35
+ */
36
+ export function evaluateAspectRatio(renderedPx, referencePx, factor = ASPECT_RATIO_WARN_FACTOR) {
37
+ if (!isPositiveSize(renderedPx) || !isPositiveSize(referencePx)) {
38
+ return {
39
+ comparable: false,
40
+ renderedAspect: null,
41
+ referenceAspect: null,
42
+ divergence: null,
43
+ warn: false,
44
+ };
45
+ }
46
+ const renderedAspect = renderedPx[0] / renderedPx[1];
47
+ const referenceAspect = referencePx[0] / referencePx[1];
48
+ const divergence = Math.max(renderedAspect / referenceAspect, referenceAspect / renderedAspect);
49
+ return {
50
+ comparable: true,
51
+ renderedAspect,
52
+ referenceAspect,
53
+ divergence,
54
+ warn: divergence > factor,
55
+ };
56
+ }
57
+ /**
58
+ * Human/agent-readable warning line for a divergent screen, or null when the
59
+ * screen does not warrant one. The caller decides where to surface it (preview
60
+ * result, preflight output) — it is advisory, never a failure.
61
+ */
62
+ export function formatAspectRatioWarning(screen, check) {
63
+ if (!check.warn || check.divergence === null)
64
+ return null;
65
+ const r = check.renderedAspect?.toFixed(2);
66
+ const ref = check.referenceAspect?.toFixed(2);
67
+ return (`Screen "${screen}" renders at aspect ratio ${r}:1 but its reference is ` +
68
+ `${ref}:1 (${check.divergence.toFixed(1)}× divergence). This often means ` +
69
+ `multiple states were stacked into one composable — author one named ` +
70
+ `composable per state instead.`);
71
+ }
72
+ //# sourceMappingURL=fidelity-aspect-guard.js.map
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Unit tests for the fidelity aspect-ratio guard.
3
+ *
4
+ * A wrapper that stacks several states into one composable renders far taller
5
+ * (or wider) than its single-state reference, so its aspect ratio diverges
6
+ * sharply from the reference's. The guard is the early warning for that
7
+ * re-stacking: it compares the rendered vs reference aspect ratios and flags a
8
+ * divergence beyond a documented fold factor — a WARNING, never a failure.
9
+ */
10
+ export {};
11
+ //# sourceMappingURL=fidelity-aspect-guard.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fidelity-aspect-guard.test.d.ts","sourceRoot":"","sources":["../../../../src/yaml/steps/fidelity-aspect-guard.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG"}
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Unit tests for the fidelity aspect-ratio guard.
3
+ *
4
+ * A wrapper that stacks several states into one composable renders far taller
5
+ * (or wider) than its single-state reference, so its aspect ratio diverges
6
+ * sharply from the reference's. The guard is the early warning for that
7
+ * re-stacking: it compares the rendered vs reference aspect ratios and flags a
8
+ * divergence beyond a documented fold factor — a WARNING, never a failure.
9
+ */
10
+ import { describe, test, expect } from '@jest/globals';
11
+ import { evaluateAspectRatio, ASPECT_RATIO_WARN_FACTOR, } from './fidelity-aspect-guard.js';
12
+ describe('evaluateAspectRatio', () => {
13
+ test('does not warn when rendered and reference aspect ratios match', () => {
14
+ // A faithful single-state banner: same proportions as its reference.
15
+ const check = evaluateAspectRatio([720, 1280], [360, 640]);
16
+ expect(check.comparable).toBe(true);
17
+ expect(check.warn).toBe(false);
18
+ expect(check.divergence).toBeCloseTo(1, 5);
19
+ });
20
+ test('warns when a stacked render is far off the reference aspect ratio', () => {
21
+ // The canonical miss: two banner states stacked in one Column render at
22
+ // ~2.2:1, judged against a single-state ~4.76:1 reference.
23
+ const rendered = [880, 400]; // 2.2:1
24
+ const reference = [952, 200]; // 4.76:1
25
+ const check = evaluateAspectRatio(rendered, reference);
26
+ expect(check.comparable).toBe(true);
27
+ expect(check.warn).toBe(true);
28
+ expect(check.divergence).toBeCloseTo(4.76 / 2.2, 1);
29
+ });
30
+ test('tolerates a small alignment/trim wobble within the fold factor', () => {
31
+ // Trim + reference alignment can nudge dimensions a few percent; that must
32
+ // not trip the warning.
33
+ const check = evaluateAspectRatio([726, 1280], [360, 640]);
34
+ expect(check.warn).toBe(false);
35
+ });
36
+ test('is not comparable when there is no reference', () => {
37
+ const check = evaluateAspectRatio([720, 1280], null);
38
+ expect(check.comparable).toBe(false);
39
+ expect(check.warn).toBe(false);
40
+ });
41
+ test('is not comparable when a dimension is zero or negative', () => {
42
+ expect(evaluateAspectRatio([720, 0], [360, 640]).comparable).toBe(false);
43
+ expect(evaluateAspectRatio([720, 1280], [0, 640]).comparable).toBe(false);
44
+ });
45
+ test('exposes a sane default fold factor', () => {
46
+ expect(ASPECT_RATIO_WARN_FACTOR).toBeGreaterThan(1);
47
+ expect(ASPECT_RATIO_WARN_FACTOR).toBeLessThan(2);
48
+ });
49
+ });
50
+ //# sourceMappingURL=fidelity-aspect-guard.test.js.map
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Reference-provenance gate.
3
+ *
4
+ * A UI-fidelity reference is a PNG the gate cannot read, so reference validity
5
+ * is asserted at export time as provenance metadata (`figmaNode`, `locale`,
6
+ * `resolved`, `exportedAt`) and the gate checks that METADATA — never the
7
+ * pixels, never via OCR. A reference that is absent, unprovenanced,
8
+ * placeholder-bearing (`resolved == false`), or in a locale other than the
9
+ * render's is UNVERIFIABLE: a hard, mechanical verdict that fails loud instead
10
+ * of producing a confident-but-false PASS, and that kills the ad-hoc-Figma-fetch
11
+ * root cause (an ad-hoc fetch has no provenance, so it is Unverifiable).
12
+ *
13
+ * Content differences (copy wording, geometry) are deliberately NOT evaluated
14
+ * here — those are soft, judged findings that legitimately drift between
15
+ * reference-export and gate-run.
16
+ *
17
+ * This module is the authoritative spec for the check. The Android render
18
+ * runtime inlines an equivalent JS implementation (it ships as a self-contained
19
+ * script string and cannot import this module); the render-step integration
20
+ * tests exercise that inlined copy end-to-end against the same fixtures, so the
21
+ * two cannot silently drift.
22
+ */
23
+ /** Export-time provenance as it travels in the run-input sidecar. */
24
+ export interface ReferenceProvenanceMeta {
25
+ figmaNode?: string;
26
+ locale?: string;
27
+ resolved?: boolean;
28
+ exportedAt?: string;
29
+ }
30
+ /** A reason a reference cannot be honestly judged. */
31
+ export interface ProvenanceFault {
32
+ /** Stable machine code (NO_PROVENANCE / UNRESOLVED_REFERENCE / …). */
33
+ code: string;
34
+ /** Human/agent-readable reason, naming the screen. */
35
+ message: string;
36
+ }
37
+ /** The per-state status string written to protocol-result.json. */
38
+ export declare const UNVERIFIABLE_STATUS = "unverifiable";
39
+ /**
40
+ * Returns the reason a reference is UNVERIFIABLE, or null when it is verifiable.
41
+ * Checks metadata then existence; order is irrelevant for single-fault inputs
42
+ * but deterministic when several faults coincide.
43
+ */
44
+ export declare function provenanceFault(screen: string, provenance: ReferenceProvenanceMeta | null | undefined, renderLocale: string, referenceExists: boolean): ProvenanceFault | null;
45
+ //# sourceMappingURL=fidelity-reference-provenance.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fidelity-reference-provenance.d.ts","sourceRoot":"","sources":["../../../../src/yaml/steps/fidelity-reference-provenance.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,qEAAqE;AACrE,MAAM,WAAW,uBAAuB;IACtC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,sDAAsD;AACtD,MAAM,WAAW,eAAe;IAC9B,sEAAsE;IACtE,IAAI,EAAE,MAAM,CAAC;IACb,sDAAsD;IACtD,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,mEAAmE;AACnE,eAAO,MAAM,mBAAmB,iBAAiB,CAAC;AAgBlD;;;;GAIG;AACH,wBAAgB,eAAe,CAC7B,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,uBAAuB,GAAG,IAAI,GAAG,SAAS,EACtD,YAAY,EAAE,MAAM,EACpB,eAAe,EAAE,OAAO,GACvB,eAAe,GAAG,IAAI,CAgCxB"}
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Reference-provenance gate.
3
+ *
4
+ * A UI-fidelity reference is a PNG the gate cannot read, so reference validity
5
+ * is asserted at export time as provenance metadata (`figmaNode`, `locale`,
6
+ * `resolved`, `exportedAt`) and the gate checks that METADATA — never the
7
+ * pixels, never via OCR. A reference that is absent, unprovenanced,
8
+ * placeholder-bearing (`resolved == false`), or in a locale other than the
9
+ * render's is UNVERIFIABLE: a hard, mechanical verdict that fails loud instead
10
+ * of producing a confident-but-false PASS, and that kills the ad-hoc-Figma-fetch
11
+ * root cause (an ad-hoc fetch has no provenance, so it is Unverifiable).
12
+ *
13
+ * Content differences (copy wording, geometry) are deliberately NOT evaluated
14
+ * here — those are soft, judged findings that legitimately drift between
15
+ * reference-export and gate-run.
16
+ *
17
+ * This module is the authoritative spec for the check. The Android render
18
+ * runtime inlines an equivalent JS implementation (it ships as a self-contained
19
+ * script string and cannot import this module); the render-step integration
20
+ * tests exercise that inlined copy end-to-end against the same fixtures, so the
21
+ * two cannot silently drift.
22
+ */
23
+ /** The per-state status string written to protocol-result.json. */
24
+ export const UNVERIFIABLE_STATUS = 'unverifiable';
25
+ function hasProvenance(p) {
26
+ // Provenance is only "present" when the fields the gate reasons over are
27
+ // there. A partial object (e.g. a hand-dropped `{ figmaNode }`) is treated as
28
+ // no provenance — verifiable is earned only through the full export path.
29
+ return (p != null &&
30
+ typeof p === 'object' &&
31
+ typeof p.locale === 'string' &&
32
+ typeof p.resolved === 'boolean');
33
+ }
34
+ /**
35
+ * Returns the reason a reference is UNVERIFIABLE, or null when it is verifiable.
36
+ * Checks metadata then existence; order is irrelevant for single-fault inputs
37
+ * but deterministic when several faults coincide.
38
+ */
39
+ export function provenanceFault(screen, provenance, renderLocale, referenceExists) {
40
+ if (!hasProvenance(provenance)) {
41
+ return {
42
+ code: 'NO_PROVENANCE',
43
+ message: `reference for ${screen} has no export provenance; a reference is ` +
44
+ `verifiable only when exported through the deterministic path`,
45
+ };
46
+ }
47
+ if (provenance.resolved === false) {
48
+ return {
49
+ code: 'UNRESOLVED_REFERENCE',
50
+ message: `reference for ${screen} still has unresolved copy placeholders ` +
51
+ `(resolved=false) — export it with the copy filled in`,
52
+ };
53
+ }
54
+ if (provenance.locale !== renderLocale) {
55
+ return {
56
+ code: 'LOCALE_MISMATCH',
57
+ message: `reference for ${screen} is locale "${provenance.locale}" but the ` +
58
+ `render locale is "${renderLocale}"`,
59
+ };
60
+ }
61
+ if (!referenceExists) {
62
+ return {
63
+ code: 'REFERENCE_ABSENT',
64
+ message: `reference image for ${screen} is absent`,
65
+ };
66
+ }
67
+ return null;
68
+ }
69
+ //# sourceMappingURL=fidelity-reference-provenance.js.map
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Unit tests for the reference-provenance gate.
3
+ *
4
+ * A UI-fidelity reference is a PNG; the gate cannot read its text. So validity
5
+ * is asserted at export time as provenance metadata, and the gate checks the
6
+ * METADATA, never the pixels (no OCR). A reference that is absent, unprovenanced,
7
+ * placeholder-bearing (`resolved == false`), or in the wrong locale is
8
+ * UNVERIFIABLE — a loud, mechanical verdict, not a confident-but-false PASS.
9
+ *
10
+ * Content differences (copy wording, geometry) are NOT checked here — those are
11
+ * soft, judged findings that legitimately drift between export and run.
12
+ */
13
+ export {};
14
+ //# sourceMappingURL=fidelity-reference-provenance.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fidelity-reference-provenance.test.d.ts","sourceRoot":"","sources":["../../../../src/yaml/steps/fidelity-reference-provenance.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG"}
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Unit tests for the reference-provenance gate.
3
+ *
4
+ * A UI-fidelity reference is a PNG; the gate cannot read its text. So validity
5
+ * is asserted at export time as provenance metadata, and the gate checks the
6
+ * METADATA, never the pixels (no OCR). A reference that is absent, unprovenanced,
7
+ * placeholder-bearing (`resolved == false`), or in the wrong locale is
8
+ * UNVERIFIABLE — a loud, mechanical verdict, not a confident-but-false PASS.
9
+ *
10
+ * Content differences (copy wording, geometry) are NOT checked here — those are
11
+ * soft, judged findings that legitimately drift between export and run.
12
+ */
13
+ import { describe, test, expect } from '@jest/globals';
14
+ import { provenanceFault, UNVERIFIABLE_STATUS, } from './fidelity-reference-provenance.js';
15
+ const GOOD = {
16
+ figmaNode: '12:345',
17
+ locale: 'en',
18
+ resolved: true,
19
+ exportedAt: '2026-06-18T10:00:00Z',
20
+ };
21
+ describe('provenanceFault', () => {
22
+ test('a present, provenanced, resolved, locale-matching reference is verifiable', () => {
23
+ expect(provenanceFault('Guest', GOOD, 'en', true)).toBeNull();
24
+ });
25
+ test('an absent reference is UNVERIFIABLE (REFERENCE_ABSENT)', () => {
26
+ const fault = provenanceFault('Guest', GOOD, 'en', false);
27
+ expect(fault?.code).toBe('REFERENCE_ABSENT');
28
+ });
29
+ test('a reference with no provenance is UNVERIFIABLE (NO_PROVENANCE)', () => {
30
+ expect(provenanceFault('Guest', null, 'en', true)?.code).toBe('NO_PROVENANCE');
31
+ expect(provenanceFault('Guest', undefined, 'en', true)?.code).toBe('NO_PROVENANCE');
32
+ });
33
+ test('incomplete provenance (missing locale/resolved) is UNVERIFIABLE (NO_PROVENANCE)', () => {
34
+ expect(provenanceFault('Guest', { figmaNode: 'x', exportedAt: 'y' }, 'en', true)?.code).toBe('NO_PROVENANCE');
35
+ });
36
+ test('a placeholder reference (resolved=false) is UNVERIFIABLE (UNRESOLVED_REFERENCE)', () => {
37
+ const fault = provenanceFault('Guest', { ...GOOD, resolved: false }, 'en', true);
38
+ expect(fault?.code).toBe('UNRESOLVED_REFERENCE');
39
+ });
40
+ test('a wrong-locale reference is UNVERIFIABLE (LOCALE_MISMATCH)', () => {
41
+ // Italian reference vs an English render.
42
+ const fault = provenanceFault('Guest', { ...GOOD, locale: 'it' }, 'en', true);
43
+ expect(fault?.code).toBe('LOCALE_MISMATCH');
44
+ });
45
+ test('every fault carries the screen name and a human reason', () => {
46
+ const fault = provenanceFault('LoggedIn', { ...GOOD, resolved: false }, 'en', true);
47
+ expect(fault?.message).toContain('LoggedIn');
48
+ expect(fault?.message.length).toBeGreaterThan(10);
49
+ });
50
+ test('exposes the unverifiable status string the result document uses', () => {
51
+ expect(UNVERIFIABLE_STATUS).toBe('unverifiable');
52
+ });
53
+ });
54
+ //# sourceMappingURL=fidelity-reference-provenance.test.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ui-fidelity-preview-android.d.ts","sourceRoot":"","sources":["../../../../src/yaml/steps/ui-fidelity-preview-android.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAsBH,OAAO,KAAK,EAEV,uBAAuB,EACvB,uBAAuB,EAExB,MAAM,0BAA0B,CAAC;AAElC,MAAM,WAAW,+BAA+B;IAC9C;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,gEAAgE;IAChE,OAAO,EAAE,uBAAuB,EAAE,CAAC;IACnC;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,kEAAkE;IAClE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,2EAA2E;IAC3E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAkBD;;;;;GAKG;AACH,eAAO,MAAM,yBAAyB,8BAA8B,CAAC;AAqErE;;;;;;GAMG;AACH,wBAAgB,8BAA8B,CAC5C,OAAO,EAAE,+BAA+B,GACvC,uBAAuB,CA6KzB"}
1
+ {"version":3,"file":"ui-fidelity-preview-android.d.ts","sourceRoot":"","sources":["../../../../src/yaml/steps/ui-fidelity-preview-android.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAuBH,OAAO,KAAK,EAEV,uBAAuB,EACvB,uBAAuB,EAGxB,MAAM,0BAA0B,CAAC;AAElC,MAAM,WAAW,+BAA+B;IAC9C;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,gEAAgE;IAChE,OAAO,EAAE,uBAAuB,EAAE,CAAC;IACnC;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,kEAAkE;IAClE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,2EAA2E;IAC3E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAQD;;;;;GAKG;AACH,eAAO,MAAM,yBAAyB,8BAA8B,CAAC;AAqErE;;;;;;GAMG;AACH,wBAAgB,8BAA8B,CAC5C,OAAO,EAAE,+BAA+B,GACvC,uBAAuB,CAuKzB"}
@@ -24,6 +24,7 @@ import { homedir, tmpdir } from 'node:os';
24
24
  import { basename, join, resolve } from 'node:path';
25
25
  import { parseScale, DEFAULT_SCALE } from './ui-fidelity-render.js';
26
26
  import { DEFAULT_GRADLE_TASK, PACKAGE_ARCHIVE_BASENAME, generateAndroidRenderScript, isValidGradleTask, } from './ui-fidelity-render-android.js';
27
+ import { buildPreviewScreenResult } from './ui-fidelity-preview.js';
27
28
  /**
28
29
  * Build error returned when the local Android toolchain is absent, so the caller
29
30
  * can fall back to a remote run instead of a confusing build failure. Generic
@@ -233,15 +234,7 @@ export function renderUiFidelityPreviewAndroid(options) {
233
234
  (detail ? `:\n${detail}` : ''));
234
235
  }
235
236
  const raw = JSON.parse(readFileSync(resultPath, 'utf-8'));
236
- const screens = (raw.screens || []).map((s) => ({
237
- screen: s.screen,
238
- status: s.status,
239
- error: s.error ?? null,
240
- referenceImagePath: absOrNull(artifactsDir, s.reference_image_path),
241
- renderedImagePath: absOrNull(artifactsDir, s.rendered_image_path),
242
- alignedImagePath: absOrNull(artifactsDir, s.aligned_image_path),
243
- dimensions: s.dimensions ?? null,
244
- }));
237
+ const screens = (raw.screens || []).map((s) => buildPreviewScreenResult(s, artifactsDir));
245
238
  const buildError = raw.error ?? null;
246
239
  const ok = buildError === null && screens.length > 0 && screens.every((s) => s.status === 'rendered');
247
240
  return { packageSource: 'inputs', buildError, screens, artifactsDir, ok };
@@ -73,6 +73,12 @@ export interface UiFidelityPreviewScreenResult {
73
73
  /** Absolute path to the reference-aligned image, or null. */
74
74
  alignedImagePath: string | null;
75
75
  dimensions: UiFidelityPreviewDimensions | null;
76
+ /**
77
+ * Advisory warning (never a failure) when the rendered aspect ratio diverges
78
+ * sharply from the reference's — the early signal that several states were
79
+ * stacked into one composable. Null when in tolerance or not comparable.
80
+ */
81
+ aspectWarning: string | null;
76
82
  }
77
83
  export interface UiFidelityPreviewResult {
78
84
  /** Always "inputs": the preview mirrors a shipped-package remote run. */
@@ -89,6 +95,26 @@ export interface UiFidelityPreviewResult {
89
95
  /** True only when every screen rendered and there was no build error. */
90
96
  ok: boolean;
91
97
  }
98
+ /** Shape of the protocol-result.json the render script writes. */
99
+ /** One screen as the render script writes it into protocol-result.json. */
100
+ export interface RawPreviewScreen {
101
+ screen: string;
102
+ status: string;
103
+ error: {
104
+ code: string;
105
+ message: string;
106
+ } | null;
107
+ reference_image_path: string | null;
108
+ rendered_image_path: string | null;
109
+ aligned_image_path?: string | null;
110
+ dimensions?: UiFidelityPreviewDimensions | null;
111
+ }
112
+ /**
113
+ * Map one raw render-script screen to a preview result, resolving image paths
114
+ * and attaching the aspect-ratio warning. Shared by the iOS and Android preview
115
+ * engines so the two paths produce identical result shapes and warnings.
116
+ */
117
+ export declare function buildPreviewScreenResult(raw: RawPreviewScreen, artifactsDir: string): UiFidelityPreviewScreenResult;
92
118
  /**
93
119
  * Render the requested screens locally and return the produced image paths and
94
120
  * dimensions. Throws only on caller errors (bad options) or when the render
@@ -1 +1 @@
1
- {"version":3,"file":"ui-fidelity-preview.d.ts","sourceRoot":"","sources":["../../../../src/yaml/steps/ui-fidelity-preview.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAwBH,+EAA+E;AAC/E,MAAM,WAAW,uBAAuB;IACtC,wEAAwE;IACxE,MAAM,EAAE,MAAM,CAAC;IACf,uEAAuE;IACvE,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,wBAAwB;IACvC;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,gEAAgE;IAChE,OAAO,EAAE,uBAAuB,EAAE,CAAC;IACnC;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,0EAA0E;IAC1E,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,iCAAiC;IACjC,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,2EAA2E;IAC3E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,2BAA2B;IAC1C,WAAW,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9B,UAAU,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,YAAY,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAAC;CACvC;AAED,MAAM,WAAW,6BAA6B;IAC5C,MAAM,EAAE,MAAM,CAAC;IACf,sEAAsE;IACtE,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAChD,iEAAiE;IACjE,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,4DAA4D;IAC5D,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,6DAA6D;IAC7D,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,UAAU,EAAE,2BAA2B,GAAG,IAAI,CAAC;CAChD;AAED,MAAM,WAAW,uBAAuB;IACtC,yEAAyE;IACzE,aAAa,EAAE,QAAQ,CAAC;IACxB,oEAAoE;IACpE,UAAU,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IACrD,wDAAwD;IACxD,OAAO,EAAE,6BAA6B,EAAE,CAAC;IACzC,mEAAmE;IACnE,YAAY,EAAE,MAAM,CAAC;IACrB,yEAAyE;IACzE,EAAE,EAAE,OAAO,CAAC;CACb;AAkCD;;;;;GAKG;AACH,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,wBAAwB,GAChC,uBAAuB,CAoIzB"}
1
+ {"version":3,"file":"ui-fidelity-preview.d.ts","sourceRoot":"","sources":["../../../../src/yaml/steps/ui-fidelity-preview.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AA4BH,+EAA+E;AAC/E,MAAM,WAAW,uBAAuB;IACtC,wEAAwE;IACxE,MAAM,EAAE,MAAM,CAAC;IACf,uEAAuE;IACvE,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,wBAAwB;IACvC;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,gEAAgE;IAChE,OAAO,EAAE,uBAAuB,EAAE,CAAC;IACnC;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,0EAA0E;IAC1E,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,iCAAiC;IACjC,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,2EAA2E;IAC3E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,2BAA2B;IAC1C,WAAW,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9B,UAAU,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,YAAY,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAAC;CACvC;AAED,MAAM,WAAW,6BAA6B;IAC5C,MAAM,EAAE,MAAM,CAAC;IACf,sEAAsE;IACtE,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAChD,iEAAiE;IACjE,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,4DAA4D;IAC5D,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,6DAA6D;IAC7D,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,UAAU,EAAE,2BAA2B,GAAG,IAAI,CAAC;IAC/C;;;;OAIG;IACH,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B;AAED,MAAM,WAAW,uBAAuB;IACtC,yEAAyE;IACzE,aAAa,EAAE,QAAQ,CAAC;IACxB,oEAAoE;IACpE,UAAU,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IACrD,wDAAwD;IACxD,OAAO,EAAE,6BAA6B,EAAE,CAAC;IACzC,mEAAmE;IACnE,YAAY,EAAE,MAAM,CAAC;IACrB,yEAAyE;IACzE,EAAE,EAAE,OAAO,CAAC;CACb;AAED,kEAAkE;AAClE,2EAA2E;AAC3E,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAChD,oBAAoB,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC,mBAAmB,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,kBAAkB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,UAAU,CAAC,EAAE,2BAA2B,GAAG,IAAI,CAAC;CACjD;AAuBD;;;;GAIG;AACH,wBAAgB,wBAAwB,CACtC,GAAG,EAAE,gBAAgB,EACrB,YAAY,EAAE,MAAM,GACnB,6BAA6B,CAgB/B;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,wBAAwB,GAChC,uBAAuB,CA8HzB"}
@@ -20,6 +20,7 @@ import { copyFileSync, existsSync, mkdirSync, mkdtempSync, readFileSync, statSyn
20
20
  import { tmpdir } from 'node:os';
21
21
  import { basename, join, resolve } from 'node:path';
22
22
  import { DEFAULT_RENDER_SIZE, DEFAULT_SCALE, generateRenderScript, parseRenderSize, parseScale, } from './ui-fidelity-render.js';
23
+ import { evaluateAspectRatio, formatAspectRatioWarning, } from './fidelity-aspect-guard.js';
23
24
  function runTar(args, pathPrepend) {
24
25
  const env = pathPrepend
25
26
  ? { ...process.env, PATH: pathPrepend + ':' + (process.env.PATH ?? '') }
@@ -34,6 +35,25 @@ function runTar(args, pathPrepend) {
34
35
  function absOrNull(artifactsDir, relative) {
35
36
  return typeof relative === 'string' && relative !== '' ? resolve(artifactsDir, relative) : null;
36
37
  }
38
+ /**
39
+ * Map one raw render-script screen to a preview result, resolving image paths
40
+ * and attaching the aspect-ratio warning. Shared by the iOS and Android preview
41
+ * engines so the two paths produce identical result shapes and warnings.
42
+ */
43
+ export function buildPreviewScreenResult(raw, artifactsDir) {
44
+ const dimensions = raw.dimensions ?? null;
45
+ const aspect = evaluateAspectRatio(dimensions?.rendered_px ?? null, dimensions?.reference_px ?? null);
46
+ return {
47
+ screen: raw.screen,
48
+ status: raw.status,
49
+ error: raw.error ?? null,
50
+ referenceImagePath: absOrNull(artifactsDir, raw.reference_image_path),
51
+ renderedImagePath: absOrNull(artifactsDir, raw.rendered_image_path),
52
+ alignedImagePath: absOrNull(artifactsDir, raw.aligned_image_path),
53
+ dimensions,
54
+ aspectWarning: formatAspectRatioWarning(raw.screen, aspect),
55
+ };
56
+ }
37
57
  /**
38
58
  * Render the requested screens locally and return the produced image paths and
39
59
  * dimensions. Throws only on caller errors (bad options) or when the render
@@ -136,15 +156,7 @@ export function renderUiFidelityPreview(options) {
136
156
  (detail ? `:\n${detail}` : ''));
137
157
  }
138
158
  const raw = JSON.parse(readFileSync(resultPath, 'utf-8'));
139
- const screens = (raw.screens || []).map((s) => ({
140
- screen: s.screen,
141
- status: s.status,
142
- error: s.error ?? null,
143
- referenceImagePath: absOrNull(artifactsDir, s.reference_image_path),
144
- renderedImagePath: absOrNull(artifactsDir, s.rendered_image_path),
145
- alignedImagePath: absOrNull(artifactsDir, s.aligned_image_path),
146
- dimensions: s.dimensions ?? null,
147
- }));
159
+ const screens = (raw.screens || []).map((s) => buildPreviewScreenResult(s, artifactsDir));
148
160
  const buildError = raw.error ?? null;
149
161
  const ok = buildError === null && screens.length > 0 && screens.every((s) => s.status === 'rendered');
150
162
  return { packageSource: 'inputs', buildError, screens, artifactsDir, ok };
@@ -12,7 +12,7 @@ import { chmodSync, existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync
12
12
  import { tmpdir } from 'node:os';
13
13
  import { dirname, join, resolve } from 'node:path';
14
14
  import { fileURLToPath } from 'node:url';
15
- import { renderUiFidelityPreview } from './ui-fidelity-preview.js';
15
+ import { renderUiFidelityPreview, buildPreviewScreenResult, } from './ui-fidelity-preview.js';
16
16
  const FIXTURE_PACKAGE = resolve(dirname(fileURLToPath(import.meta.url)), '../../../test/fixtures/ui-fidelity-package');
17
17
  const PNG_MAGIC = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);
18
18
  // A real, decodable 1x1 transparent PNG, used as the reference for the
@@ -100,6 +100,36 @@ describe('renderUiFidelityPreview — option validation', () => {
100
100
  })).toThrow(/reference for HomeView not found/);
101
101
  });
102
102
  });
103
+ describe('buildPreviewScreenResult — aspect-ratio warning wiring', () => {
104
+ function rawScreen(rendered, reference) {
105
+ return {
106
+ screen: 'PromoBanner',
107
+ status: 'rendered',
108
+ error: null,
109
+ reference_image_path: 'references/PromoBanner.png',
110
+ rendered_image_path: 'rendered/PromoBanner.png',
111
+ aligned_image_path: 'aligned/PromoBanner.png',
112
+ dimensions: {
113
+ rendered_px: rendered,
114
+ logical_pt: [rendered[0] / 2, rendered[1] / 2],
115
+ reference_px: reference,
116
+ },
117
+ };
118
+ }
119
+ test('attaches a warning when a stacked render diverges from its reference', () => {
120
+ const result = buildPreviewScreenResult(rawScreen([880, 400], [952, 200]), '/tmp/art');
121
+ expect(result.aspectWarning).toMatch(/divergence/);
122
+ expect(result.aspectWarning).toContain('PromoBanner');
123
+ });
124
+ test('leaves no warning when the render matches the reference aspect ratio', () => {
125
+ const result = buildPreviewScreenResult(rawScreen([720, 1280], [360, 640]), '/tmp/art');
126
+ expect(result.aspectWarning).toBeNull();
127
+ });
128
+ test('leaves no warning when there is no reference to compare', () => {
129
+ const result = buildPreviewScreenResult(rawScreen([720, 1280], null), '/tmp/art');
130
+ expect(result.aspectWarning).toBeNull();
131
+ });
132
+ });
103
133
  describe('renderUiFidelityPreview — fake toolchain', () => {
104
134
  test('archives the package, renders in inputs mode, and returns image paths + dims', () => {
105
135
  const result = renderUiFidelityPreview({
@@ -9,6 +9,15 @@
9
9
  * 1. Reads `.ci/inputs/params.json`
10
10
  * ({ "screens": { "<ScreenName>": "<referenceFileBasename>" } });
11
11
  * reference images live at `.ci/inputs/<basename>`.
12
+ * Optional reference-provenance sidecar (manifest-driven multi-state path):
13
+ * `"render_locale": "<locale>"` plus
14
+ * `"provenance": { "<ScreenName>": { figmaNode, locale, resolved, exportedAt } }`.
15
+ * When `render_locale` is present, each screen's reference is gated on its
16
+ * provenance METADATA (never pixels/OCR): absent, unprovenanced,
17
+ * `resolved == false`, or `locale != render_locale` → status
18
+ * `unverifiable` for that screen, so a rotten reference fails loud instead
19
+ * of yielding a confident-but-false PASS. Content differences (copy,
20
+ * geometry) are NOT gated here — they stay soft, judged findings.
12
21
  * 2. Extracts and validates the shipped Gradle project shipped as
13
22
  * `.ci/inputs/package.tar.gz` (single project root carrying a settings
14
23
  * script, bounded extracted size, safe member paths).
@@ -1 +1 @@
1
- {"version":3,"file":"ui-fidelity-render-android.d.ts","sourceRoot":"","sources":["../../../../src/yaml/steps/ui-fidelity-render-android.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AAEH,OAAO,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AACxD,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AAIpE,sDAAsD;AACtD,MAAM,WAAW,6BAA6B;IAC5C;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;GAGG;AACH,eAAO,MAAM,mBAAmB,yBAAyB,CAAC;AAE1D,gFAAgF;AAChF,eAAO,MAAM,wBAAwB,mBAAmB,CAAC;AAEzD,4DAA4D;AAC5D,eAAO,MAAM,gBAAgB,0BAA0B,CAAC;AAExD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEvD;AAED,8EAA8E;AAC9E,MAAM,WAAW,yBAAyB;IACxC,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;CACpB;AAqnBD;;;GAGG;AACH,wBAAgB,2BAA2B,CAAC,MAAM,EAAE,yBAAyB,GAAG,MAAM,CAUrF;AAED;;;GAGG;AACH,qBAAa,mCAAoC,SAAQ,gBAAgB;IACvE,yBAAyB,CACvB,OAAO,EAAE,6BAA6B,EACtC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC5B,OAAO,EAAE,QAAQ,GAChB,qBAAqB,EAAE;IAepB,OAAO,CACX,MAAM,EAAE,6BAA6B,EACrC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC5B,OAAO,EAAE,QAAQ,GAChB,OAAO,CAAC,OAAO,CAAC;CAgBpB"}
1
+ {"version":3,"file":"ui-fidelity-render-android.d.ts","sourceRoot":"","sources":["../../../../src/yaml/steps/ui-fidelity-render-android.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AAEH,OAAO,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AACxD,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AAIpE,sDAAsD;AACtD,MAAM,WAAW,6BAA6B;IAC5C;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;GAGG;AACH,eAAO,MAAM,mBAAmB,yBAAyB,CAAC;AAE1D,gFAAgF;AAChF,eAAO,MAAM,wBAAwB,mBAAmB,CAAC;AAEzD,4DAA4D;AAC5D,eAAO,MAAM,gBAAgB,0BAA0B,CAAC;AAExD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEvD;AAED,8EAA8E;AAC9E,MAAM,WAAW,yBAAyB;IACxC,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;CACpB;AAmsBD;;;GAGG;AACH,wBAAgB,2BAA2B,CAAC,MAAM,EAAE,yBAAyB,GAAG,MAAM,CAUrF;AAED;;;GAGG;AACH,qBAAa,mCAAoC,SAAQ,gBAAgB;IACvE,yBAAyB,CACvB,OAAO,EAAE,6BAA6B,EACtC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC5B,OAAO,EAAE,QAAQ,GAChB,qBAAqB,EAAE;IAepB,OAAO,CACX,MAAM,EAAE,6BAA6B,EACrC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC5B,OAAO,EAAE,QAAQ,GAChB,OAAO,CAAC,OAAO,CAAC;CAgBpB"}