circuitjson-toolkit 1.0.10 → 1.0.17

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.
Files changed (40) hide show
  1. package/AGENTS.md +20 -3
  2. package/docs/model-format.md +15 -0
  3. package/package.json +3 -2
  4. package/src/core/CircuitJsonBomBuilder.mjs +22 -25
  5. package/src/core/CircuitJsonElementValidator.mjs +233 -16
  6. package/src/core/CircuitJsonIndexer.mjs +510 -5
  7. package/src/core/CircuitJsonManufacturingBuilder.mjs +488 -16
  8. package/src/core/CircuitJsonManufacturingDownloadBuilder.mjs +196 -0
  9. package/src/core/CircuitJsonPcbClearanceDiagnostics.mjs +329 -0
  10. package/src/core/CircuitJsonPcbCopperGeometry.mjs +503 -0
  11. package/src/core/CircuitJsonPcbDrawingStyle.mjs +88 -0
  12. package/src/core/CircuitJsonPcbHolePrimitiveModel.mjs +172 -0
  13. package/src/core/CircuitJsonPcbNetMetadata.mjs +247 -0
  14. package/src/core/CircuitJsonPcbPadPrimitiveModel.mjs +70 -0
  15. package/src/core/CircuitJsonPcbPrimitiveArtwork.mjs +992 -0
  16. package/src/core/CircuitJsonPcbPrimitiveBuilder.mjs +872 -0
  17. package/src/core/CircuitJsonPcbPrimitiveFields.mjs +233 -0
  18. package/src/core/CircuitJsonPcbPrimitiveGeometry.mjs +142 -0
  19. package/src/core/CircuitJsonPcbPrimitiveGroups.mjs +305 -0
  20. package/src/core/CircuitJsonPcbPrimitiveIndex.mjs +65 -0
  21. package/src/core/CircuitJsonPcbPrimitiveOverlays.mjs +895 -0
  22. package/src/core/CircuitJsonPcbTraceLengthModel.mjs +257 -0
  23. package/src/core/CircuitJsonPcbZonePrimitiveBuilder.mjs +683 -0
  24. package/src/core/CircuitJsonSourceMetadata.mjs +233 -0
  25. package/src/core/CircuitJsonSupportMatrixBuilder.mjs +227 -5
  26. package/src/core/PcbBoundsSelectionModel.mjs +250 -0
  27. package/src/core/PcbCandidateSelectionModel.mjs +77 -0
  28. package/src/core/PcbDiagnosticFocusModel.mjs +423 -0
  29. package/src/core/PcbInteractionPrimitiveModel.mjs +560 -0
  30. package/src/core/SelectedPartCircuitJsonExportAdapter.mjs +335 -0
  31. package/src/index.mjs +2 -0
  32. package/src/renderers.mjs +29 -0
  33. package/src/ui/CircuitJsonPcbPrimitiveAttributeRenderer.mjs +128 -0
  34. package/src/ui/CircuitJsonPcbSvgRenderer.mjs +964 -0
  35. package/src/ui/CircuitJsonPcbViaSvgRenderer.mjs +168 -0
  36. package/src/ui/CircuitJsonSchematicSvgArcPath.mjs +138 -0
  37. package/src/ui/CircuitJsonSchematicSvgPortMetadata.mjs +114 -0
  38. package/src/ui/CircuitJsonSchematicSvgPrimitiveAttributes.mjs +130 -0
  39. package/src/ui/CircuitJsonSchematicSvgRenderer.mjs +994 -0
  40. package/src/ui/CircuitJsonSchematicTableSvgRenderer.mjs +439 -0
package/AGENTS.md CHANGED
@@ -34,9 +34,11 @@
34
34
  ## Library Scope
35
35
 
36
36
  - Include CircuitJSON element-array validation, parsing, indexing, unit helpers,
37
- and small summary helpers.
38
- - Do not include renderer code, Three.js code, ECAD parser logic, UI wiring, or
39
- source-format-specific compatibility adapters.
37
+ deterministic renderer-neutral primitive helpers, deterministic SVG renderers,
38
+ and small summary/export helpers.
39
+ - Do not include Three.js code, ECAD parser logic, UI wiring, host app state,
40
+ DOM event orchestration, download UI, or source-format-specific compatibility
41
+ adapters.
40
42
 
41
43
  ## Testing Guidelines
42
44
 
@@ -45,6 +47,21 @@
45
47
  - Keep tests focused on observable CircuitJSON utility behavior.
46
48
  - Tests must use small fake CircuitJSON samples only.
47
49
 
50
+ ## Fix Quality Rules
51
+
52
+ - Fixes must always address the general behavior, not a specific example,
53
+ fixture, file, project, or test case.
54
+ - Do not implement workarounds, cheats, allowlists, hard-coded example handling,
55
+ or special-case logic to make one sample pass.
56
+ - When you encounter existing workaround code, cheating behavior, or
57
+ example-specific handling, rewrite it into general-purpose behavior
58
+ immediately when it is in scope for the change.
59
+ - Keep fixes universal and structural: derive behavior from the underlying data
60
+ model, format, protocol, or UI contract instead of matching known sample text,
61
+ filenames, labels, or project identifiers.
62
+ - After fixing code, run the appropriate repo-owned tests and do not modify tests
63
+ just to make a workaround pass.
64
+
48
65
  ## Security & Configuration Tips
49
66
 
50
67
  - Treat CircuitJSON files as untrusted input.
@@ -63,6 +63,21 @@ Indexed element IDs are stored with a type prefix, such as
63
63
  `pcb_board:board_1`, so different element classes can reuse local IDs without
64
64
  colliding.
65
65
 
66
+ PCB courtyard artwork rows use the normal `type_id` identifier convention. The
67
+ validator accepts generic `pcb_courtyard` rows plus shape-specific variants such
68
+ as `pcb_courtyard_rect`, `pcb_courtyard_circle`, `pcb_courtyard_outline`,
69
+ `pcb_courtyard_path`, and `pcb_courtyard_line`.
70
+
71
+ ## Group Indexing
72
+
73
+ `CircuitJsonIndexer` builds `groupsById` and `elementsByGroupId` from direct
74
+ group fields such as `source_group_id`, `pcb_group_id`,
75
+ `schematic_group_id`, `group_id`, and `group_ids`. It also treats member-style
76
+ fields such as `member_source_group_ids`, `member_pcb_group_ids`,
77
+ `member_schematic_group_ids`, and `member_group_ids` as group memberships, so
78
+ source nets and other non-group elements can participate in group indexes
79
+ without duplicating direct group fields.
80
+
66
81
  ## Units
67
82
 
68
83
  CircuitJSON PCB coordinates and dimensions are millimeter-based. The toolkit
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "circuitjson-toolkit",
3
- "version": "1.0.10",
3
+ "version": "1.0.17",
4
4
  "description": "Dependency-free CircuitJSON parsing, validation, indexing, and utility helpers",
5
5
  "keywords": [
6
6
  "circuitjson",
@@ -28,7 +28,8 @@
28
28
  "main": "./src/index.mjs",
29
29
  "exports": {
30
30
  ".": "./src/index.mjs",
31
- "./parser": "./src/parser.mjs"
31
+ "./parser": "./src/parser.mjs",
32
+ "./renderers": "./src/renderers.mjs"
32
33
  },
33
34
  "files": [
34
35
  "src",
@@ -1,3 +1,5 @@
1
+ import { CircuitJsonSourceMetadata } from './CircuitJsonSourceMetadata.mjs'
2
+
1
3
  /**
2
4
  * Builds app-compatible BOM rows from source component elements.
3
5
  */
@@ -14,16 +16,24 @@ export class CircuitJsonBomBuilder {
14
16
  const designator = CircuitJsonBomBuilder.#designator(component)
15
17
  if (!designator) continue
16
18
 
17
- const key = CircuitJsonBomBuilder.#groupKey(component)
19
+ const metadata =
20
+ CircuitJsonSourceMetadata.normalizeSourceComponent(component)
21
+ const key = CircuitJsonBomBuilder.#groupKey(component, metadata)
18
22
  if (!groups.has(key)) {
19
23
  groups.set(key, {
20
24
  designators: [],
21
25
  quantity: 0,
22
26
  value: CircuitJsonBomBuilder.#value(component),
23
- pattern: CircuitJsonBomBuilder.#pattern(component),
27
+ pattern: CircuitJsonBomBuilder.#pattern(
28
+ component,
29
+ metadata
30
+ ),
24
31
  source: CircuitJsonBomBuilder.#source(component),
25
- supplierPartNumber:
26
- CircuitJsonBomBuilder.#supplierPartNumber(component)
32
+ supplierPartNumber: metadata.supplierPartNumber,
33
+ supplierPartNumbers: metadata.supplierPartNumbers,
34
+ sourceFtype: metadata.sourceFtype,
35
+ componentType: metadata.componentType,
36
+ componentIcon: metadata.componentIcon
27
37
  })
28
38
  }
29
39
  const row = groups.get(key)
@@ -57,14 +67,16 @@ export class CircuitJsonBomBuilder {
57
67
  /**
58
68
  * Builds a stable grouping key from BOM-facing fields.
59
69
  * @param {object} component Source component.
70
+ * @param {object} metadata Normalized source metadata.
60
71
  * @returns {string}
61
72
  */
62
- static #groupKey(component) {
73
+ static #groupKey(component, metadata) {
63
74
  return [
64
75
  CircuitJsonBomBuilder.#value(component),
65
- CircuitJsonBomBuilder.#pattern(component),
76
+ CircuitJsonBomBuilder.#pattern(component, metadata),
66
77
  CircuitJsonBomBuilder.#source(component),
67
- CircuitJsonBomBuilder.#supplierPartNumber(component)
78
+ metadata.supplierPartNumber,
79
+ metadata.sourceFtype
68
80
  ].join('\u001f')
69
81
  }
70
82
 
@@ -88,14 +100,15 @@ export class CircuitJsonBomBuilder {
88
100
  /**
89
101
  * Resolves a footprint/package pattern label.
90
102
  * @param {object} component Source component.
103
+ * @param {object} metadata Normalized source metadata.
91
104
  * @returns {string}
92
105
  */
93
- static #pattern(component) {
106
+ static #pattern(component, metadata) {
94
107
  return String(
95
108
  component.footprint ??
96
109
  component.package ??
97
110
  component.package_name ??
98
- component.ftype ??
111
+ metadata.sourceFtype ??
99
112
  ''
100
113
  ).trim()
101
114
  }
@@ -115,22 +128,6 @@ export class CircuitJsonBomBuilder {
115
128
  ).trim()
116
129
  }
117
130
 
118
- /**
119
- * Resolves a supplier part number.
120
- * @param {object} component Source component.
121
- * @returns {string}
122
- */
123
- static #supplierPartNumber(component) {
124
- if (component.supplier_part_number) {
125
- return String(component.supplier_part_number).trim()
126
- }
127
- const numbers = component.supplier_part_numbers
128
- if (!numbers || typeof numbers !== 'object' || Array.isArray(numbers)) {
129
- return ''
130
- }
131
- return String(Object.values(numbers).find(Boolean) || '').trim()
132
- }
133
-
134
131
  /**
135
132
  * Compares designators using natural ordering.
136
133
  * @param {string} left Left designator.
@@ -16,7 +16,9 @@ const KNOWN_ELEMENT_TYPES = new Set([
16
16
  'pcb_copper_text',
17
17
  'pcb_courtyard',
18
18
  'pcb_courtyard_circle',
19
+ 'pcb_courtyard_line',
19
20
  'pcb_courtyard_outline',
21
+ 'pcb_courtyard_path',
20
22
  'pcb_courtyard_overlap_error',
21
23
  'pcb_courtyard_pill',
22
24
  'pcb_courtyard_polygon',
@@ -111,7 +113,6 @@ const KNOWN_ELEMENT_TYPES = new Set([
111
113
  'source_failed_to_create_component_error',
112
114
  'source_group',
113
115
  'source_i2c_misconfigured_error',
114
- 'source_interconnect',
115
116
  'source_invalid_component_property_error',
116
117
  'source_manually_placed_via',
117
118
  'source_missing_manufacturer_part_number_warning',
@@ -143,6 +144,7 @@ const ID_FIELD_EXCEPTIONS = new Set([
143
144
  ])
144
145
 
145
146
  const SOURCE_COMPONENT_FTYPES = new Set([
147
+ 'interconnect',
146
148
  'simple_ammeter',
147
149
  'simple_battery',
148
150
  'simple_capacitor',
@@ -172,16 +174,9 @@ const SOURCE_COMPONENT_FTYPES = new Set([
172
174
  'simple_voltage_source'
173
175
  ])
174
176
 
175
- const LAYERS = new Set([
176
- 'top',
177
- 'bottom',
178
- 'inner1',
179
- 'inner2',
180
- 'inner3',
181
- 'inner4',
182
- 'inner5',
183
- 'inner6'
184
- ])
177
+ const LAYERS = new Set(
178
+ 'top bottom inner1 inner2 inner3 inner4 inner5 inner6'.split(' ')
179
+ )
185
180
 
186
181
  const SMT_PAD_SHAPES = new Set([
187
182
  'circle',
@@ -189,9 +184,88 @@ const SMT_PAD_SHAPES = new Set([
189
184
  'rotated_rect',
190
185
  'rotated_pill',
191
186
  'pill',
192
- 'polygon'
187
+ 'polygon',
188
+ 'rounded_rect'
189
+ ])
190
+
191
+ const PCB_HOLE_SHAPES = new Set([
192
+ 'circle',
193
+ 'circle_or_square',
194
+ 'oval',
195
+ 'pill',
196
+ 'rect',
197
+ 'rotated_pill',
198
+ 'round'
193
199
  ])
194
200
 
201
+ const PCB_PLATED_HOLE_SHAPES = new Set([
202
+ 'circle',
203
+ 'circular_hole_with_rect_pad',
204
+ 'hole_with_polygon_pad',
205
+ 'oval',
206
+ 'pill',
207
+ 'pill_hole_with_rect_pad',
208
+ 'rotated_pill_hole_with_rect_pad'
209
+ ])
210
+
211
+ const PCB_PLATED_HOLE_HOLE_SHAPES = new Set([
212
+ 'circle',
213
+ 'oval',
214
+ 'pill',
215
+ 'rotated_pill'
216
+ ])
217
+
218
+ const PCB_SOLDER_PASTE_SHAPES = new Set([
219
+ 'circle',
220
+ 'oval',
221
+ 'pill',
222
+ 'rect',
223
+ 'rotated_rect'
224
+ ])
225
+
226
+ const PCB_BOARD_SHAPES = new Set(['polygon', 'rect'])
227
+
228
+ const PCB_CUTOUT_SHAPES = new Set(['circle', 'path', 'polygon', 'rect'])
229
+
230
+ const PCB_COPPER_POUR_SHAPES = new Set(['brep', 'polygon', 'rect'])
231
+
232
+ const SIMULATION_SOURCE_KINDS = new Set(['ac', 'dc'])
233
+
234
+ const SIMULATION_WAVE_SHAPES = new Set(
235
+ 'sawtooth sinewave square triangle'.split(' ')
236
+ )
237
+
238
+ const SIMULATION_EXPERIMENT_METHODS = new Set(['gear', 'trap'])
239
+
240
+ const VARIANT_SETS = {
241
+ sourceComponentFtypes: SOURCE_COMPONENT_FTYPES,
242
+ pcbBoardShapes: PCB_BOARD_SHAPES,
243
+ pcbSmtPadShapes: SMT_PAD_SHAPES,
244
+ pcbHoleShapes: PCB_HOLE_SHAPES,
245
+ pcbPlatedHoleShapes: PCB_PLATED_HOLE_SHAPES,
246
+ pcbPlatedHoleHoleShapes: PCB_PLATED_HOLE_HOLE_SHAPES,
247
+ pcbSolderPasteShapes: PCB_SOLDER_PASTE_SHAPES,
248
+ pcbCutoutShapes: PCB_CUTOUT_SHAPES,
249
+ pcbCopperPourShapes: PCB_COPPER_POUR_SHAPES,
250
+ simulationSourceKinds: SIMULATION_SOURCE_KINDS,
251
+ simulationWaveShapes: SIMULATION_WAVE_SHAPES,
252
+ simulationExperimentMethods: SIMULATION_EXPERIMENT_METHODS
253
+ }
254
+
255
+ const SORTED_VARIANT_SET_NAMES = Object.keys(VARIANT_SETS).sort()
256
+
257
+ /**
258
+ * @typedef {{ set: string, value: string }} CircuitJsonVariantDiff
259
+ */
260
+
261
+ /**
262
+ * @typedef {{ elementTypes: string[], idFieldExceptions: string[], variantSets: Record<string, string[]> }} CircuitJsonSchemaSnapshot
263
+ */
264
+
265
+ /**
266
+ * @typedef {{ matches: boolean, missingElementTypes: string[], unexpectedElementTypes: string[], missingIdFieldExceptions: string[], unexpectedIdFieldExceptions: string[], missingVariants: CircuitJsonVariantDiff[], unexpectedVariants: CircuitJsonVariantDiff[] }} CircuitJsonSchemaSnapshotComparison
267
+ */
268
+
195
269
  /**
196
270
  * Validates serialized CircuitJSON element objects without external runtime
197
271
  * dependencies.
@@ -255,10 +329,35 @@ export class CircuitJsonElementValidator {
255
329
  return [...ID_FIELD_EXCEPTIONS]
256
330
  }
257
331
 
332
+ /**
333
+ * Returns schema metadata suitable for drift snapshots.
334
+ * @returns {CircuitJsonSchemaSnapshot}
335
+ */
336
+ static schemaSnapshot() {
337
+ return {
338
+ elementTypes: CircuitJsonElementValidator.knownElementTypes(),
339
+ idFieldExceptions: CircuitJsonElementValidator.idFieldExceptions(),
340
+ variantSets: CircuitJsonElementValidator.variantSets()
341
+ }
342
+ }
343
+
344
+ /**
345
+ * Returns known variant discriminants from the active schema metadata.
346
+ * @returns {Record<string, string[]>}
347
+ */
348
+ static variantSets() {
349
+ return Object.fromEntries(
350
+ SORTED_VARIANT_SET_NAMES.map((name) => [
351
+ name,
352
+ [...VARIANT_SETS[name]].sort()
353
+ ])
354
+ )
355
+ }
356
+
258
357
  /**
259
358
  * Compares current schema metadata against a saved snapshot.
260
- * @param {{ elementTypes?: string[], idFieldExceptions?: string[] }} snapshot Schema snapshot.
261
- * @returns {{ matches: boolean, missingElementTypes: string[], unexpectedElementTypes: string[], missingIdFieldExceptions: string[], unexpectedIdFieldExceptions: string[] }}
359
+ * @param {{ elementTypes?: string[], idFieldExceptions?: string[], variantSets?: Record<string, string[]> }} snapshot Schema snapshot.
360
+ * @returns {CircuitJsonSchemaSnapshotComparison}
262
361
  */
263
362
  static compareSchemaSnapshot(snapshot = {}) {
264
363
  const elementComparison = CircuitJsonElementValidator.#compareSets(
@@ -269,17 +368,25 @@ export class CircuitJsonElementValidator {
269
368
  new Set(snapshot.idFieldExceptions || []),
270
369
  ID_FIELD_EXCEPTIONS
271
370
  )
371
+ const variantComparison =
372
+ CircuitJsonElementValidator.#compareVariantSets(
373
+ snapshot.variantSets || {}
374
+ )
272
375
 
273
376
  return {
274
377
  matches:
275
378
  elementComparison.missing.length === 0 &&
276
379
  elementComparison.unexpected.length === 0 &&
277
380
  exceptionComparison.missing.length === 0 &&
278
- exceptionComparison.unexpected.length === 0,
381
+ exceptionComparison.unexpected.length === 0 &&
382
+ variantComparison.missing.length === 0 &&
383
+ variantComparison.unexpected.length === 0,
279
384
  missingElementTypes: elementComparison.missing,
280
385
  unexpectedElementTypes: elementComparison.unexpected,
281
386
  missingIdFieldExceptions: exceptionComparison.missing,
282
- unexpectedIdFieldExceptions: exceptionComparison.unexpected
387
+ unexpectedIdFieldExceptions: exceptionComparison.unexpected,
388
+ missingVariants: variantComparison.missing,
389
+ unexpectedVariants: variantComparison.unexpected
283
390
  }
284
391
  }
285
392
 
@@ -342,6 +449,14 @@ export class CircuitJsonElementValidator {
342
449
 
343
450
  if (type === 'pcb_smtpad') {
344
451
  CircuitJsonElementValidator.#validatePcbSmtPad(element, errors)
452
+ return
453
+ }
454
+
455
+ if (type === 'simulation_oscilloscope_trace') {
456
+ CircuitJsonElementValidator.#validateSimulationOscilloscopeTrace(
457
+ element,
458
+ errors
459
+ )
345
460
  }
346
461
  }
347
462
 
@@ -566,6 +681,55 @@ export class CircuitJsonElementValidator {
566
681
  }
567
682
  }
568
683
 
684
+ /**
685
+ * Validates oscilloscope trace reference and unit constraints.
686
+ * @param {Record<string, unknown>} element Element.
687
+ * @param {string[]} errors Error sink.
688
+ * @returns {void}
689
+ */
690
+ static #validateSimulationOscilloscopeTrace(element, errors) {
691
+ const voltageReferences = [
692
+ element.simulation_transient_voltage_graph_id,
693
+ element.simulation_voltage_probe_id
694
+ ].filter((value) =>
695
+ CircuitJsonElementValidator.#isNonEmptyString(value)
696
+ ).length
697
+ const currentReferences = [
698
+ element.simulation_transient_current_graph_id,
699
+ element.simulation_current_probe_id
700
+ ].filter((value) =>
701
+ CircuitJsonElementValidator.#isNonEmptyString(value)
702
+ ).length
703
+
704
+ if (voltageReferences + currentReferences !== 1) {
705
+ errors.push(
706
+ 'simulation_oscilloscope_trace must reference exactly one graph or probe.'
707
+ )
708
+ }
709
+ if (voltageReferences > 0 && Object.hasOwn(element, 'amps_per_div')) {
710
+ errors.push(
711
+ 'voltage oscilloscope traces must use volts_per_div, not amps_per_div.'
712
+ )
713
+ }
714
+ if (currentReferences > 0 && Object.hasOwn(element, 'volts_per_div')) {
715
+ errors.push(
716
+ 'current oscilloscope traces must use amps_per_div, not volts_per_div.'
717
+ )
718
+ }
719
+ CircuitJsonElementValidator.#optionalPositiveNumber(
720
+ element,
721
+ 'simulation_oscilloscope_trace',
722
+ 'volts_per_div',
723
+ errors
724
+ )
725
+ CircuitJsonElementValidator.#optionalPositiveNumber(
726
+ element,
727
+ 'simulation_oscilloscope_trace',
728
+ 'amps_per_div',
729
+ errors
730
+ )
731
+ }
732
+
569
733
  /**
570
734
  * Requires a non-empty string field.
571
735
  * @param {Record<string, unknown>} element Element.
@@ -666,6 +830,24 @@ export class CircuitJsonElementValidator {
666
830
  }
667
831
  }
668
832
 
833
+ /**
834
+ * Validates an optional positive finite number field.
835
+ * @param {Record<string, unknown>} element Element.
836
+ * @param {string} type Element type.
837
+ * @param {string} field Field name.
838
+ * @param {string[]} errors Error sink.
839
+ * @returns {void}
840
+ */
841
+ static #optionalPositiveNumber(element, type, field, errors) {
842
+ if (!Object.hasOwn(element, field)) {
843
+ return
844
+ }
845
+ const value = Number(element[field])
846
+ if (!Number.isFinite(value) || value <= 0) {
847
+ errors.push(type + ' ' + field + ' must be a positive number.')
848
+ }
849
+ }
850
+
669
851
  /**
670
852
  * Requires a finite point object field.
671
853
  * @param {Record<string, unknown>} element Element.
@@ -770,4 +952,39 @@ export class CircuitJsonElementValidator {
770
952
  .sort((left, right) => left.localeCompare(right))
771
953
  }
772
954
  }
955
+
956
+ /**
957
+ * Compares expected and actual variant metadata sets.
958
+ * @param {Record<string, string[]>} snapshotSets Snapshot variant sets.
959
+ * @returns {{ missing: CircuitJsonVariantDiff[], unexpected: CircuitJsonVariantDiff[] }}
960
+ */
961
+ static #compareVariantSets(snapshotSets) {
962
+ const missing = []
963
+ const unexpected = []
964
+ const setNames = new Set([
965
+ ...SORTED_VARIANT_SET_NAMES,
966
+ ...Object.keys(snapshotSets || {})
967
+ ])
968
+
969
+ for (const setName of [...setNames].sort()) {
970
+ const comparison = CircuitJsonElementValidator.#compareSets(
971
+ new Set((snapshotSets?.[setName] || []).map(String)),
972
+ new Set(VARIANT_SETS[setName] || [])
973
+ )
974
+ missing.push(
975
+ ...comparison.missing.map((value) => ({
976
+ set: setName,
977
+ value
978
+ }))
979
+ )
980
+ unexpected.push(
981
+ ...comparison.unexpected.map((value) => ({
982
+ set: setName,
983
+ value
984
+ }))
985
+ )
986
+ }
987
+
988
+ return { missing, unexpected }
989
+ }
773
990
  }