@invarn/cibuild 2.2.2 → 2.2.4

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.
@@ -263,6 +263,47 @@ export interface AppearanceReference {
263
263
  * color facts are derived against the render's per-node colors. */
264
264
  color?: ColorSource;
265
265
  }
266
+ /** The per-screen reference params the runner assembles an `AppearanceReference`
267
+ * from (the slices of `params.reference_*` for one screen). */
268
+ export interface AppearanceReferenceParams {
269
+ /** Per-screen `reference_geometry` (design/baseline geometry). Its keys seed
270
+ * the element-id set. */
271
+ refGeom?: ReferenceGeometry | null;
272
+ /** Per-screen `reference_text` (text per element id). */
273
+ refText?: TextSource | null;
274
+ /** Per-screen `reference_color` (fill/text color per element id). */
275
+ refColor?: ColorSource | null;
276
+ baseline: AppearanceBaseline;
277
+ }
278
+ /**
279
+ * Assemble the `AppearanceReference` from a screen's reference params. Issue 05
280
+ * decouples the appearance container from `reference_geometry`: the reference is
281
+ * built when ANY of geometry / text / color is present, with `elementIds` = the
282
+ * UNION of their keys (geometry first, then text, then color — deduped, order
283
+ * preserved). So a run carrying `reference_color` but no `reference_geometry` now
284
+ * yields a reference (→ color facts), while a geometry-only run is byte-identical
285
+ * to before (color/text keys are a subset). `text`/`color` are attached only when
286
+ * present. No reference of any kind → null (no appearance container).
287
+ */
288
+ export declare function buildAppearanceReferenceFromParams(params: AppearanceReferenceParams): AppearanceReference | null;
289
+ /**
290
+ * Map a previous catalog render's persisted appearance snapshot
291
+ * (`StructuralAppearance.rendered` — element ids + per-element colors) into the
292
+ * `previous_render` AppearanceReference the next render diffs against. The
293
+ * no-Figma drift path: the colors/ids the next render compares itself to are the
294
+ * LAST render's, so a diverging fill surfaces as a regression (drift), marked
295
+ * `baseline: 'previous_render'`, distinct from a design divergence.
296
+ *
297
+ * Pure (no I/O) — the testable core the thin catalog-fetch adapter wraps,
298
+ * mirroring how the geometry baseline reuses the previous render's persisted
299
+ * `geometry`. `color` is carried only when the previous render exposed colors;
300
+ * a previous render with no extractable color yields a presence-only reference
301
+ * (element ids, no color). A genuinely absent previous render (no snapshot) →
302
+ * null, so the next render carries no appearance container, exactly as before.
303
+ * Issue 01 carries the color channels; the previous render's `text` (copy) is a
304
+ * later slice's concern and is intentionally not mapped here.
305
+ */
306
+ export declare function appearanceReferenceFromPreviousRender(previous: AppearanceRendered | null | undefined): AppearanceReference | null;
266
307
  /**
267
308
  * Element-presence facts from the set difference of the rendered element-id set
268
309
  * and the reference element-id set, keyed on the stable element id shared with
@@ -418,6 +459,7 @@ export declare function getGeometryStageInternals(): {
418
459
  derivePresenceFacts: (renderedIds: Iterable<string>, referenceIds: Iterable<string>) => AppearanceFact[];
419
460
  deriveCopyFacts: (rendered: TextSource, reference: TextSource) => AppearanceFact[];
420
461
  deriveColorFacts: (rendered: ColorSource, reference: ColorSource) => AppearanceFact[];
462
+ buildAppearanceReferenceFromParams: (params: AppearanceReferenceParams) => AppearanceReference | null;
421
463
  ciede2000: (lab1: Lab, lab2: Lab) => number;
422
464
  hexToLab: (hex: string) => Lab;
423
465
  };
@@ -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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"}
@@ -8,7 +8,7 @@
8
8
  * overflowing voucher tag; the deriver must surface the bottom clip as a
9
9
  * fact so it is never again skipped by eye.
10
10
  */
11
- import { deriveStructuralFacts, derivePresenceFacts, deriveCopyFacts, deriveColorFacts, ciede2000, hexToLab, diffKeyGeometry, buildStructuralBlock, buildReviewScaffold, getStructuralFactsStageInternals, getGeometryStageInternals, } from './structural-facts.js';
11
+ import { deriveStructuralFacts, derivePresenceFacts, deriveCopyFacts, deriveColorFacts, ciede2000, hexToLab, diffKeyGeometry, buildStructuralBlock, buildReviewScaffold, appearanceReferenceFromPreviousRender, buildAppearanceReferenceFromParams, getStructuralFactsStageInternals, getGeometryStageInternals, } from './structural-facts.js';
12
12
  // 200x400 canvas in px for all fixtures unless noted.
13
13
  const CANVAS = { left: 0, top: 0, right: 200, bottom: 400 };
14
14
  describe('deriveStructuralFacts — containment', () => {
@@ -741,6 +741,33 @@ describe('buildStructuralBlock — appearance container (Signal C, additive)', (
741
741
  },
742
742
  ]);
743
743
  });
744
+ test('with reference text color → a text-channel color fact (issue 02)', () => {
745
+ // COLOR_DUMP renders `card` text #000000; the reference declares #ffffff for
746
+ // the text channel and the matching fill — so exactly one fact, channel:'text'.
747
+ const block = buildStructuralBlock(COLOR_DUMP, null, 2, undefined, {
748
+ baseline: 'figma',
749
+ elementIds: ['card'],
750
+ color: { card: { fill: '#ff0000', text: '#ffffff' } },
751
+ });
752
+ expect(block.appearance?.facts).toEqual([
753
+ {
754
+ kind: 'color',
755
+ elementId: 'card',
756
+ channel: 'text',
757
+ rendered: '#000000',
758
+ reference: '#ffffff',
759
+ deltaE: ciede2000(hexToLab('#000000'), hexToLab('#ffffff')),
760
+ },
761
+ ]);
762
+ });
763
+ test('an identical text color → no text fact (issue 02)', () => {
764
+ const block = buildStructuralBlock(COLOR_DUMP, null, 2, undefined, {
765
+ baseline: 'figma',
766
+ elementIds: ['card'],
767
+ color: { card: { fill: '#ff0000', text: '#000000' } },
768
+ });
769
+ expect(block.appearance?.facts).toEqual([]);
770
+ });
744
771
  test('an appearanceReference without color → no color facts (presence only)', () => {
745
772
  const block = buildStructuralBlock(COLOR_DUMP, null, 2, undefined, {
746
773
  baseline: 'figma',
@@ -802,6 +829,222 @@ describe('buildStructuralBlock — appearance container (Signal C, additive)', (
802
829
  expect(block.appearance).toBeUndefined();
803
830
  });
804
831
  });
832
+ // ---- Signal C: previous-render → AppearanceReference mapper (issue 01) ----
833
+ //
834
+ // The no-Figma drift path: a previous catalog render persisted its own
835
+ // appearance snapshot (`appearance.rendered`: element ids + per-element colors).
836
+ // This pure mapper turns that snapshot into the `previous_render`
837
+ // AppearanceReference the next render diffs against — the colors/ids to compare,
838
+ // marked as drift (regression vs the last render), not a design divergence. It is
839
+ // the testable core the thin catalog-fetch adapter wraps; no I/O here.
840
+ describe('appearanceReferenceFromPreviousRender — previous render snapshot → reference', () => {
841
+ test('a previous render with per-element fill colors → a previous_render reference carrying color + ids', () => {
842
+ const previous = {
843
+ elementIds: ['banner', 'voucher'],
844
+ color: { banner: { fill: '#fff0ee' } },
845
+ };
846
+ expect(appearanceReferenceFromPreviousRender(previous)).toEqual({
847
+ baseline: 'previous_render',
848
+ elementIds: ['banner', 'voucher'],
849
+ color: { banner: { fill: '#fff0ee' } },
850
+ });
851
+ });
852
+ test('a previous render with per-element text colors → the text channel rides through into the reference (issue 02)', () => {
853
+ // The mapper carries the whole previous color map, so the foreground (text)
854
+ // channel reaches the reference exactly as the fill channel does — channel
855
+ // genericity, not a fill-only carry.
856
+ const previous = {
857
+ elementIds: ['banner', 'copy'],
858
+ color: { banner: { fill: '#fff0ee' }, copy: { text: '#1b1b1b' } },
859
+ };
860
+ expect(appearanceReferenceFromPreviousRender(previous)).toEqual({
861
+ baseline: 'previous_render',
862
+ elementIds: ['banner', 'copy'],
863
+ color: { banner: { fill: '#fff0ee' }, copy: { text: '#1b1b1b' } },
864
+ });
865
+ });
866
+ test('a previous render with no extractable color → a presence-only reference (ids, no color)', () => {
867
+ const previous = { elementIds: ['banner', 'voucher'] };
868
+ expect(appearanceReferenceFromPreviousRender(previous)).toEqual({
869
+ baseline: 'previous_render',
870
+ elementIds: ['banner', 'voucher'],
871
+ });
872
+ });
873
+ test('an empty color map is treated as no color signal (presence-only)', () => {
874
+ const previous = { elementIds: ['banner'], color: {} };
875
+ expect(appearanceReferenceFromPreviousRender(previous)).toEqual({
876
+ baseline: 'previous_render',
877
+ elementIds: ['banner'],
878
+ });
879
+ });
880
+ test('a genuinely absent previous render → null (no appearance container downstream)', () => {
881
+ expect(appearanceReferenceFromPreviousRender(null)).toBeNull();
882
+ expect(appearanceReferenceFromPreviousRender(undefined)).toBeNull();
883
+ expect(appearanceReferenceFromPreviousRender({})).toBeNull();
884
+ });
885
+ test('fed into buildStructuralBlock, a drifted fill produces a previous_render color fact (the tracer)', () => {
886
+ // The render now paints the banner the wrong fill (#1e88e5); the previous
887
+ // render painted #fff0ee. The mapper turns that previous snapshot into the
888
+ // drift reference, and the assembled block carries one fill color fact with a
889
+ // large positive ΔE — exactly the blue-banner divergence, proven through the
890
+ // public interface (the e2e proves the same on a real render).
891
+ const driftedDump = {
892
+ canvas: { left: 0, top: 0, right: 686, bottom: 282 },
893
+ nodes: [
894
+ { id: 'banner', bounds: { left: 0, top: 0, right: 686, bottom: 282 }, parentId: null, clipsChildren: false, fillColor: '#1e88e5' },
895
+ ],
896
+ };
897
+ const previous = { elementIds: ['banner'], color: { banner: { fill: '#fff0ee' } } };
898
+ const reference = appearanceReferenceFromPreviousRender(previous);
899
+ const block = buildStructuralBlock(driftedDump, null, 2, undefined, reference);
900
+ expect(block.appearance?.baseline).toBe('previous_render');
901
+ const colorFacts = (block.appearance?.facts ?? []).filter((f) => f.kind === 'color');
902
+ expect(colorFacts).toEqual([
903
+ {
904
+ kind: 'color',
905
+ elementId: 'banner',
906
+ channel: 'fill',
907
+ rendered: '#1e88e5',
908
+ reference: '#fff0ee',
909
+ deltaE: ciede2000(hexToLab('#1e88e5'), hexToLab('#fff0ee')),
910
+ },
911
+ ]);
912
+ expect(colorFacts[0].kind === 'color' && colorFacts[0].deltaE).toBeGreaterThan(10);
913
+ });
914
+ test('fed into buildStructuralBlock, a drifted text color produces a previous_render text-channel fact (issue 02)', () => {
915
+ // The render paints `copy` the wrong foreground color (#1e88e5); the previous
916
+ // render painted #1b1b1b. The mapper carries the text channel through, and the
917
+ // assembled block carries one text color fact with a large positive ΔE —
918
+ // distinct from fill, proven through the public interface.
919
+ const driftedDump = {
920
+ canvas: { left: 0, top: 0, right: 686, bottom: 282 },
921
+ nodes: [
922
+ { id: 'copy', bounds: { left: 0, top: 0, right: 343, bottom: 40 }, parentId: null, clipsChildren: false, textColor: '#1e88e5' },
923
+ ],
924
+ };
925
+ const previous = { elementIds: ['copy'], color: { copy: { text: '#1b1b1b' } } };
926
+ const reference = appearanceReferenceFromPreviousRender(previous);
927
+ const block = buildStructuralBlock(driftedDump, null, 2, undefined, reference);
928
+ expect(block.appearance?.baseline).toBe('previous_render');
929
+ const colorFacts = (block.appearance?.facts ?? []).filter((f) => f.kind === 'color');
930
+ expect(colorFacts).toEqual([
931
+ {
932
+ kind: 'color',
933
+ elementId: 'copy',
934
+ channel: 'text',
935
+ rendered: '#1e88e5',
936
+ reference: '#1b1b1b',
937
+ deltaE: ciede2000(hexToLab('#1e88e5'), hexToLab('#1b1b1b')),
938
+ },
939
+ ]);
940
+ expect(colorFacts[0].kind === 'color' && colorFacts[0].deltaE).toBeGreaterThan(10);
941
+ });
942
+ test('a correct fill with a wrong text color yields a text-channel fact only — the two never collapse (issue 02)', () => {
943
+ // One element carries both channels: fill matches the previous render, text
944
+ // drifts. Exactly one fact, channel:'text' — the correct fill produces none.
945
+ const dump = {
946
+ canvas: { left: 0, top: 0, right: 343, bottom: 72 },
947
+ nodes: [
948
+ { id: 'banner', bounds: { left: 0, top: 0, right: 343, bottom: 72 }, parentId: null, clipsChildren: false, fillColor: '#fff0ee', textColor: '#1e88e5' },
949
+ ],
950
+ };
951
+ const previous = {
952
+ elementIds: ['banner'],
953
+ color: { banner: { fill: '#fff0ee', text: '#1b1b1b' } },
954
+ };
955
+ const block = buildStructuralBlock(dump, null, 2, undefined, appearanceReferenceFromPreviousRender(previous));
956
+ const colorFacts = (block.appearance?.facts ?? []).filter((f) => f.kind === 'color');
957
+ expect(colorFacts).toEqual([
958
+ {
959
+ kind: 'color',
960
+ elementId: 'banner',
961
+ channel: 'text',
962
+ rendered: '#1e88e5',
963
+ reference: '#1b1b1b',
964
+ deltaE: ciede2000(hexToLab('#1e88e5'), hexToLab('#1b1b1b')),
965
+ },
966
+ ]);
967
+ });
968
+ });
969
+ // ---- Signal C: build the appearance reference from run params (issue 05) ----
970
+ //
971
+ // The runner builds the appearanceReference from the per-screen reference_geometry
972
+ // / reference_text / reference_color params. Issue 05 decouples the container from
973
+ // reference_geometry: it is assembled when ANY of the three is present, with
974
+ // elementIds = the UNION of their keys, so a run with reference_color but no
975
+ // reference_geometry still emits color facts.
976
+ describe('buildAppearanceReferenceFromParams — union of geometry/text/color ids', () => {
977
+ test('color only (no geometry) → a reference carrying color, ids from the color keys', () => {
978
+ const ref = buildAppearanceReferenceFromParams({
979
+ refColor: { banner: { fill: '#fff0ee' } },
980
+ baseline: 'previous_render',
981
+ });
982
+ expect(ref).toEqual({
983
+ baseline: 'previous_render',
984
+ elementIds: ['banner'],
985
+ color: { banner: { fill: '#fff0ee' } },
986
+ });
987
+ });
988
+ test('geometry only → ids from the geometry keys, no text/color attached', () => {
989
+ const ref = buildAppearanceReferenceFromParams({
990
+ refGeom: { banner: { w: 343, h: 96 }, voucher: { w: 81, h: 81 } },
991
+ baseline: 'figma',
992
+ });
993
+ expect(ref).toEqual({ baseline: 'figma', elementIds: ['banner', 'voucher'] });
994
+ });
995
+ test('geometry + color (color ids ⊆ geometry ids) → ids unchanged (additive, no regression)', () => {
996
+ const ref = buildAppearanceReferenceFromParams({
997
+ refGeom: { banner: { w: 343, h: 96 }, voucher: { w: 81, h: 81 } },
998
+ refColor: { banner: { fill: '#fff0ee' } },
999
+ baseline: 'figma',
1000
+ });
1001
+ expect(ref).toEqual({
1002
+ baseline: 'figma',
1003
+ elementIds: ['banner', 'voucher'],
1004
+ color: { banner: { fill: '#fff0ee' } },
1005
+ });
1006
+ });
1007
+ test('text + color, no geometry → union ids, both attached', () => {
1008
+ const ref = buildAppearanceReferenceFromParams({
1009
+ refText: { copy: 'Get 5 € off' },
1010
+ refColor: { banner: { fill: '#fff0ee' } },
1011
+ baseline: 'previous_render',
1012
+ });
1013
+ expect(ref).toEqual({
1014
+ baseline: 'previous_render',
1015
+ elementIds: ['copy', 'banner'],
1016
+ text: { copy: 'Get 5 € off' },
1017
+ color: { banner: { fill: '#fff0ee' } },
1018
+ });
1019
+ });
1020
+ test('none present → null (no appearance container)', () => {
1021
+ expect(buildAppearanceReferenceFromParams({ baseline: 'figma' })).toBeNull();
1022
+ expect(buildAppearanceReferenceFromParams({ baseline: 'figma', refGeom: {}, refColor: {}, refText: {} })).toBeNull();
1023
+ });
1024
+ test('fed into buildStructuralBlock, a color-only reference still emits the color fact', () => {
1025
+ const dump = {
1026
+ canvas: { left: 0, top: 0, right: 686, bottom: 282 },
1027
+ nodes: [
1028
+ { id: 'banner', bounds: { left: 0, top: 0, right: 686, bottom: 282 }, parentId: null, clipsChildren: false, fillColor: '#1e88e5' },
1029
+ ],
1030
+ };
1031
+ const ref = buildAppearanceReferenceFromParams({
1032
+ refColor: { banner: { fill: '#fff0ee' } },
1033
+ baseline: 'previous_render',
1034
+ });
1035
+ const block = buildStructuralBlock(dump, null, 2, undefined, ref);
1036
+ expect(block.appearance?.facts).toEqual([
1037
+ {
1038
+ kind: 'color',
1039
+ elementId: 'banner',
1040
+ channel: 'fill',
1041
+ rendered: '#1e88e5',
1042
+ reference: '#fff0ee',
1043
+ deltaE: ciede2000(hexToLab('#1e88e5'), hexToLab('#fff0ee')),
1044
+ },
1045
+ ]);
1046
+ });
1047
+ });
805
1048
  describe('embedded geometry stage parity', () => {
806
1049
  // The render ships a JS-source port of the differ + assembler. It must produce
807
1050
  // byte-identical blocks to the canonical typed module, or run-side structural
@@ -871,6 +1114,20 @@ describe('embedded geometry stage parity', () => {
871
1114
  expect(embedded.hexToLab(hex)).toEqual(hexToLab(hex));
872
1115
  }
873
1116
  });
1117
+ test('embedded buildAppearanceReferenceFromParams matches the module (Signal C, issue 05)', () => {
1118
+ const cases = [
1119
+ { refColor: { banner: { fill: '#fff0ee' } }, baseline: 'previous_render' },
1120
+ { refGeom: { banner: { w: 343, h: 96 }, voucher: { w: 81, h: 81 } }, baseline: 'figma' },
1121
+ { refGeom: { banner: { w: 343 } }, refColor: { banner: { fill: '#fff0ee' } }, baseline: 'figma' },
1122
+ { refText: { copy: 'Get 5 € off' }, refColor: { banner: { fill: '#fff0ee' } }, baseline: 'previous_render' },
1123
+ { refGeom: { banner: { w: 1 } }, refText: { copy: 'x' }, refColor: { logo: { fill: '#000000' } }, baseline: 'figma' },
1124
+ { baseline: 'figma' },
1125
+ { refGeom: {}, refText: {}, refColor: {}, baseline: 'figma' },
1126
+ ];
1127
+ for (const c of cases) {
1128
+ expect(embedded.buildAppearanceReferenceFromParams(c)).toEqual(buildAppearanceReferenceFromParams(c));
1129
+ }
1130
+ });
874
1131
  test('embedded deriveColorFacts matches the module (Signal C)', () => {
875
1132
  const cases = [
876
1133
  [{ card: { fill: '#ff0000' } }, { card: { fill: '#ff0000' } }],
@@ -897,6 +1154,22 @@ describe('embedded geometry stage parity', () => {
897
1154
  };
898
1155
  expect(embedded.buildStructuralBlock(dumpWithColor, null, 2, undefined, appearanceReference)).toEqual(buildStructuralBlock(dumpWithColor, null, 2, undefined, appearanceReference));
899
1156
  });
1157
+ test('embedded buildStructuralBlock matches the module for a text-only color drift (issue 02)', () => {
1158
+ // Correct fill + wrong text: the embedded port must emit the same single
1159
+ // text-channel fact (no collapse), matching the typed module exactly.
1160
+ const dumpWithText = {
1161
+ canvas: { left: 0, top: 0, right: 343, bottom: 72 },
1162
+ nodes: [
1163
+ { id: 'banner', bounds: { left: 0, top: 0, right: 343, bottom: 72 }, parentId: null, clipsChildren: false, fillColor: '#fff0ee', textColor: '#1e88e5' },
1164
+ ],
1165
+ };
1166
+ const appearanceReference = {
1167
+ baseline: 'previous_render',
1168
+ elementIds: ['banner'],
1169
+ color: { banner: { fill: '#fff0ee', text: '#1b1b1b' } },
1170
+ };
1171
+ expect(embedded.buildStructuralBlock(dumpWithText, null, 2, undefined, appearanceReference)).toEqual(buildStructuralBlock(dumpWithText, null, 2, undefined, appearanceReference));
1172
+ });
900
1173
  const parityCases = [
901
1174
  { name: 'factsOnly', dump: HUB_PROMO_DUMP, ref: null },
902
1175
  { name: 'withGeometry', dump: HUB_PROMO_DUMP, ref: HUB_PROMO_REFERENCE },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@invarn/cibuild",
3
- "version": "2.2.2",
3
+ "version": "2.2.4",
4
4
  "description": "CI Build CLI — local pipeline orchestration and validation",
5
5
  "type": "module",
6
6
  "main": "dist/cli.cjs",