feat: connections improvements

This commit is contained in:
2026-03-11 23:05:01 +01:00
parent 3ef30bd58c
commit e79821bd81
6 changed files with 180 additions and 21 deletions

View File

@@ -36,6 +36,16 @@ export type FlowContextValue = {
addConnectionPathTrigger: (nodeId: string) => void
/** All node ids on the path of an update. Edges with both endpoints in this set animate. */
connectionPathNodeIds: Set<string>
/** Path nodes in the "paused" segment (from trigger up to Archon on hold). Those edges are yellow. */
connectionPathPausedSegmentNodeIds: Set<string>
/** Path nodes not in the paused segment (downstream of pause). Only those edges are blue (updating). */
connectionPathActiveSegmentNodeIds: Set<string>
/** Archon-type nodes that are on hold (e.g. Agent waiting for Run). */
connectionPathPausedNodeIds: string[]
/** Add this node as paused (on hold); remove when user continues. */
addConnectionPathPausedNode: (nodeId: string) => void
/** Remove this node from paused. */
removeConnectionPathPausedNode: (nodeId: string) => void
/** Call when a path update starts for this node. Animation runs at least CONNECTION_PATH_UPDATE_MIN_MS. */
startConnectionPathUpdate: (nodeId: string) => void
/** Call when a path update ends for this node. If min duration not reached, animation continues until then. */

View File

@@ -40,28 +40,84 @@ export function getUpstreamNodeIds(edges: GraphEdge[], seedIds: string[]): Set<s
/**
* All node ids that lie on the path of an update.
* - If triggerNodeIds is non-empty: path = nodes that are both downstream of a trigger and
* upstream of an updating node (only the chain that actually triggered the update).
* - Otherwise: path = upstream updating downstream of updating (legacy full upstream/downstream).
* An edge should show the update animation iff both its source and target are in this set.
* - If updatingNodeIds non-empty: path = downstream(trigger) ∩ (upstream(updating) downstream(updating))
* so we include config→agent→rendering when agent is running.
* - Else if triggerNodeIds and pausedNodeIds non-empty: path = downstream(trigger) ∩ upstream(paused)
* so we show yellow (config→agent) when config changed and agent is on hold.
* An edge should show color iff both its source and target are in this set.
*/
export function getPathNodeIds(
edges: GraphEdge[],
updatingNodeIds: string[],
triggerNodeIds?: string[]
triggerNodeIds?: string[],
pausedNodeIds?: string[]
): Set<string> {
if (updatingNodeIds.length === 0) return new Set()
const upstream = getUpstreamNodeIds(edges, updatingNodeIds)
if (triggerNodeIds != null && triggerNodeIds.length > 0) {
const hasUpdating = updatingNodeIds.length > 0
const hasPausedPath =
pausedNodeIds != null &&
pausedNodeIds.length > 0 &&
triggerNodeIds != null &&
triggerNodeIds.length > 0
if (hasUpdating && triggerNodeIds != null && triggerNodeIds.length > 0) {
const downstreamOfTrigger = getDownstreamNodeIds(edges, triggerNodeIds)
const upstreamOfUpdating = getUpstreamNodeIds(edges, updatingNodeIds)
const downstreamOfUpdating = getDownstreamNodeIds(edges, updatingNodeIds)
const path = new Set<string>()
downstreamOfTrigger.forEach((id) => {
if (upstreamOfUpdating.has(id) || downstreamOfUpdating.has(id)) path.add(id)
})
return path
}
if (hasPausedPath && !hasUpdating) {
const upstream = getUpstreamNodeIds(edges, pausedNodeIds!)
const downstreamOfTrigger = getDownstreamNodeIds(edges, triggerNodeIds!)
const path = new Set<string>()
upstream.forEach((id) => {
if (downstreamOfTrigger.has(id)) path.add(id)
})
return path
}
const downstream = getDownstreamNodeIds(edges, updatingNodeIds)
const path = new Set<string>(upstream)
downstream.forEach((id) => path.add(id))
return path
if (hasUpdating) {
const upstream = getUpstreamNodeIds(edges, updatingNodeIds)
const downstream = getDownstreamNodeIds(edges, updatingNodeIds)
const path = new Set<string>(upstream)
downstream.forEach((id) => path.add(id))
return path
}
return new Set()
}
/**
* Path nodes from triggers up to and including the first paused node (Archon on hold).
* Used to color those edges yellow; rest of path stays blue.
*/
export function getPausedSegmentNodeIds(
edges: GraphEdge[],
pathNodeIds: Set<string>,
triggerNodeIds: string[],
pausedNodeIds: string[]
): Set<string> {
if (pausedNodeIds.length === 0 || triggerNodeIds.length === 0) return new Set()
const pausedSet = new Set(pausedNodeIds)
const seeds = triggerNodeIds.filter((id) => pathNodeIds.has(id))
if (seeds.length === 0) return new Set()
const out = new Set<string>(seeds)
const frontier: string[] = [...seeds]
const visited = new Set<string>(seeds)
while (frontier.length > 0) {
const n = frontier.shift()!
if (pausedSet.has(n)) continue
for (const e of edges) {
if (e.source !== n || !pathNodeIds.has(e.target) || visited.has(e.target)) continue
visited.add(e.target)
out.add(e.target)
if (pausedSet.has(e.target)) continue
frontier.push(e.target)
}
}
return out
}