@invarn/cibuild 2.1.3 → 2.1.5
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 +365 -8
- package/dist/src/yaml/steps/structural-facts.d.ts +152 -0
- package/dist/src/yaml/steps/structural-facts.d.ts.map +1 -1
- package/dist/src/yaml/steps/structural-facts.js +0 -0
- package/dist/src/yaml/steps/structural-facts.test.js +178 -1
- package/dist/src/yaml/steps/ui-fidelity-render-android.d.ts.map +1 -1
- package/dist/src/yaml/steps/ui-fidelity-render-android.js +20 -1
- package/dist/src/yaml/steps/ui-fidelity-render-android.test.js +51 -0
- package/package.json +1 -1
|
@@ -81,6 +81,128 @@ export interface DeriveStructuralFactsOptions {
|
|
|
81
81
|
overlapMinRatio?: number;
|
|
82
82
|
}
|
|
83
83
|
export declare function deriveStructuralFacts(dump: LayoutDump, options?: DeriveStructuralFactsOptions): StructuralFact[];
|
|
84
|
+
/** One element's geometry; x/y optional (position is advisory, see ADR-0004). */
|
|
85
|
+
export interface ElementGeometry {
|
|
86
|
+
width: number;
|
|
87
|
+
height: number;
|
|
88
|
+
x?: number;
|
|
89
|
+
y?: number;
|
|
90
|
+
}
|
|
91
|
+
export type GeometrySource = Record<string, ElementGeometry>;
|
|
92
|
+
export interface DimensionDelta {
|
|
93
|
+
dimension: 'width' | 'height' | 'x' | 'y';
|
|
94
|
+
generated: number;
|
|
95
|
+
reference: number;
|
|
96
|
+
delta: number;
|
|
97
|
+
}
|
|
98
|
+
export interface GeometryDelta {
|
|
99
|
+
elementId: string;
|
|
100
|
+
deltas: DimensionDelta[];
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Per-element dimensional deltas between a GENERATED geometry (render layout) and
|
|
104
|
+
* a REFERENCE geometry (design numbers), both in the same unit. Only elements
|
|
105
|
+
* present on both sides, and only dimensions supplied on both, are compared.
|
|
106
|
+
* Port of `packages/cli/lib/fidelity-geometry-diff.mjs`.
|
|
107
|
+
*/
|
|
108
|
+
export declare function diffKeyGeometry(generated: GeometrySource, reference: GeometrySource | null | undefined, options?: {
|
|
109
|
+
tolerancePx?: number;
|
|
110
|
+
}): GeometryDelta[];
|
|
111
|
+
/** `params.reference_geometry["<Screen>"]` — design px, treated 1:1 with dp. */
|
|
112
|
+
export type ReferenceGeometry = Record<string, {
|
|
113
|
+
w?: number;
|
|
114
|
+
h?: number;
|
|
115
|
+
x?: number;
|
|
116
|
+
y?: number;
|
|
117
|
+
}>;
|
|
118
|
+
/**
|
|
119
|
+
* Per-edge overhang of an element that overflows its frame (Signal B v2). For a
|
|
120
|
+
* clipping element the load-bearing invariant is its overhang per edge (= the
|
|
121
|
+
* clip), NOT its raw w/h: width can match and the clip still be wrong. Frame-
|
|
122
|
+
* relative edge overhang is the real clip, so it IS flagged — unlike absolute
|
|
123
|
+
* canvas x/y, which stays suppressed as a canvas-width artifact.
|
|
124
|
+
*
|
|
125
|
+
* rendered[edge] = element's unclipped edge − frame's edge (dp)
|
|
126
|
+
* design[edge] = design element edge − design frame edge (from reference_geometry)
|
|
127
|
+
* deltas = edges where |rendered − design| > overhangTolerance
|
|
128
|
+
* clipFlag = any delta present
|
|
129
|
+
*/
|
|
130
|
+
export interface StructuralOverhang {
|
|
131
|
+
unit: 'dp';
|
|
132
|
+
rendered: Partial<Record<Edge, number>>;
|
|
133
|
+
design: Partial<Record<Edge, number>>;
|
|
134
|
+
deltas: Array<{
|
|
135
|
+
edge: Edge;
|
|
136
|
+
delta: number;
|
|
137
|
+
}>;
|
|
138
|
+
clipFlag: boolean;
|
|
139
|
+
}
|
|
140
|
+
export interface StructuralGeometryElement {
|
|
141
|
+
id: string;
|
|
142
|
+
rendered: {
|
|
143
|
+
w: number;
|
|
144
|
+
h: number;
|
|
145
|
+
};
|
|
146
|
+
design: {
|
|
147
|
+
w: number | undefined;
|
|
148
|
+
h: number | undefined;
|
|
149
|
+
};
|
|
150
|
+
deltas: Array<{
|
|
151
|
+
dimension: 'w' | 'h' | 'x' | 'y';
|
|
152
|
+
delta: number;
|
|
153
|
+
}>;
|
|
154
|
+
sizeFlag: boolean;
|
|
155
|
+
/** Present only when the element overflows its (declared) frame. */
|
|
156
|
+
overhang?: StructuralOverhang;
|
|
157
|
+
}
|
|
158
|
+
export interface StructuralGeometry {
|
|
159
|
+
unit: 'dp';
|
|
160
|
+
density: number;
|
|
161
|
+
elements: StructuralGeometryElement[];
|
|
162
|
+
}
|
|
163
|
+
export interface StructuralSummary {
|
|
164
|
+
clean: boolean;
|
|
165
|
+
factCount: number;
|
|
166
|
+
maxSizeDeltaDp: number | null;
|
|
167
|
+
/** Largest |overhang delta| in dp, or null when there is no geometry. */
|
|
168
|
+
maxClipDeltaDp: number | null;
|
|
169
|
+
}
|
|
170
|
+
export interface StructuralBlock {
|
|
171
|
+
summary: StructuralSummary;
|
|
172
|
+
facts: StructuralFact[];
|
|
173
|
+
geometry?: StructuralGeometry;
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Assemble the inline `structural` block (ADR-0004): Signal-A facts always, plus
|
|
177
|
+
* the Signal-B geometry block when `referenceGeometry` is supplied. Size (w/h)
|
|
178
|
+
* deltas drive `sizeFlag`/`summary.clean`; x/y position deltas are reported but
|
|
179
|
+
* never flagged (canvas-width artifact). Mirrors Invarn's `buildStructuralBlock`.
|
|
180
|
+
*/
|
|
181
|
+
export declare function buildStructuralBlock(dump: LayoutDump, referenceGeometry?: ReferenceGeometry | null, density?: number, options?: {
|
|
182
|
+
sizeTolerancePx?: number;
|
|
183
|
+
overhangTolerancePx?: number;
|
|
184
|
+
}): StructuralBlock;
|
|
185
|
+
export interface ReviewRow {
|
|
186
|
+
element: string;
|
|
187
|
+
rendered: string | null;
|
|
188
|
+
design: string | null;
|
|
189
|
+
fact: string;
|
|
190
|
+
verdict: null;
|
|
191
|
+
}
|
|
192
|
+
export interface ReviewScaffold {
|
|
193
|
+
verdict: null;
|
|
194
|
+
mustExplain: string[];
|
|
195
|
+
rows: ReviewRow[];
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Build the run-only `review` scaffold from an assembled structural block:
|
|
199
|
+
* size-flagged geometry elements first (load-bearing), then every structural
|
|
200
|
+
* fact, each as a row with a null `verdict` the agent must replace.
|
|
201
|
+
*/
|
|
202
|
+
export declare function buildReviewScaffold(structural: {
|
|
203
|
+
facts?: StructuralFact[];
|
|
204
|
+
geometry?: StructuralGeometry | null;
|
|
205
|
+
}): ReviewScaffold;
|
|
84
206
|
/**
|
|
85
207
|
* Render-side embedded form of the deriver.
|
|
86
208
|
*
|
|
@@ -105,4 +227,34 @@ export declare const RENDER_SCRIPT_FACTS_STAGE: string;
|
|
|
105
227
|
export declare function getStructuralFactsStageInternals(): {
|
|
106
228
|
deriveStructuralFacts: (dump: LayoutDump, options?: DeriveStructuralFactsOptions) => StructuralFact[];
|
|
107
229
|
};
|
|
230
|
+
/**
|
|
231
|
+
* Render-side embedded form of the Signal-B differ + structural-block assembler.
|
|
232
|
+
*
|
|
233
|
+
* Concatenated into the generated render script alongside `RENDER_SCRIPT_FACTS_STAGE`
|
|
234
|
+
* (function declarations hoist, so order does not matter — `buildStructuralBlock`
|
|
235
|
+
* calls the facts stage's `deriveStructuralFacts`). `assembleStructural` references
|
|
236
|
+
* runtime globals (`fs`, `path`, `log`) defined in the main script and is only
|
|
237
|
+
* invoked from there. Kept byte-equivalent to the typed `buildStructuralBlock` /
|
|
238
|
+
* `diffKeyGeometry` above by the parity test, so run-side and preview-side (Invarn)
|
|
239
|
+
* structural blocks cannot drift.
|
|
240
|
+
*/
|
|
241
|
+
export declare const RENDER_SCRIPT_GEOMETRY_STAGE: string;
|
|
242
|
+
/**
|
|
243
|
+
* Evaluates the embedded facts + geometry stages together (mirroring how the
|
|
244
|
+
* render script concatenates them) and returns the pure `diffKeyGeometry` +
|
|
245
|
+
* `buildStructuralBlock`, so the parity test exercises exactly the shipped code.
|
|
246
|
+
*/
|
|
247
|
+
export declare function getGeometryStageInternals(): {
|
|
248
|
+
diffKeyGeometry: (generated: GeometrySource, reference: GeometrySource | null | undefined, options?: {
|
|
249
|
+
tolerancePx?: number;
|
|
250
|
+
}) => GeometryDelta[];
|
|
251
|
+
buildStructuralBlock: (dump: LayoutDump, referenceGeometry?: ReferenceGeometry | null, density?: number, options?: {
|
|
252
|
+
sizeTolerancePx?: number;
|
|
253
|
+
overhangTolerancePx?: number;
|
|
254
|
+
}) => StructuralBlock;
|
|
255
|
+
buildReviewScaffold: (structural: {
|
|
256
|
+
facts?: StructuralFact[];
|
|
257
|
+
geometry?: StructuralGeometry | null;
|
|
258
|
+
}) => ReviewScaffold;
|
|
259
|
+
};
|
|
108
260
|
//# sourceMappingURL=structural-facts.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"structural-facts.d.ts","sourceRoot":"","sources":["../../../../src/yaml/steps/structural-facts.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,UAAU;IACzB,oDAAoD;IACpD,EAAE,EAAE,MAAM,CAAC;IACX;6CACyC;IACzC,MAAM,EAAE,YAAY,CAAC;IACrB;;;+EAG2E;IAC3E,eAAe,CAAC,EAAE,YAAY,CAAC;IAC/B,+CAA+C;IAC/C,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB;oFACgF;IAChF,aAAa,EAAE,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,UAAU;IACzB,+CAA+C;IAC/C,MAAM,EAAE,YAAY,CAAC;IACrB,KAAK,EAAE,UAAU,EAAE,CAAC;CACrB;AAED,MAAM,MAAM,IAAI,GAAG,MAAM,GAAG,KAAK,GAAG,OAAO,GAAG,QAAQ,CAAC;AAEvD,MAAM,MAAM,cAAc,GACtB;IACE,IAAI,EAAE,UAAU,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,IAAI,EAAE,CAAC;IACd,UAAU,EAAE,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC;CAC3C,GACD;IACE,IAAI,EAAE,mBAAmB,CAAC;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,IAAI,EAAE,CAAC;IACd,SAAS,EAAE,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC;CAC1C,GACD;IACE,IAAI,EAAE,YAAY,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,IAAI,EAAE,CAAC;IACd,WAAW,EAAE,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC;CAC5C,GACD;IACE,IAAI,EAAE,SAAS,CAAC;IAChB,OAAO,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC1B,SAAS,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAC7C,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEN,MAAM,WAAW,4BAA4B;IAC3C;yEACqE;IACrE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;mCAC+B;IAC/B,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAoCD,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,UAAU,EAChB,OAAO,GAAE,4BAAiC,GACzC,cAAc,EAAE,CAuHlB;AAED;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,yBAAyB,EAAE,MAgJvC,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,gCAAgC,IAAI;IAClD,qBAAqB,EAAE,CACrB,IAAI,EAAE,UAAU,EAChB,OAAO,CAAC,EAAE,4BAA4B,KACnC,cAAc,EAAE,CAAC;CACvB,CAWA"}
|
|
1
|
+
{"version":3,"file":"structural-facts.d.ts","sourceRoot":"","sources":["../../../../src/yaml/steps/structural-facts.ts"],"names":[],"mappings":"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"}
|
|
Binary file
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* overflowing voucher tag; the deriver must surface the bottom clip as a
|
|
9
9
|
* fact so it is never again skipped by eye.
|
|
10
10
|
*/
|
|
11
|
-
import { deriveStructuralFacts, getStructuralFactsStageInternals, } from './structural-facts.js';
|
|
11
|
+
import { deriveStructuralFacts, diffKeyGeometry, buildStructuralBlock, buildReviewScaffold, getStructuralFactsStageInternals, getGeometryStageInternals, } from './structural-facts.js';
|
|
12
12
|
// 200x400 canvas in px for all fixtures unless noted.
|
|
13
13
|
const CANVAS = { left: 0, top: 0, right: 200, bottom: 400 };
|
|
14
14
|
describe('deriveStructuralFacts — containment', () => {
|
|
@@ -256,4 +256,181 @@ describe('deriveStructuralFacts — motivating regression (voucher tag bottom-cl
|
|
|
256
256
|
});
|
|
257
257
|
});
|
|
258
258
|
});
|
|
259
|
+
// ---- Signal B: key-geometry diff + inline structural-block assembly ----
|
|
260
|
+
describe('diffKeyGeometry — dimensional gaps', () => {
|
|
261
|
+
test('reports a size delta on the right element beyond tolerance', () => {
|
|
262
|
+
const generated = { card: { width: 328, height: 81 }, chip: { width: 64, height: 24 } };
|
|
263
|
+
const reference = { card: { width: 328, height: 72 }, chip: { width: 64, height: 24 } };
|
|
264
|
+
expect(diffKeyGeometry(generated, reference)).toEqual([
|
|
265
|
+
{ elementId: 'card', deltas: [{ dimension: 'height', generated: 81, reference: 72, delta: 9 }] },
|
|
266
|
+
]);
|
|
267
|
+
});
|
|
268
|
+
test('degrades cleanly to no deltas when there is no reference geometry', () => {
|
|
269
|
+
const generated = { card: { width: 100, height: 50 } };
|
|
270
|
+
expect(diffKeyGeometry(generated, null)).toEqual([]);
|
|
271
|
+
expect(diffKeyGeometry(generated, {})).toEqual([]);
|
|
272
|
+
});
|
|
273
|
+
});
|
|
274
|
+
// The golden hub promo banner (xhdpi, density 2): banner fills the 720px canvas,
|
|
275
|
+
// voucher 96x96dp vs Figma 81x81, banner 96dp tall vs Figma 72, copy<->voucher
|
|
276
|
+
// overlap. Identical fixture + expectations to Invarn's fidelity-structural test,
|
|
277
|
+
// so preview (Invarn) and run (cibuild) assemble byte-identical blocks.
|
|
278
|
+
const HUB_PROMO_DUMP = {
|
|
279
|
+
canvas: { left: 0, top: 0, right: 720, bottom: 192 },
|
|
280
|
+
nodes: [
|
|
281
|
+
{ id: 'node-1', bounds: { left: 0, top: 0, right: 720, bottom: 192 }, unclippedBounds: { left: 0, top: 0, right: 720, bottom: 192 }, parentId: null, clipsChildren: false },
|
|
282
|
+
{ id: 'banner', bounds: { left: 0, top: 0, right: 720, bottom: 192 }, unclippedBounds: { left: 0, top: 0, right: 720, bottom: 192 }, parentId: 'node-1', clipsChildren: false },
|
|
283
|
+
{ id: 'voucher', bounds: { left: 496, top: 0, right: 688, bottom: 192 }, unclippedBounds: { left: 496, top: 0, right: 688, bottom: 192 }, parentId: 'banner', clipsChildren: false },
|
|
284
|
+
{ id: 'copy', bounds: { left: 64, top: 24, right: 656, bottom: 117 }, unclippedBounds: { left: 64, top: 24, right: 656, bottom: 117 }, parentId: 'banner', clipsChildren: false },
|
|
285
|
+
],
|
|
286
|
+
};
|
|
287
|
+
const HUB_PROMO_REFERENCE = { voucher: { w: 81, h: 81 }, banner: { w: 343, h: 72 } };
|
|
288
|
+
describe('buildStructuralBlock — facts only (no reference geometry)', () => {
|
|
289
|
+
test('surfaces facts + summary, no geometry block', () => {
|
|
290
|
+
const block = buildStructuralBlock(HUB_PROMO_DUMP, null, 2);
|
|
291
|
+
expect(block.facts).toEqual([
|
|
292
|
+
{ kind: 'overlap', nodeIds: ['copy', 'voucher'], overlapPx: { width: 160, height: 93 }, ratio: 0.4036458333333333 },
|
|
293
|
+
]);
|
|
294
|
+
expect(block.summary).toEqual({ clean: false, factCount: 1, maxSizeDeltaDp: null, maxClipDeltaDp: null });
|
|
295
|
+
expect(block.geometry).toBeUndefined();
|
|
296
|
+
});
|
|
297
|
+
});
|
|
298
|
+
describe('buildStructuralBlock — with reference geometry (Signal B)', () => {
|
|
299
|
+
test('reports the golden size deltas and a not-clean summary', () => {
|
|
300
|
+
const block = buildStructuralBlock(HUB_PROMO_DUMP, HUB_PROMO_REFERENCE, 2);
|
|
301
|
+
expect(block.summary).toEqual({ clean: false, factCount: 1, maxSizeDeltaDp: 24, maxClipDeltaDp: 0 });
|
|
302
|
+
expect(block.geometry?.unit).toBe('dp');
|
|
303
|
+
expect(block.geometry?.density).toBe(2);
|
|
304
|
+
const voucher = block.geometry?.elements.find((e) => e.id === 'voucher');
|
|
305
|
+
expect(voucher).toEqual({
|
|
306
|
+
id: 'voucher',
|
|
307
|
+
rendered: { w: 96, h: 96 },
|
|
308
|
+
design: { w: 81, h: 81 },
|
|
309
|
+
deltas: [{ dimension: 'w', delta: 15 }, { dimension: 'h', delta: 15 }],
|
|
310
|
+
sizeFlag: true,
|
|
311
|
+
});
|
|
312
|
+
const banner = block.geometry?.elements.find((e) => e.id === 'banner');
|
|
313
|
+
expect(banner?.sizeFlag).toBe(true);
|
|
314
|
+
expect(banner?.deltas).toContainEqual({ dimension: 'h', delta: 24 });
|
|
315
|
+
});
|
|
316
|
+
});
|
|
317
|
+
// ---- Signal B v2: per-edge overhang (clip) diff ----
|
|
318
|
+
//
|
|
319
|
+
// The euro-coin miss: a voucher tag meant to overflow-and-clip on the bottom-right
|
|
320
|
+
// reproduced at the wrong size, so its bottom clip came out wrong even though its
|
|
321
|
+
// width matched. Signal B v1 (raw w/h) is blind here — the load-bearing invariant
|
|
322
|
+
// for a clipping element is its per-edge OVERHANG (= the clip), not its raw size.
|
|
323
|
+
// These two fixtures are the field validation numbers (px @ density 2): the
|
|
324
|
+
// logged-in state MUST flag (bottom Δ−9), the guest state MUST NOT (bottom Δ+2,
|
|
325
|
+
// within tolerance). Same fixtures + expectations as Invarn's preview copy.
|
|
326
|
+
// Logged-in: rendered voucher unclipped 289..370 × 16..86.5dp, frame card 343×70.5dp.
|
|
327
|
+
// right overhang 27=27 (match); bottom overhang 16 vs design 25 → Δ−9 (FLAG).
|
|
328
|
+
const OVERHANG_LOGGED_IN_DUMP = {
|
|
329
|
+
canvas: { left: 0, top: 0, right: 760, bottom: 200 },
|
|
330
|
+
nodes: [
|
|
331
|
+
{ id: 'node-1', bounds: { left: 0, top: 0, right: 760, bottom: 200 }, unclippedBounds: { left: 0, top: 0, right: 760, bottom: 200 }, parentId: null, clipsChildren: false },
|
|
332
|
+
{ id: 'banner', bounds: { left: 0, top: 0, right: 686, bottom: 141 }, unclippedBounds: { left: 0, top: 0, right: 686, bottom: 141 }, parentId: 'node-1', clipsChildren: false },
|
|
333
|
+
{ id: 'voucher', bounds: { left: 578, top: 32, right: 740, bottom: 173 }, unclippedBounds: { left: 578, top: 32, right: 740, bottom: 173 }, parentId: 'banner', clipsChildren: false },
|
|
334
|
+
],
|
|
335
|
+
};
|
|
336
|
+
const OVERHANG_LOGGED_IN_REF = { banner: { x: 0, y: 0, w: 343, h: 72 }, voucher: { x: 289, y: 16, w: 81, h: 81 } };
|
|
337
|
+
// Guest: rendered voucher unclipped 272..381 × 19..127.5dp, frame card 343×108.5dp.
|
|
338
|
+
// right overhang 38=38 (match); bottom overhang 19 vs design 17 → Δ+2 (within tol, PASS).
|
|
339
|
+
const OVERHANG_GUEST_DUMP = {
|
|
340
|
+
canvas: { left: 0, top: 0, right: 800, bottom: 280 },
|
|
341
|
+
nodes: [
|
|
342
|
+
{ id: 'node-1', bounds: { left: 0, top: 0, right: 800, bottom: 280 }, unclippedBounds: { left: 0, top: 0, right: 800, bottom: 280 }, parentId: null, clipsChildren: false },
|
|
343
|
+
{ id: 'banner', bounds: { left: 0, top: 0, right: 686, bottom: 217 }, unclippedBounds: { left: 0, top: 0, right: 686, bottom: 217 }, parentId: 'node-1', clipsChildren: false },
|
|
344
|
+
{ id: 'voucher', bounds: { left: 544, top: 38, right: 762, bottom: 255 }, unclippedBounds: { left: 544, top: 38, right: 762, bottom: 255 }, parentId: 'banner', clipsChildren: false },
|
|
345
|
+
],
|
|
346
|
+
};
|
|
347
|
+
const OVERHANG_GUEST_REF = { banner: { x: 0, y: 0, w: 343, h: 111 }, voucher: { x: 272, y: 19, w: 109, h: 109 } };
|
|
348
|
+
describe('buildStructuralBlock — per-edge overhang (Signal B v2)', () => {
|
|
349
|
+
test('logged-in voucher: flags the bottom clip (Δ−9), not the matching right edge', () => {
|
|
350
|
+
const block = buildStructuralBlock(OVERHANG_LOGGED_IN_DUMP, OVERHANG_LOGGED_IN_REF, 2);
|
|
351
|
+
const voucher = block.geometry?.elements.find((e) => e.id === 'voucher');
|
|
352
|
+
expect(voucher?.overhang).toEqual({
|
|
353
|
+
unit: 'dp',
|
|
354
|
+
rendered: { right: 27, bottom: 16 },
|
|
355
|
+
design: { right: 27, bottom: 25 },
|
|
356
|
+
deltas: [{ edge: 'bottom', delta: -9 }],
|
|
357
|
+
clipFlag: true,
|
|
358
|
+
});
|
|
359
|
+
expect(block.summary.clean).toBe(false);
|
|
360
|
+
expect(block.summary.maxClipDeltaDp).toBe(9);
|
|
361
|
+
});
|
|
362
|
+
test('guest voucher: matching right edge, bottom within tolerance → no clipFlag', () => {
|
|
363
|
+
const block = buildStructuralBlock(OVERHANG_GUEST_DUMP, OVERHANG_GUEST_REF, 2);
|
|
364
|
+
const voucher = block.geometry?.elements.find((e) => e.id === 'voucher');
|
|
365
|
+
expect(voucher?.overhang).toEqual({
|
|
366
|
+
unit: 'dp',
|
|
367
|
+
rendered: { right: 38, bottom: 19 },
|
|
368
|
+
design: { right: 38, bottom: 17 },
|
|
369
|
+
deltas: [],
|
|
370
|
+
clipFlag: false,
|
|
371
|
+
});
|
|
372
|
+
});
|
|
373
|
+
test('a fully-contained element gets no overhang block', () => {
|
|
374
|
+
const block = buildStructuralBlock(HUB_PROMO_DUMP, HUB_PROMO_REFERENCE, 2);
|
|
375
|
+
const voucher = block.geometry?.elements.find((e) => e.id === 'voucher');
|
|
376
|
+
expect(voucher?.overhang).toBeUndefined();
|
|
377
|
+
expect(block.summary.maxClipDeltaDp).toBe(0);
|
|
378
|
+
});
|
|
379
|
+
});
|
|
380
|
+
describe('buildReviewScaffold — run-only verdict obligation', () => {
|
|
381
|
+
test('forces a verdict per flagged element + fact, with the golden mustExplain', () => {
|
|
382
|
+
const block = buildStructuralBlock(HUB_PROMO_DUMP, HUB_PROMO_REFERENCE, 2);
|
|
383
|
+
const review = buildReviewScaffold(block);
|
|
384
|
+
expect(review.verdict).toBeNull();
|
|
385
|
+
expect(review.mustExplain).toEqual(['voucher', 'banner', 'copy↔voucher']);
|
|
386
|
+
expect(review.rows.every((r) => r.verdict === null)).toBe(true);
|
|
387
|
+
expect(review.rows.find((r) => r.element === 'voucher')).toEqual({
|
|
388
|
+
element: 'voucher',
|
|
389
|
+
rendered: '96×96dp',
|
|
390
|
+
design: '81×81dp',
|
|
391
|
+
fact: 'size +15×+15',
|
|
392
|
+
verdict: null,
|
|
393
|
+
});
|
|
394
|
+
expect(review.rows.find((r) => r.element === 'copy↔voucher')?.fact).toBe('overlap 160×93px (ratio 0.40)');
|
|
395
|
+
});
|
|
396
|
+
test('a clipFlag element forces a verdict row describing the overhang mismatch', () => {
|
|
397
|
+
const block = buildStructuralBlock(OVERHANG_LOGGED_IN_DUMP, OVERHANG_LOGGED_IN_REF, 2);
|
|
398
|
+
const review = buildReviewScaffold(block);
|
|
399
|
+
expect(review.mustExplain).toContain('voucher');
|
|
400
|
+
const clipRow = review.rows.find((r) => r.element === 'voucher' && r.fact.startsWith('clip'));
|
|
401
|
+
expect(clipRow).toEqual({
|
|
402
|
+
element: 'voucher',
|
|
403
|
+
rendered: 'bottom 16dp',
|
|
404
|
+
design: 'bottom 25dp',
|
|
405
|
+
fact: 'clip bottom 16dp vs design 25dp (-9)',
|
|
406
|
+
verdict: null,
|
|
407
|
+
});
|
|
408
|
+
});
|
|
409
|
+
});
|
|
410
|
+
describe('embedded geometry stage parity', () => {
|
|
411
|
+
// The render ships a JS-source port of the differ + assembler. It must produce
|
|
412
|
+
// byte-identical blocks to the canonical typed module, or run-side structural
|
|
413
|
+
// would diverge from preview-side (Invarn) and from the tests.
|
|
414
|
+
const embedded = getGeometryStageInternals();
|
|
415
|
+
test('embedded diffKeyGeometry matches the module', () => {
|
|
416
|
+
const generated = { card: { width: 328, height: 81 } };
|
|
417
|
+
const reference = { card: { width: 328, height: 72 } };
|
|
418
|
+
expect(embedded.diffKeyGeometry(generated, reference)).toEqual(diffKeyGeometry(generated, reference));
|
|
419
|
+
});
|
|
420
|
+
const parityCases = [
|
|
421
|
+
{ name: 'factsOnly', dump: HUB_PROMO_DUMP, ref: null },
|
|
422
|
+
{ name: 'withGeometry', dump: HUB_PROMO_DUMP, ref: HUB_PROMO_REFERENCE },
|
|
423
|
+
{ name: 'overhangLoggedIn', dump: OVERHANG_LOGGED_IN_DUMP, ref: OVERHANG_LOGGED_IN_REF },
|
|
424
|
+
{ name: 'overhangGuest', dump: OVERHANG_GUEST_DUMP, ref: OVERHANG_GUEST_REF },
|
|
425
|
+
];
|
|
426
|
+
for (const { name, dump, ref } of parityCases) {
|
|
427
|
+
test(`embedded buildStructuralBlock matches the module (${name})`, () => {
|
|
428
|
+
expect(embedded.buildStructuralBlock(dump, ref, 2)).toEqual(buildStructuralBlock(dump, ref, 2));
|
|
429
|
+
});
|
|
430
|
+
test(`embedded buildReviewScaffold matches the module (${name})`, () => {
|
|
431
|
+
const block = buildStructuralBlock(dump, ref, 2);
|
|
432
|
+
expect(embedded.buildReviewScaffold(block)).toEqual(buildReviewScaffold(block));
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
});
|
|
259
436
|
//# sourceMappingURL=structural-facts.test.js.map
|
|
@@ -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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;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;AAKpE,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;
|
|
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;AAKpE,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;AA8vBD;;;GAGG;AACH,wBAAgB,2BAA2B,CAAC,MAAM,EAAE,yBAAyB,GAAG,MAAM,CAYrF;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"}
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
import { BaseStepExecutor } from './base.js';
|
|
51
51
|
import { parseScale, DEFAULT_SCALE } from './ui-fidelity-render.js';
|
|
52
52
|
import { RENDER_SCRIPT_TRIM_STAGE } from './render-post-processor.js';
|
|
53
|
-
import { RENDER_SCRIPT_FACTS_STAGE } from './structural-facts.js';
|
|
53
|
+
import { RENDER_SCRIPT_FACTS_STAGE, RENDER_SCRIPT_GEOMETRY_STAGE } from './structural-facts.js';
|
|
54
54
|
/**
|
|
55
55
|
* Default Roborazzi record task. Run from the project root so Gradle resolves
|
|
56
56
|
* it in whichever subproject declares it.
|
|
@@ -91,6 +91,10 @@ var resultPath = path.join(artifactsDir, 'protocol-result.json');
|
|
|
91
91
|
|
|
92
92
|
var currentEntries = [];
|
|
93
93
|
var buildError = null;
|
|
94
|
+
// params.reference_geometry (design-px geometry per screen), set in main() and
|
|
95
|
+
// read by the structural assembler. Null when the run declared none → Signal B
|
|
96
|
+
// is skipped and the structural block carries facts only.
|
|
97
|
+
var referenceGeometry = null;
|
|
94
98
|
|
|
95
99
|
function log(message) {
|
|
96
100
|
console.log('[ui-fidelity-render-android] ' + message);
|
|
@@ -597,7 +601,13 @@ function renderScreens(renderable, projectRoot) {
|
|
|
597
601
|
}
|
|
598
602
|
}
|
|
599
603
|
if (layoutSource !== null) {
|
|
604
|
+
// Signal A: write the facts + layout artifacts (deep-dive path).
|
|
600
605
|
deriveAndWriteFacts(entry, layoutSource);
|
|
606
|
+
// ADR-0004: assemble the inline structural block (facts + Signal-B
|
|
607
|
+
// geometry deltas vs reference_geometry) onto the screen entry, so the
|
|
608
|
+
// judge reads the signals from protocol-result.json without fetching
|
|
609
|
+
// artifacts. CONFIG.scale is the render density (dp = px / scale).
|
|
610
|
+
assembleStructural(entry, layoutSource, referenceGeometry, CONFIG.scale);
|
|
601
611
|
}
|
|
602
612
|
});
|
|
603
613
|
} finally {
|
|
@@ -655,6 +665,14 @@ function main() {
|
|
|
655
665
|
params.provenance && typeof params.provenance === 'object' && !Array.isArray(params.provenance)
|
|
656
666
|
? params.provenance
|
|
657
667
|
: {};
|
|
668
|
+
// Signal-B design numbers (advisory): { "<Screen>": { "<tagId>": {w,h,x,y} } }
|
|
669
|
+
// in design px. Absent → the structural block carries Signal-A facts only.
|
|
670
|
+
referenceGeometry =
|
|
671
|
+
params.reference_geometry &&
|
|
672
|
+
typeof params.reference_geometry === 'object' &&
|
|
673
|
+
!Array.isArray(params.reference_geometry)
|
|
674
|
+
? params.reference_geometry
|
|
675
|
+
: null;
|
|
658
676
|
|
|
659
677
|
var screens = Object.keys(params.screens);
|
|
660
678
|
currentEntries = screens.map(function (screen) {
|
|
@@ -826,6 +844,7 @@ export function generateAndroidRenderScript(config) {
|
|
|
826
844
|
'var PACKAGE_ARCHIVE_NAME = ' + JSON.stringify(PACKAGE_ARCHIVE_BASENAME) + ';',
|
|
827
845
|
RENDER_SCRIPT_TRIM_STAGE,
|
|
828
846
|
RENDER_SCRIPT_FACTS_STAGE,
|
|
847
|
+
RENDER_SCRIPT_GEOMETRY_STAGE,
|
|
829
848
|
ANDROID_RENDER_SCRIPT_MAIN,
|
|
830
849
|
].join('\n');
|
|
831
850
|
}
|
|
@@ -315,6 +315,57 @@ describe('android render runtime — render + trim (happy path)', () => {
|
|
|
315
315
|
],
|
|
316
316
|
});
|
|
317
317
|
});
|
|
318
|
+
test('assembles the inline structural block (facts) onto the screen entry', async () => {
|
|
319
|
+
const project = makeProject({ screens: { XProof: 'x.png' } });
|
|
320
|
+
writeReference(project, 'x.png');
|
|
321
|
+
stageArchive(project, gradleProjectEntries());
|
|
322
|
+
const run = runScript(project, await buildScript(), {
|
|
323
|
+
FAKE_ROBORAZZI_SCREENS: 'XProof',
|
|
324
|
+
FAKE_ROBORAZZI_LAYOUT: '1',
|
|
325
|
+
});
|
|
326
|
+
expect(run.status).toBe(0);
|
|
327
|
+
const screen = readResult(project).screens.find((s) => s.screen === 'XProof');
|
|
328
|
+
// Signal A surfaces inline (no reference_geometry → no geometry block).
|
|
329
|
+
expect(screen?.structural?.summary).toEqual({ clean: false, factCount: 1, maxSizeDeltaDp: null, maxClipDeltaDp: null });
|
|
330
|
+
expect(screen?.structural?.facts).toHaveLength(1);
|
|
331
|
+
expect(screen?.structural?.geometry).toBeUndefined();
|
|
332
|
+
// The verdict obligation is baked onto the run result by the runner: the
|
|
333
|
+
// clip fact (node 'tag') becomes a must-explain row with a null verdict.
|
|
334
|
+
expect(screen?.structural?.review?.verdict).toBeNull();
|
|
335
|
+
expect(screen?.structural?.review?.mustExplain).toEqual(['tag']);
|
|
336
|
+
expect(screen?.structural?.review?.rows.every((r) => r.verdict === null)).toBe(true);
|
|
337
|
+
});
|
|
338
|
+
test('adds Signal-B geometry deltas to the structural block when reference_geometry is given', async () => {
|
|
339
|
+
// The fake layout dump: card bounds 160x180px, tag 80x74px (density 2 → dp
|
|
340
|
+
// card 80x90, tag 40x37). Declare design numbers so the card reads +10dp tall.
|
|
341
|
+
const project = makeProject({
|
|
342
|
+
screens: { XProof: 'x.png' },
|
|
343
|
+
reference_geometry: { XProof: { card: { w: 80, h: 80 }, tag: { w: 40, h: 37 } } },
|
|
344
|
+
});
|
|
345
|
+
writeReference(project, 'x.png');
|
|
346
|
+
stageArchive(project, gradleProjectEntries());
|
|
347
|
+
const run = runScript(project, await buildScript(), {
|
|
348
|
+
FAKE_ROBORAZZI_SCREENS: 'XProof',
|
|
349
|
+
FAKE_ROBORAZZI_LAYOUT: '1',
|
|
350
|
+
});
|
|
351
|
+
expect(run.status).toBe(0);
|
|
352
|
+
const screen = readResult(project).screens.find((s) => s.screen === 'XProof');
|
|
353
|
+
expect(screen?.structural?.summary).toEqual({ clean: false, factCount: 1, maxSizeDeltaDp: 10, maxClipDeltaDp: 0 });
|
|
354
|
+
const geometry = screen?.structural?.geometry;
|
|
355
|
+
expect(geometry?.unit).toBe('dp');
|
|
356
|
+
expect(geometry?.density).toBe(2);
|
|
357
|
+
const card = geometry?.elements.find((e) => e.id === 'card');
|
|
358
|
+
expect(card).toEqual({
|
|
359
|
+
id: 'card',
|
|
360
|
+
rendered: { w: 80, h: 90 },
|
|
361
|
+
design: { w: 80, h: 80 },
|
|
362
|
+
deltas: [{ dimension: 'h', delta: 10 }],
|
|
363
|
+
sizeFlag: true,
|
|
364
|
+
});
|
|
365
|
+
const tag = geometry?.elements.find((e) => e.id === 'tag');
|
|
366
|
+
expect(tag?.sizeFlag).toBe(false);
|
|
367
|
+
expect(tag?.deltas).toEqual([]);
|
|
368
|
+
});
|
|
318
369
|
test('finds the layout dump by dot-segment match even when its name differs from the PNG stem', async () => {
|
|
319
370
|
const project = makeProject({ screens: { XProof: 'x.png' } });
|
|
320
371
|
writeReference(project, 'x.png');
|