@invarn/cibuild 2.1.4 → 2.1.6
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 +171 -22
- package/dist/src/yaml/steps/structural-facts.d.ts +34 -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 +93 -5
- package/dist/src/yaml/steps/ui-fidelity-render-android.test.js +2 -2
- package/package.json +1 -1
|
@@ -115,6 +115,34 @@ 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
|
+
/** The clip delta is explained by the element's OWN size being wrong on the
|
|
140
|
+
* clip axis (a size delta, not the design's intended clip). */
|
|
141
|
+
sizeDriven: boolean;
|
|
142
|
+
/** …and that wrong size matches the frame's size — the parent-relative
|
|
143
|
+
* (`fillMaxHeight`/`matchParentSize`) signature. The fix is a fixed size. */
|
|
144
|
+
frameRelative: boolean;
|
|
145
|
+
}
|
|
118
146
|
export interface StructuralGeometryElement {
|
|
119
147
|
id: string;
|
|
120
148
|
rendered: {
|
|
@@ -130,6 +158,8 @@ export interface StructuralGeometryElement {
|
|
|
130
158
|
delta: number;
|
|
131
159
|
}>;
|
|
132
160
|
sizeFlag: boolean;
|
|
161
|
+
/** Present only when the element overflows its (declared) frame. */
|
|
162
|
+
overhang?: StructuralOverhang;
|
|
133
163
|
}
|
|
134
164
|
export interface StructuralGeometry {
|
|
135
165
|
unit: 'dp';
|
|
@@ -140,6 +170,8 @@ export interface StructuralSummary {
|
|
|
140
170
|
clean: boolean;
|
|
141
171
|
factCount: number;
|
|
142
172
|
maxSizeDeltaDp: number | null;
|
|
173
|
+
/** Largest |overhang delta| in dp, or null when there is no geometry. */
|
|
174
|
+
maxClipDeltaDp: number | null;
|
|
143
175
|
}
|
|
144
176
|
export interface StructuralBlock {
|
|
145
177
|
summary: StructuralSummary;
|
|
@@ -154,6 +186,7 @@ export interface StructuralBlock {
|
|
|
154
186
|
*/
|
|
155
187
|
export declare function buildStructuralBlock(dump: LayoutDump, referenceGeometry?: ReferenceGeometry | null, density?: number, options?: {
|
|
156
188
|
sizeTolerancePx?: number;
|
|
189
|
+
overhangTolerancePx?: number;
|
|
157
190
|
}): StructuralBlock;
|
|
158
191
|
export interface ReviewRow {
|
|
159
192
|
element: string;
|
|
@@ -223,6 +256,7 @@ export declare function getGeometryStageInternals(): {
|
|
|
223
256
|
}) => GeometryDelta[];
|
|
224
257
|
buildStructuralBlock: (dump: LayoutDump, referenceGeometry?: ReferenceGeometry | null, density?: number, options?: {
|
|
225
258
|
sizeTolerancePx?: number;
|
|
259
|
+
overhangTolerancePx?: number;
|
|
226
260
|
}) => StructuralBlock;
|
|
227
261
|
buildReviewScaffold: (structural: {
|
|
228
262
|
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,75 @@ 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
|
+
// the clip is explained by the voucher's own height (71 vs 81dp) tracking
|
|
359
|
+
// the frame (the fillMaxHeight tell) — a sizing defect, not an intended clip.
|
|
360
|
+
sizeDriven: true,
|
|
361
|
+
frameRelative: true,
|
|
362
|
+
});
|
|
363
|
+
expect(block.summary.clean).toBe(false);
|
|
364
|
+
expect(block.summary.maxClipDeltaDp).toBe(9);
|
|
365
|
+
});
|
|
366
|
+
test('guest voucher: matching right edge, bottom within tolerance → no clipFlag', () => {
|
|
367
|
+
const block = buildStructuralBlock(OVERHANG_GUEST_DUMP, OVERHANG_GUEST_REF, 2);
|
|
368
|
+
const voucher = block.geometry?.elements.find((e) => e.id === 'voucher');
|
|
369
|
+
expect(voucher?.overhang).toEqual({
|
|
370
|
+
unit: 'dp',
|
|
371
|
+
rendered: { right: 38, bottom: 19 },
|
|
372
|
+
design: { right: 38, bottom: 17 },
|
|
373
|
+
deltas: [],
|
|
374
|
+
clipFlag: false,
|
|
375
|
+
sizeDriven: false,
|
|
376
|
+
frameRelative: false,
|
|
377
|
+
});
|
|
378
|
+
});
|
|
379
|
+
test('a fully-contained element gets no overhang block', () => {
|
|
380
|
+
const block = buildStructuralBlock(HUB_PROMO_DUMP, HUB_PROMO_REFERENCE, 2);
|
|
381
|
+
const voucher = block.geometry?.elements.find((e) => e.id === 'voucher');
|
|
382
|
+
expect(voucher?.overhang).toBeUndefined();
|
|
383
|
+
expect(block.summary.maxClipDeltaDp).toBe(0);
|
|
384
|
+
});
|
|
385
|
+
});
|
|
317
386
|
describe('buildReviewScaffold — run-only verdict obligation', () => {
|
|
318
387
|
test('forces a verdict per flagged element + fact, with the golden mustExplain', () => {
|
|
319
388
|
const block = buildStructuralBlock(HUB_PROMO_DUMP, HUB_PROMO_REFERENCE, 2);
|
|
@@ -330,6 +399,19 @@ describe('buildReviewScaffold — run-only verdict obligation', () => {
|
|
|
330
399
|
});
|
|
331
400
|
expect(review.rows.find((r) => r.element === 'copy↔voucher')?.fact).toBe('overlap 160×93px (ratio 0.40)');
|
|
332
401
|
});
|
|
402
|
+
test('a clipFlag element forces a verdict row describing the overhang mismatch', () => {
|
|
403
|
+
const block = buildStructuralBlock(OVERHANG_LOGGED_IN_DUMP, OVERHANG_LOGGED_IN_REF, 2);
|
|
404
|
+
const review = buildReviewScaffold(block);
|
|
405
|
+
expect(review.mustExplain).toContain('voucher');
|
|
406
|
+
const clipRow = review.rows.find((r) => r.element === 'voucher' && r.fact.startsWith('clip'));
|
|
407
|
+
expect(clipRow).toEqual({
|
|
408
|
+
element: 'voucher',
|
|
409
|
+
rendered: 'bottom 16dp',
|
|
410
|
+
design: 'bottom 25dp',
|
|
411
|
+
fact: "clip bottom 16dp vs design 25dp (-9) — driven by the element's own size (h 71 vs 81dp) tracking the frame (parent-relative); reproduce at the fixed design size",
|
|
412
|
+
verdict: null,
|
|
413
|
+
});
|
|
414
|
+
});
|
|
333
415
|
});
|
|
334
416
|
describe('embedded geometry stage parity', () => {
|
|
335
417
|
// The render ships a JS-source port of the differ + assembler. It must produce
|
|
@@ -341,12 +423,18 @@ describe('embedded geometry stage parity', () => {
|
|
|
341
423
|
const reference = { card: { width: 328, height: 72 } };
|
|
342
424
|
expect(embedded.diffKeyGeometry(generated, reference)).toEqual(diffKeyGeometry(generated, reference));
|
|
343
425
|
});
|
|
344
|
-
|
|
426
|
+
const parityCases = [
|
|
427
|
+
{ name: 'factsOnly', dump: HUB_PROMO_DUMP, ref: null },
|
|
428
|
+
{ name: 'withGeometry', dump: HUB_PROMO_DUMP, ref: HUB_PROMO_REFERENCE },
|
|
429
|
+
{ name: 'overhangLoggedIn', dump: OVERHANG_LOGGED_IN_DUMP, ref: OVERHANG_LOGGED_IN_REF },
|
|
430
|
+
{ name: 'overhangGuest', dump: OVERHANG_GUEST_DUMP, ref: OVERHANG_GUEST_REF },
|
|
431
|
+
];
|
|
432
|
+
for (const { name, dump, ref } of parityCases) {
|
|
345
433
|
test(`embedded buildStructuralBlock matches the module (${name})`, () => {
|
|
346
|
-
expect(embedded.buildStructuralBlock(
|
|
434
|
+
expect(embedded.buildStructuralBlock(dump, ref, 2)).toEqual(buildStructuralBlock(dump, ref, 2));
|
|
347
435
|
});
|
|
348
436
|
test(`embedded buildReviewScaffold matches the module (${name})`, () => {
|
|
349
|
-
const block = buildStructuralBlock(
|
|
437
|
+
const block = buildStructuralBlock(dump, ref, 2);
|
|
350
438
|
expect(embedded.buildReviewScaffold(block)).toEqual(buildReviewScaffold(block));
|
|
351
439
|
});
|
|
352
440
|
}
|
|
@@ -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);
|