@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.
@@ -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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"}
@@ -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
- for (const [name, ref] of Object.entries({ factsOnly: null, withGeometry: HUB_PROMO_REFERENCE })) {
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(HUB_PROMO_DUMP, ref, 2)).toEqual(buildStructuralBlock(HUB_PROMO_DUMP, ref, 2));
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(HUB_PROMO_DUMP, ref, 2);
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);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@invarn/cibuild",
3
- "version": "2.1.4",
3
+ "version": "2.1.6",
4
4
  "description": "CI Build CLI — local pipeline orchestration and validation",
5
5
  "type": "module",
6
6
  "main": "dist/cli.cjs",