Skip to content

Commit d5dc867

Browse files
committed
feat(workflows): version diff view with a field-level change list
Side-by-side view of two workflow states: the overlaid canvas on the left and a change list on the right, sharing selection. Each touched block is a collapsible card with the app's block tile; modified blocks show field rows as folded line diffs with word marks, item-by-item list diffs for tools, conditions, routes and input fields, old to new pairs for scalars, and only the fact for secrets. Added and removed blocks show every field diffed against nothing, with long one-sided bodies capped behind one expander. Fork comparisons group credentials, picked resources and trigger paths into a muted environment bindings section.
1 parent 463603c commit d5dc867

12 files changed

Lines changed: 2511 additions & 0 deletions

File tree

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
'use client'
2+
3+
import { ArrowRight } from '@sim/emcn/icons'
4+
import { resolveFieldLabel } from '@/lib/workflows/comparison/resolve-values'
5+
import {
6+
classifyChange,
7+
formatScalar,
8+
} from '@/app/workspace/[workspaceId]/w/components/workflow-diff/utils'
9+
import { formatParameterLabel } from '@/tools/params'
10+
11+
interface BindingChangeRowProps {
12+
blockType: string
13+
field: string
14+
oldValue: unknown
15+
newValue: unknown
16+
}
17+
18+
/**
19+
* A workspace-bound field that differs between the two sides. Muted on purpose:
20+
* the sync's Mappings and Trigger URLs sections decide what these become, so
21+
* the row only records that they differ, and by what, never as a red or green
22+
* change competing with the logic rows above it.
23+
*/
24+
export function BindingChangeRow({ blockType, field, oldValue, newValue }: BindingChangeRowProps) {
25+
const resolved = resolveFieldLabel(blockType, field)
26+
const label = resolved === field ? formatParameterLabel(field) : resolved
27+
const kind = classifyChange(blockType, field, oldValue, newValue)
28+
29+
return (
30+
<div className='flex min-w-0 items-center gap-2 text-caption'>
31+
<span className='w-[112px] shrink-0 truncate text-[var(--text-tertiary)]'>{label}</span>
32+
{kind === 'secret' ? (
33+
<span className='text-[var(--text-muted)]'>Differs between workspaces</span>
34+
) : (
35+
<>
36+
<span className='min-w-0 truncate text-[var(--text-muted)]'>
37+
{formatScalar(blockType, field, oldValue)}
38+
</span>
39+
<ArrowRight className='size-[12px] shrink-0 text-[var(--text-icon)]' />
40+
<span className='min-w-0 truncate text-[var(--text-tertiary)]'>
41+
{formatScalar(blockType, field, newValue)}
42+
</span>
43+
</>
44+
)}
45+
</div>
46+
)
47+
}

0 commit comments

Comments
 (0)