@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.
@@ -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":"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;AAkBD,iFAAiF;AACjF,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,CAAC,CAAC,EAAE,MAAM,CAAC;IACX,CAAC,CAAC,EAAE,MAAM,CAAC;CACZ;AACD,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;AAC7D,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,OAAO,GAAG,QAAQ,GAAG,GAAG,GAAG,GAAG,CAAC;IAC1C,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;CACf;AACD,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,cAAc,EAAE,CAAC;CAC1B;AAKD;;;;;GAKG;AACH,wBAAgB,eAAe,CAC7B,SAAS,EAAE,cAAc,EACzB,SAAS,EAAE,cAAc,GAAG,IAAI,GAAG,SAAS,EAC5C,OAAO,GAAE;IAAE,WAAW,CAAC,EAAE,MAAM,CAAA;CAAO,GACrC,aAAa,EAAE,CAqBjB;AAED,gFAAgF;AAChF,MAAM,MAAM,iBAAiB,GAAG,MAAM,CACpC,MAAM,EACN;IAAE,CAAC,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,CAAC,EAAE,MAAM,CAAA;CAAE,CACnD,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,IAAI,CAAC;IACX,QAAQ,EAAE,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC;IACxC,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC;IACtC,MAAM,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,IAAI,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC7C,QAAQ,EAAE,OAAO,CAAC;CACnB;AACD,MAAM,WAAW,yBAAyB;IACxC,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACnC,MAAM,EAAE;QAAE,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;QAAC,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;KAAE,CAAC;IACzD,MAAM,EAAE,KAAK,CAAC;QAAE,SAAS,EAAE,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACnE,QAAQ,EAAE,OAAO,CAAC;IAClB,oEAAoE;IACpE,QAAQ,CAAC,EAAE,kBAAkB,CAAC;CAC/B;AACD,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,IAAI,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,yBAAyB,EAAE,CAAC;CACvC;AACD,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,OAAO,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,yEAAyE;IACzE,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B;AACD,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,iBAAiB,CAAC;IAC3B,KAAK,EAAE,cAAc,EAAE,CAAC;IACxB,QAAQ,CAAC,EAAE,kBAAkB,CAAC;CAC/B;AA2GD;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,UAAU,EAChB,iBAAiB,CAAC,EAAE,iBAAiB,GAAG,IAAI,EAC5C,OAAO,CAAC,EAAE,MAAM,EAChB,OAAO,GAAE;IAAE,eAAe,CAAC,EAAE,MAAM,CAAC;IAAC,mBAAmB,CAAC,EAAE,MAAM,CAAA;CAAO,GACvE,eAAe,CA6EjB;AAaD,MAAM,WAAW,SAAS;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,IAAI,CAAC;CACf;AACD,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,IAAI,CAAC;IACd,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,IAAI,EAAE,SAAS,EAAE,CAAC;CACnB;AAwDD;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,UAAU,EAAE;IAC9C,KAAK,CAAC,EAAE,cAAc,EAAE,CAAC;IACzB,QAAQ,CAAC,EAAE,kBAAkB,GAAG,IAAI,CAAC;CACtC,GAAG,cAAc,CA2CjB;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;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,4BAA4B,EAAE,MAmV1C,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,yBAAyB,IAAI;IAC3C,eAAe,EAAE,CACf,SAAS,EAAE,cAAc,EACzB,SAAS,EAAE,cAAc,GAAG,IAAI,GAAG,SAAS,EAC5C,OAAO,CAAC,EAAE;QAAE,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE,KAC/B,aAAa,EAAE,CAAC;IACrB,oBAAoB,EAAE,CACpB,IAAI,EAAE,UAAU,EAChB,iBAAiB,CAAC,EAAE,iBAAiB,GAAG,IAAI,EAC5C,OAAO,CAAC,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE;QAAE,eAAe,CAAC,EAAE,MAAM,CAAC;QAAC,mBAAmB,CAAC,EAAE,MAAM,CAAA;KAAE,KACjE,eAAe,CAAC;IACrB,mBAAmB,EAAE,CAAC,UAAU,EAAE;QAChC,KAAK,CAAC,EAAE,cAAc,EAAE,CAAC;QACzB,QAAQ,CAAC,EAAE,kBAAkB,GAAG,IAAI,CAAC;KACtC,KAAK,cAAc,CAAC;CACtB,CAOA"}
@@ -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
- for (const [name, ref] of Object.entries({ factsOnly: null, withGeometry: HUB_PROMO_REFERENCE })) {
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(HUB_PROMO_DUMP, ref, 2)).toEqual(buildStructuralBlock(HUB_PROMO_DUMP, ref, 2));
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(HUB_PROMO_DUMP, ref, 2);
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);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@invarn/cibuild",
3
- "version": "2.1.4",
3
+ "version": "2.1.5",
4
4
  "description": "CI Build CLI — local pipeline orchestration and validation",
5
5
  "type": "module",
6
6
  "main": "dist/cli.cjs",