@invarn/cibuild 2.1.4 → 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 +136 -21
- package/dist/src/yaml/steps/structural-facts.d.ts +28 -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 +87 -5
- package/dist/src/yaml/steps/ui-fidelity-render-android.test.js +2 -2
- package/package.json +1 -1
|
@@ -115,6 +115,28 @@ export type ReferenceGeometry = Record<string, {
|
|
|
115
115
|
x?: number;
|
|
116
116
|
y?: number;
|
|
117
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
|
+
}
|
|
118
140
|
export interface StructuralGeometryElement {
|
|
119
141
|
id: string;
|
|
120
142
|
rendered: {
|
|
@@ -130,6 +152,8 @@ export interface StructuralGeometryElement {
|
|
|
130
152
|
delta: number;
|
|
131
153
|
}>;
|
|
132
154
|
sizeFlag: boolean;
|
|
155
|
+
/** Present only when the element overflows its (declared) frame. */
|
|
156
|
+
overhang?: StructuralOverhang;
|
|
133
157
|
}
|
|
134
158
|
export interface StructuralGeometry {
|
|
135
159
|
unit: 'dp';
|
|
@@ -140,6 +164,8 @@ export interface StructuralSummary {
|
|
|
140
164
|
clean: boolean;
|
|
141
165
|
factCount: number;
|
|
142
166
|
maxSizeDeltaDp: number | null;
|
|
167
|
+
/** Largest |overhang delta| in dp, or null when there is no geometry. */
|
|
168
|
+
maxClipDeltaDp: number | null;
|
|
143
169
|
}
|
|
144
170
|
export interface StructuralBlock {
|
|
145
171
|
summary: StructuralSummary;
|
|
@@ -154,6 +180,7 @@ export interface StructuralBlock {
|
|
|
154
180
|
*/
|
|
155
181
|
export declare function buildStructuralBlock(dump: LayoutDump, referenceGeometry?: ReferenceGeometry | null, density?: number, options?: {
|
|
156
182
|
sizeTolerancePx?: number;
|
|
183
|
+
overhangTolerancePx?: number;
|
|
157
184
|
}): StructuralBlock;
|
|
158
185
|
export interface ReviewRow {
|
|
159
186
|
element: string;
|
|
@@ -223,6 +250,7 @@ export declare function getGeometryStageInternals(): {
|
|
|
223
250
|
}) => GeometryDelta[];
|
|
224
251
|
buildStructuralBlock: (dump: LayoutDump, referenceGeometry?: ReferenceGeometry | null, density?: number, options?: {
|
|
225
252
|
sizeTolerancePx?: number;
|
|
253
|
+
overhangTolerancePx?: number;
|
|
226
254
|
}) => StructuralBlock;
|
|
227
255
|
buildReviewScaffold: (structural: {
|
|
228
256
|
facts?: StructuralFact[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"structural-facts.d.ts","sourceRoot":"","sources":["../../../../src/yaml/steps/structural-facts.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"structural-facts.d.ts","sourceRoot":"","sources":["../../../../src/yaml/steps/structural-facts.ts"],"names":[],"mappings":"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"}
|
|
Binary file
|
|
@@ -291,14 +291,14 @@ describe('buildStructuralBlock — facts only (no reference geometry)', () => {
|
|
|
291
291
|
expect(block.facts).toEqual([
|
|
292
292
|
{ kind: 'overlap', nodeIds: ['copy', 'voucher'], overlapPx: { width: 160, height: 93 }, ratio: 0.4036458333333333 },
|
|
293
293
|
]);
|
|
294
|
-
expect(block.summary).toEqual({ clean: false, factCount: 1, maxSizeDeltaDp: null });
|
|
294
|
+
expect(block.summary).toEqual({ clean: false, factCount: 1, maxSizeDeltaDp: null, maxClipDeltaDp: null });
|
|
295
295
|
expect(block.geometry).toBeUndefined();
|
|
296
296
|
});
|
|
297
297
|
});
|
|
298
298
|
describe('buildStructuralBlock — with reference geometry (Signal B)', () => {
|
|
299
299
|
test('reports the golden size deltas and a not-clean summary', () => {
|
|
300
300
|
const block = buildStructuralBlock(HUB_PROMO_DUMP, HUB_PROMO_REFERENCE, 2);
|
|
301
|
-
expect(block.summary).toEqual({ clean: false, factCount: 1, maxSizeDeltaDp: 24 });
|
|
301
|
+
expect(block.summary).toEqual({ clean: false, factCount: 1, maxSizeDeltaDp: 24, maxClipDeltaDp: 0 });
|
|
302
302
|
expect(block.geometry?.unit).toBe('dp');
|
|
303
303
|
expect(block.geometry?.density).toBe(2);
|
|
304
304
|
const voucher = block.geometry?.elements.find((e) => e.id === 'voucher');
|
|
@@ -314,6 +314,69 @@ describe('buildStructuralBlock — with reference geometry (Signal B)', () => {
|
|
|
314
314
|
expect(banner?.deltas).toContainEqual({ dimension: 'h', delta: 24 });
|
|
315
315
|
});
|
|
316
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
|
+
});
|
|
317
380
|
describe('buildReviewScaffold — run-only verdict obligation', () => {
|
|
318
381
|
test('forces a verdict per flagged element + fact, with the golden mustExplain', () => {
|
|
319
382
|
const block = buildStructuralBlock(HUB_PROMO_DUMP, HUB_PROMO_REFERENCE, 2);
|
|
@@ -330,6 +393,19 @@ describe('buildReviewScaffold — run-only verdict obligation', () => {
|
|
|
330
393
|
});
|
|
331
394
|
expect(review.rows.find((r) => r.element === 'copy↔voucher')?.fact).toBe('overlap 160×93px (ratio 0.40)');
|
|
332
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
|
+
});
|
|
333
409
|
});
|
|
334
410
|
describe('embedded geometry stage parity', () => {
|
|
335
411
|
// The render ships a JS-source port of the differ + assembler. It must produce
|
|
@@ -341,12 +417,18 @@ describe('embedded geometry stage parity', () => {
|
|
|
341
417
|
const reference = { card: { width: 328, height: 72 } };
|
|
342
418
|
expect(embedded.diffKeyGeometry(generated, reference)).toEqual(diffKeyGeometry(generated, reference));
|
|
343
419
|
});
|
|
344
|
-
|
|
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) {
|
|
345
427
|
test(`embedded buildStructuralBlock matches the module (${name})`, () => {
|
|
346
|
-
expect(embedded.buildStructuralBlock(
|
|
428
|
+
expect(embedded.buildStructuralBlock(dump, ref, 2)).toEqual(buildStructuralBlock(dump, ref, 2));
|
|
347
429
|
});
|
|
348
430
|
test(`embedded buildReviewScaffold matches the module (${name})`, () => {
|
|
349
|
-
const block = buildStructuralBlock(
|
|
431
|
+
const block = buildStructuralBlock(dump, ref, 2);
|
|
350
432
|
expect(embedded.buildReviewScaffold(block)).toEqual(buildReviewScaffold(block));
|
|
351
433
|
});
|
|
352
434
|
}
|
|
@@ -326,7 +326,7 @@ describe('android render runtime — render + trim (happy path)', () => {
|
|
|
326
326
|
expect(run.status).toBe(0);
|
|
327
327
|
const screen = readResult(project).screens.find((s) => s.screen === 'XProof');
|
|
328
328
|
// Signal A surfaces inline (no reference_geometry → no geometry block).
|
|
329
|
-
expect(screen?.structural?.summary).toEqual({ clean: false, factCount: 1, maxSizeDeltaDp: null });
|
|
329
|
+
expect(screen?.structural?.summary).toEqual({ clean: false, factCount: 1, maxSizeDeltaDp: null, maxClipDeltaDp: null });
|
|
330
330
|
expect(screen?.structural?.facts).toHaveLength(1);
|
|
331
331
|
expect(screen?.structural?.geometry).toBeUndefined();
|
|
332
332
|
// The verdict obligation is baked onto the run result by the runner: the
|
|
@@ -350,7 +350,7 @@ describe('android render runtime — render + trim (happy path)', () => {
|
|
|
350
350
|
});
|
|
351
351
|
expect(run.status).toBe(0);
|
|
352
352
|
const screen = readResult(project).screens.find((s) => s.screen === 'XProof');
|
|
353
|
-
expect(screen?.structural?.summary).toEqual({ clean: false, factCount: 1, maxSizeDeltaDp: 10 });
|
|
353
|
+
expect(screen?.structural?.summary).toEqual({ clean: false, factCount: 1, maxSizeDeltaDp: 10, maxClipDeltaDp: 0 });
|
|
354
354
|
const geometry = screen?.structural?.geometry;
|
|
355
355
|
expect(geometry?.unit).toBe('dp');
|
|
356
356
|
expect(geometry?.density).toBe(2);
|