@invarn/cibuild 2.1.2 → 2.1.3

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.
@@ -1,10 +1,10 @@
1
1
  /**
2
2
  * Structural-facts deriver — Signal A of the UI-fidelity structural pass.
3
3
  *
4
- * Pure and platform-agnostic: it takes a per-node layout dump (bounds in px,
5
- * parent, clip flag) emitted alongside the render and derives intent-agnostic
6
- * geometric facts the fidelity judge should check instead of discovering them
7
- * by eye:
4
+ * Pure and platform-agnostic: it takes a per-node layout dump (clipped +
5
+ * unclipped bounds in px, parent) emitted alongside the render and derives
6
+ * intent-agnostic geometric facts the fidelity judge should check instead of
7
+ * discovering them by eye:
8
8
  *
9
9
  * - `overflow` — a node extends past its parent's box (parent does
10
10
  * NOT clip, so the excess is visibly sticking out).
@@ -26,11 +26,18 @@ export interface LayoutBounds {
26
26
  export interface LayoutNode {
27
27
  /** Stable id/label for the node within one dump. */
28
28
  id: string;
29
- /** Root-space bounds in px. */
29
+ /** Clipped root-space bounds in px (boundsInRoot — the visible region,
30
+ * clamped by any clipping ancestor). */
30
31
  bounds: LayoutBounds;
32
+ /** Unclipped root-space bounds in px (positionInRoot + size — the true layout
33
+ * extent). Absent in legacy dumps emitted before the structural pass tracked
34
+ * it; when present, overflow/clip is derived from this so it survives a
35
+ * clamping parent (which would otherwise hide the excess in `bounds`). */
36
+ unclippedBounds?: LayoutBounds;
31
37
  /** Parent node id, or null for a root node. */
32
38
  parentId: string | null;
33
- /** Whether this node clips its children to its bounds. */
39
+ /** Legacy clip hint (parent clips its children). The current emitter writes a
40
+ * constant false; used only for the legacy (no-`unclippedBounds`) fallback. */
34
41
  clipsChildren: boolean;
35
42
  }
36
43
  export interface LayoutDump {
@@ -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,+BAA+B;IAC/B,MAAM,EAAE,YAAY,CAAC;IACrB,+CAA+C;IAC/C,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,0DAA0D;IAC1D,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,CA2ElB;AAED;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,yBAAyB,EAAE,MAuHvC,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":"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"}
@@ -63,6 +63,75 @@ describe('deriveStructuralFacts — overflow vs clip', () => {
63
63
  });
64
64
  });
65
65
  });
66
+ describe('deriveStructuralFacts — unclippedBounds path (real clamping renders)', () => {
67
+ // A real clipping parent CLAMPS the child's boundsInRoot to its own edge, so
68
+ // the clipped `bounds` show no excess — the overflow lives only in
69
+ // `unclippedBounds`. This is the case bounds-only v1 went blind on.
70
+ test('a clamped child is reported clipped-by-parent, recovered from unclippedBounds', () => {
71
+ const dump = {
72
+ canvas: { left: 0, top: 0, right: 360, bottom: 720 },
73
+ nodes: [
74
+ {
75
+ id: 'HubPromoBanner',
76
+ bounds: { left: 16, top: 100, right: 344, bottom: 262 },
77
+ unclippedBounds: { left: 16, top: 100, right: 344, bottom: 262 },
78
+ parentId: null,
79
+ clipsChildren: false,
80
+ },
81
+ {
82
+ // visible bounds CLAMPED to the card bottom (262); the true extent runs
83
+ // 18px past it → 18px cut away. The current emitter writes clipsChildren
84
+ // false, so only unclippedBounds can reveal this.
85
+ id: 'voucherTag',
86
+ bounds: { left: 250, top: 210, right: 330, bottom: 262 },
87
+ unclippedBounds: { left: 250, top: 210, right: 330, bottom: 280 },
88
+ parentId: 'HubPromoBanner',
89
+ clipsChildren: false,
90
+ },
91
+ ],
92
+ };
93
+ const facts = deriveStructuralFacts(dump);
94
+ expect(facts).toHaveLength(1);
95
+ expect(facts[0]).toMatchObject({
96
+ kind: 'clipped_by_parent',
97
+ nodeId: 'voucherTag',
98
+ clipperId: 'HubPromoBanner',
99
+ edges: ['bottom'],
100
+ clippedPx: { bottom: 18 },
101
+ });
102
+ });
103
+ test('a child sticking out of a non-clipping parent (bounds == unclippedBounds) is overflow', () => {
104
+ const dump = {
105
+ canvas: CANVAS,
106
+ nodes: [
107
+ {
108
+ id: 'row',
109
+ bounds: { left: 20, top: 20, right: 180, bottom: 100 },
110
+ unclippedBounds: { left: 20, top: 20, right: 180, bottom: 100 },
111
+ parentId: null,
112
+ clipsChildren: false,
113
+ },
114
+ {
115
+ // not clamped (bounds == unclipped) → the 15px excess is visible.
116
+ id: 'chip',
117
+ bounds: { left: 150, top: 30, right: 195, bottom: 80 },
118
+ unclippedBounds: { left: 150, top: 30, right: 195, bottom: 80 },
119
+ parentId: 'row',
120
+ clipsChildren: false,
121
+ },
122
+ ],
123
+ };
124
+ const facts = deriveStructuralFacts(dump);
125
+ expect(facts).toHaveLength(1);
126
+ expect(facts[0]).toMatchObject({
127
+ kind: 'overflow',
128
+ nodeId: 'chip',
129
+ parentId: 'row',
130
+ edges: ['right'],
131
+ overflowPx: { right: 15 },
132
+ });
133
+ });
134
+ });
66
135
  describe('deriveStructuralFacts — off-screen', () => {
67
136
  test('a node extending past the canvas is reported off-screen on those edges', () => {
68
137
  const dump = {
@@ -138,6 +207,22 @@ describe('embedded render-side stage parity', () => {
138
207
  { id: 'b', bounds: { left: 80, top: 60, right: 180, bottom: 160 }, parentId: 'root', clipsChildren: false },
139
208
  ],
140
209
  },
210
+ // New unclippedBounds path: a clamped child (clip) and a visible-excess child
211
+ // (overflow) — exercises the embedded port's new branch, not just the legacy one.
212
+ clipReal: {
213
+ canvas: CANVAS,
214
+ nodes: [
215
+ { id: 'card', bounds: { left: 20, top: 20, right: 180, bottom: 200 }, unclippedBounds: { left: 20, top: 20, right: 180, bottom: 200 }, parentId: null, clipsChildren: false },
216
+ { id: 'tag', bounds: { left: 60, top: 150, right: 140, bottom: 200 }, unclippedBounds: { left: 60, top: 150, right: 140, bottom: 224 }, parentId: 'card', clipsChildren: false },
217
+ ],
218
+ },
219
+ overflowReal: {
220
+ canvas: CANVAS,
221
+ nodes: [
222
+ { id: 'row', bounds: { left: 20, top: 20, right: 180, bottom: 100 }, unclippedBounds: { left: 20, top: 20, right: 180, bottom: 100 }, parentId: null, clipsChildren: false },
223
+ { id: 'chip', bounds: { left: 150, top: 30, right: 195, bottom: 80 }, unclippedBounds: { left: 150, top: 30, right: 195, bottom: 80 }, parentId: 'row', clipsChildren: false },
224
+ ],
225
+ },
141
226
  };
142
227
  for (const [name, dump] of Object.entries(fixtures)) {
143
228
  test(`embedded port matches the module for the ${name} fixture`, () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@invarn/cibuild",
3
- "version": "2.1.2",
3
+ "version": "2.1.3",
4
4
  "description": "CI Build CLI — local pipeline orchestration and validation",
5
5
  "type": "module",
6
6
  "main": "dist/cli.cjs",