@@ -42,6 +42,37 @@ interface FieldChange {
4242 newValue : unknown
4343}
4444
45+ /** The loop configuration fields a container diff compares, in the order they are reported. */
46+ export const LOOP_CONFIG_FIELDS = [
47+ 'loopType' ,
48+ 'iterations' ,
49+ 'forEachItems' ,
50+ 'whileCondition' ,
51+ 'doWhileCondition' ,
52+ ] as const
53+
54+ /** The parallel configuration fields a container diff compares, in the order they are reported. */
55+ export const PARALLEL_CONFIG_FIELDS = [ 'parallelType' , 'count' , 'distribution' ] as const
56+
57+ export type ContainerConfigField =
58+ | ( typeof LOOP_CONFIG_FIELDS ) [ number ]
59+ | ( typeof PARALLEL_CONFIG_FIELDS ) [ number ]
60+
61+ /**
62+ * A loop or parallel container present on both sides whose configuration or
63+ * membership differs. Added and removed containers are reported as blocks.
64+ */
65+ export interface ContainerChange {
66+ id : string
67+ kind : 'loop' | 'parallel'
68+ name ?: string
69+ changes : FieldChange [ ]
70+ /** Block ids that are inside the container on the current side only */
71+ nodesAdded : string [ ]
72+ /** Block ids that were inside the container on the previous side only */
73+ nodesRemoved : string [ ]
74+ }
75+
4576/**
4677 * Result of workflow diff analysis between two workflow states
4778 */
@@ -57,6 +88,7 @@ export interface WorkflowDiffSummary {
5788 }
5889 loopChanges : { added : number ; removed : number ; modified : number }
5990 parallelChanges : { added : number ; removed : number ; modified : number }
91+ containerChanges : ContainerChange [ ]
6092 variableChanges : {
6193 added : number
6294 removed : number
@@ -68,6 +100,62 @@ export interface WorkflowDiffSummary {
68100 hasChanges : boolean
69101}
70102
103+ /**
104+ * Field-by-field description of a container that changed, from the normalized
105+ * shapes the equality check already uses so the two can never disagree.
106+ */
107+ function describeContainerChange (
108+ id : string ,
109+ kind : ContainerChange [ 'kind' ] ,
110+ name : string | undefined ,
111+ current : { nodes : string [ ] } | null ,
112+ previous : { nodes : string [ ] } | null ,
113+ fields : readonly string [ ]
114+ ) : ContainerChange {
115+ const changes : FieldChange [ ] = [ ]
116+ const currentRecord = ( current ?? { } ) as Record < string , unknown >
117+ const previousRecord = ( previous ?? { } ) as Record < string , unknown >
118+ for ( const field of fields ) {
119+ if ( normalizedStringify ( currentRecord [ field ] ) !== normalizedStringify ( previousRecord [ field ] ) ) {
120+ changes . push ( {
121+ field,
122+ oldValue : previousRecord [ field ] ?? null ,
123+ newValue : currentRecord [ field ] ?? null ,
124+ } )
125+ }
126+ }
127+ const currentNodes = new Set ( current ?. nodes ?? [ ] )
128+ const previousNodes = new Set ( previous ?. nodes ?? [ ] )
129+ return {
130+ id,
131+ kind,
132+ name,
133+ changes,
134+ nodesAdded : [ ...currentNodes ] . filter ( ( node ) => ! previousNodes . has ( node ) ) ,
135+ nodesRemoved : [ ...previousNodes ] . filter ( ( node ) => ! currentNodes . has ( node ) ) ,
136+ }
137+ }
138+
139+ /** Whether any counted section of a summary reports a difference. */
140+ export function summaryHasChanges ( summary : Omit < WorkflowDiffSummary , 'hasChanges' > ) : boolean {
141+ return (
142+ summary . addedBlocks . length > 0 ||
143+ summary . removedBlocks . length > 0 ||
144+ summary . modifiedBlocks . length > 0 ||
145+ summary . edgeChanges . added > 0 ||
146+ summary . edgeChanges . removed > 0 ||
147+ summary . loopChanges . added > 0 ||
148+ summary . loopChanges . removed > 0 ||
149+ summary . loopChanges . modified > 0 ||
150+ summary . parallelChanges . added > 0 ||
151+ summary . parallelChanges . removed > 0 ||
152+ summary . parallelChanges . modified > 0 ||
153+ summary . variableChanges . added > 0 ||
154+ summary . variableChanges . removed > 0 ||
155+ summary . variableChanges . modified > 0
156+ )
157+ }
158+
71159/**
72160 * Generate a detailed diff summary between two workflow states
73161 */
@@ -82,6 +170,7 @@ export function generateWorkflowDiffSummary(
82170 edgeChanges : { added : 0 , removed : 0 , addedDetails : [ ] , removedDetails : [ ] } ,
83171 loopChanges : { added : 0 , removed : 0 , modified : 0 } ,
84172 parallelChanges : { added : 0 , removed : 0 , modified : 0 } ,
173+ containerChanges : [ ] ,
85174 variableChanges : {
86175 added : 0 ,
87176 removed : 0 ,
@@ -354,10 +443,23 @@ export function generateWorkflowDiffSummary(
354443 if ( ! previousLoopIds . includes ( id ) ) {
355444 result . loopChanges . added ++
356445 } else {
357- const normalizedCurrent = normalizeValue ( normalizeLoop ( currentLoops [ id ] ) )
358- const normalizedPrevious = normalizeValue ( normalizeLoop ( previousLoops [ id ] ) )
359- if ( normalizedStringify ( normalizedCurrent ) !== normalizedStringify ( normalizedPrevious ) ) {
446+ const normalizedCurrent = normalizeLoop ( currentLoops [ id ] )
447+ const normalizedPrevious = normalizeLoop ( previousLoops [ id ] )
448+ if (
449+ normalizedStringify ( normalizeValue ( normalizedCurrent ) ) !==
450+ normalizedStringify ( normalizeValue ( normalizedPrevious ) )
451+ ) {
360452 result . loopChanges . modified ++
453+ result . containerChanges . push (
454+ describeContainerChange (
455+ id ,
456+ 'loop' ,
457+ currentBlocks [ id ] ?. name ?? previousBlocks [ id ] ?. name ,
458+ normalizedCurrent ?? null ,
459+ normalizedPrevious ?? null ,
460+ LOOP_CONFIG_FIELDS
461+ )
462+ )
361463 }
362464 }
363465 }
@@ -376,10 +478,23 @@ export function generateWorkflowDiffSummary(
376478 if ( ! previousParallelIds . includes ( id ) ) {
377479 result . parallelChanges . added ++
378480 } else {
379- const normalizedCurrent = normalizeValue ( normalizeParallel ( currentParallels [ id ] ) )
380- const normalizedPrevious = normalizeValue ( normalizeParallel ( previousParallels [ id ] ) )
381- if ( normalizedStringify ( normalizedCurrent ) !== normalizedStringify ( normalizedPrevious ) ) {
481+ const normalizedCurrent = normalizeParallel ( currentParallels [ id ] )
482+ const normalizedPrevious = normalizeParallel ( previousParallels [ id ] )
483+ if (
484+ normalizedStringify ( normalizeValue ( normalizedCurrent ) ) !==
485+ normalizedStringify ( normalizeValue ( normalizedPrevious ) )
486+ ) {
382487 result . parallelChanges . modified ++
488+ result . containerChanges . push (
489+ describeContainerChange (
490+ id ,
491+ 'parallel' ,
492+ currentBlocks [ id ] ?. name ?? previousBlocks [ id ] ?. name ,
493+ normalizedCurrent ?? null ,
494+ normalizedPrevious ?? null ,
495+ PARALLEL_CONFIG_FIELDS
496+ )
497+ )
383498 }
384499 }
385500 }
@@ -417,21 +532,7 @@ export function generateWorkflowDiffSummary(
417532 }
418533 }
419534
420- result . hasChanges =
421- result . addedBlocks . length > 0 ||
422- result . removedBlocks . length > 0 ||
423- result . modifiedBlocks . length > 0 ||
424- result . edgeChanges . added > 0 ||
425- result . edgeChanges . removed > 0 ||
426- result . loopChanges . added > 0 ||
427- result . loopChanges . removed > 0 ||
428- result . loopChanges . modified > 0 ||
429- result . parallelChanges . added > 0 ||
430- result . parallelChanges . removed > 0 ||
431- result . parallelChanges . modified > 0 ||
432- result . variableChanges . added > 0 ||
433- result . variableChanges . removed > 0 ||
434- result . variableChanges . modified > 0
535+ result . hasChanges = summaryHasChanges ( result )
435536
436537 return result
437538}
0 commit comments