Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions packages/web/__tests__/flow-layout.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,21 @@ describe('computeElkLayout', () => {
expect(cron!.y).toBe(events!.y)
})

it('pins actor-typed nodes to the leftmost layer even when they also appear as a target', async () => {
const yaml = readFileSync(new URL('../../../examples/order-flow.yaml', import.meta.url), 'utf8')
const parsed = parseFlowYaml(yaml)
expect(parsed.success).toBe(true)
if (!parsed.success || !parsed.data) return

const { computeElkLayout } = await import('../src/lib/flow-layout.ts')
const topology = buildFlowTopology(parsed.data as Flow)
const layout = await computeElkLayout(topology)
const userPos = layout.positions.get('user')
expect(userPos).toBeTruthy()
const minX = Math.min(...[...layout.positions.values()].map((p) => p.x))
expect(userPos!.x).toBe(minX)
})

it('routes the api to order-service edge clear of the rate-limit node in the real example', async () => {
const yaml = readFileSync(new URL('../../../examples/order-flow.yaml', import.meta.url), 'utf8')
const parsed = parseFlowYaml(yaml)
Expand Down
19 changes: 12 additions & 7 deletions packages/web/src/lib/flow-layout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -876,18 +876,23 @@ function buildElkGraph(topology: FlowTopology, portAssignments?: Map<string, Edg
},
}))

// Actors represent the human/external initiator — pin them to the
// leftmost layer so the flow always reads "user → system → ..." even
// when the actor also has incoming edges (e.g. a final response
// step).
const isActor = topology.nodeSnapshots.get(id)?.nodeType === 'actor'
const nodeLayoutOptions: Record<string, string> = {}
if (portAssignments) nodeLayoutOptions.portConstraints = 'FIXED_SIDE'
if (isActor) nodeLayoutOptions['elk.layered.layering.layerConstraint'] = 'FIRST'

return {
id,
width: NODE_WIDTH,
height: NODE_HEIGHT,
...(portAssignments
? {
layoutOptions: {
portConstraints: 'FIXED_SIDE',
},
ports,
}
...(Object.keys(nodeLayoutOptions).length > 0
? { layoutOptions: nodeLayoutOptions }
: null),
...(portAssignments ? { ports } : null),
}
}),
edges: topology.displayEdges.map((edge) => ({
Expand Down
Loading