fix: suppress link rendering during slot sync after graph reconfigure (#8367)

## Description

Fixes link alignment issues after undo/redo operations in Vue Nodes 2.0.
When multiple connections exist from the same output to different nodes,
performing an undo would cause the connections to become misaligned with
their inputs.

## Root Cause

When undo triggers `loadGraphData`, the graph is reconfigured and Vue
node components are destroyed and recreated. The new slot elements mount
and schedule RAF-batched position syncs via `scheduleSlotLayoutSync`.
However, links are drawn **before** the RAF batch completes, causing
`getSlotPosition()` to return stale/missing positions.

## Solution

- Export a new `flushPendingSlotLayoutSyncs()` function from
`useSlotElementTracking.ts`
- Create a `useGraphConfigureSlotSync` composable that flushes pending
syncs after graph configuration
- Integrate the flush into `addAfterConfigureHandler` in `app.ts`,
called after `onAfterGraphConfigured`
- Force canvas redraw after flushing to render links with correct
positions

## Testing

- Added unit tests for `flushPendingSlotLayoutSyncs`
- Added unit tests for `useGraphConfigureSlotSync` composable
- Manual verification: connections now align correctly after undo/redo
operations

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8367-fix-flush-pending-slot-layout-syncs-after-graph-configure-2f66d73d365081a3a564fac96c44a048)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Subagent 5 <subagent@example.com>
Co-authored-by: Amp <amp@ampcode.com>
This commit is contained in:
Christian Byrne
2026-02-03 15:56:31 -08:00
committed by GitHub
parent 278d491030
commit f98232c272
4 changed files with 66 additions and 9 deletions

View File

@@ -12,6 +12,7 @@ import { useSharedCanvasPositionConversion } from '@/composables/element/useCanv
import { LiteGraph } from '@/lib/litegraph/src/litegraph'
import { getSlotKey } from '@/renderer/core/layout/slots/slotIdentifier'
import { layoutStore } from '@/renderer/core/layout/store/layoutStore'
import { app } from '@/scripts/app'
import type { SlotLayout } from '@/renderer/core/layout/types'
import {
isPointEqual,
@@ -28,16 +29,36 @@ const raf = createRafBatch(() => {
function scheduleSlotLayoutSync(nodeId: string) {
pendingNodes.add(nodeId)
// Re-assert pending flag for late mounts (Vue components mounting after
// flushScheduledSlotLayoutSync was called synchronously in onConfigure)
layoutStore.setPendingSlotSync(true)
raf.schedule()
}
function flushScheduledSlotLayoutSync() {
if (pendingNodes.size === 0) return
export function flushScheduledSlotLayoutSync() {
if (pendingNodes.size === 0) {
// No pending nodes - check if we should wait for Vue components to mount
const graph = app.canvas?.graph
const hasNodes = graph && graph._nodes && graph._nodes.length > 0
if (hasNodes) {
// Graph has nodes but Vue hasn't mounted them yet - keep flag set
// so late mounts can re-assert it via scheduleSlotLayoutSync()
return
}
// No nodes in graph - safe to clear the flag (no Vue components will mount)
layoutStore.setPendingSlotSync(false)
app.canvas?.setDirty(true, true)
return
}
const conv = useSharedCanvasPositionConversion()
for (const nodeId of Array.from(pendingNodes)) {
pendingNodes.delete(nodeId)
syncNodeSlotLayoutsFromDOM(nodeId, conv)
}
// Clear the pending sync flag - slots are now synced
layoutStore.setPendingSlotSync(false)
// Trigger canvas redraw now that links can render with correct positions
app.canvas?.setDirty(true, true)
}
export function syncNodeSlotLayoutsFromDOM(