Skip to content
Draft
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
1efc931
feat: add link store lifecycle rehydration contract
DrJKL Feb 26, 2026
bad3fa5
feat: route link topology reads through store projection
DrJKL Feb 26, 2026
6c94b9f
feat: centralize disconnect/remove link mutation path
DrJKL Feb 26, 2026
ae5bfe9
feat: centralize normal connect mutation path
DrJKL Feb 26, 2026
38ed312
feat: centralize subgraph io mutation paths
DrJKL Feb 26, 2026
1c4c000
feat: normalize subgraph slot identity by canonical name
DrJKL Feb 26, 2026
e17a2c6
feat: extract shared subgraph boundary remap adapter
DrJKL Feb 26, 2026
6580500
feat: isolate persistence and node remap adapters
DrJKL Feb 26, 2026
34786a1
feat: unify lifecycle event dispatcher callbacks
DrJKL Feb 26, 2026
025a803
feat: narrow legacy slot repair to mismatch cases
DrJKL Feb 26, 2026
0331c1c
test: stabilize flaky unit expectations
DrJKL Feb 26, 2026
7d737a5
Fix: expectation on renaming. The label and name now align, deduping …
DrJKL Feb 26, 2026
3331ab9
fix: harden subgraph disconnect cleanup for stale links
DrJKL Feb 26, 2026
2686bec
fix: correct disconnect dispatch args, import typo, and test timeout
DrJKL Feb 26, 2026
9f1376d
refactor: extract addAfterConfigureHandler to graphConfigureUtil
DrJKL Feb 26, 2026
8026585
fix: finalize link topology store migration
DrJKL Feb 26, 2026
9028ee9
fix: keep link store topology type internal
DrJKL Feb 26, 2026
b9c4675
Merge branch 'main' into drjkl/he-come-to-town
DrJKL Feb 27, 2026
9d4ae0d
Render app builder in arrange mode (#9260)
pythongosssss Feb 27, 2026
de8be5c
App mode - discard slow preview messages to prevent overwriting outpu…
pythongosssss Feb 27, 2026
bd12669
App builder exit updates (#9218)
pythongosssss Feb 27, 2026
13040e7
Fix essentials nodes not being marked core (#9287)
AustinMroz Feb 28, 2026
237e210
fix: pre-rasterize SubgraphNode SVG icon to bitmap canvas (#9172)
christian-byrne Feb 28, 2026
ab59e37
1.41.8 (#9288)
comfy-pr-bot Feb 28, 2026
1917804
fix: node replacement fails after execution and modal sync (#9269)
jaeone94 Feb 28, 2026
e2ad7a5
refactor(node-replacement): reorganize domain components and expand c…
jaeone94 Feb 28, 2026
e0cc4d9
feat: Node Library sidebar and V2 Search dialog UI/UX updates (#9085)
christian-byrne Feb 28, 2026
2182375
feat: wrap CURVE widget value with typed format (#9294)
jtydhr88 Feb 28, 2026
6464d14
Add indicator circle when new unseen menu items are available (#9220)
pythongosssss Feb 28, 2026
779694c
fix: stabilize nested subgraph promoted widget resolution (#9282)
DrJKL Feb 28, 2026
0a940d6
Merge origin/main into drjkl/he-come-to-town
DrJKL Feb 28, 2026
d6e05bd
fix: use Map lookup for subgraph output disconnect
DrJKL Feb 28, 2026
767f19f
fix: replace graph.linkStore with raw map lookups in changeTracker test
DrJKL Feb 28, 2026
f0ca965
fix: address must-fix review items for link topology PR
DrJKL Mar 1, 2026
8a3c647
refactor: simplify SafeWidgetData and useGraphNodeManager
DrJKL Mar 1, 2026
2cc3997
refactor: PR #9247 should-fix items S1–S11
DrJKL Mar 1, 2026
7b41b03
refactor: simplify LGraph with L3, L4, L7 remediation items
DrJKL Mar 1, 2026
838b168
Merge branch 'main' into drjkl/he-come-to-town
DrJKL Mar 3, 2026
0b8cc7f
fix: address CodeRabbit review findings for subgraph lifecycle parity
DrJKL Mar 3, 2026
00cfb70
merge: resolve conflict in litegraphUtil.test.ts keeping both sides
DrJKL Mar 5, 2026
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
98 changes: 98 additions & 0 deletions browser_tests/tests/changeTracker.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
comfyExpect as expect,
comfyPageFixture as test
} from '../fixtures/ComfyPage'
import type { WorkspaceStore } from '../types/globals'

async function beforeChange(comfyPage: ComfyPage) {
await comfyPage.page.evaluate(() => {
Expand Down Expand Up @@ -63,6 +64,103 @@
expect(await comfyPage.workflow.getUndoQueueSize()).toBe(0)
expect(await comfyPage.workflow.getRedoQueueSize()).toBe(2)
})

test('undo/redo restores link topology with reroutes and floating links', async ({
comfyPage
}) => {
await comfyPage.workflow.loadWorkflow('links/batch_move_links')

const readTopology = () =>
comfyPage.page.evaluate(() => {
const graph = window.app!.rootGraph
return {
links: graph.links.size,
floatingLinks: graph.floatingLinks.size,
reroutes: graph.reroutes.size,
serialised: graph.serialize()
}
})

const baseline = await readTopology()

await beforeChange(comfyPage)
await comfyPage.page.evaluate(() => {
const graph = window.app!.rootGraph
const firstLink = graph.links.values().next().value
if (!firstLink) throw new Error('Expected at least one link')

const reroute = graph.createReroute(
[firstLink.id * 5, firstLink.id * 3],
firstLink
)
graph.addFloatingLink(firstLink.toFloating('output', reroute.id))
})
await afterChange(comfyPage)

const mutated = await readTopology()
expect(mutated.floatingLinks).toBeGreaterThan(baseline.floatingLinks)
expect(mutated.reroutes).toBeGreaterThan(baseline.reroutes)
await expect.poll(() => comfyPage.workflow.getUndoQueueSize()).toBe(1)

await comfyPage.page.evaluate(async () => {
await (
window.app!.extensionManager as WorkspaceStore
).workflow.activeWorkflow?.changeTracker.undo()
})
const afterUndo = await readTopology()
expect(afterUndo.links).toBe(baseline.links)
expect(afterUndo.floatingLinks).toBe(baseline.floatingLinks)
expect(afterUndo.reroutes).toBe(baseline.reroutes)
expect(afterUndo.serialised).toEqual(baseline.serialised)

await comfyPage.page.evaluate(async () => {
await (
window.app!.extensionManager as WorkspaceStore
).workflow.activeWorkflow?.changeTracker.redo()
})
const afterRedo = await readTopology()
expect(afterRedo.links).toBe(mutated.links)
expect(afterRedo.floatingLinks).toBe(mutated.floatingLinks)
expect(afterRedo.reroutes).toBe(mutated.reroutes)
expect(afterRedo.serialised).toEqual(mutated.serialised)
})

test('read-through accessors stay in sync for links, floating links, and reroutes', async ({
comfyPage
}) => {
await comfyPage.workflow.loadWorkflow('links/batch_move_links')

const parity = await comfyPage.page.evaluate(() => {
const graph = window.app!.rootGraph
const firstLink = graph.links.values().next().value
if (!firstLink) throw new Error('Expected at least one link')

const reroute = graph.createReroute(
[firstLink.id * 7, firstLink.id * 4],
firstLink
)
const floatingLink = firstLink.toFloating('output', reroute.id)
graph.addFloatingLink(floatingLink)

return {
normalLinkMatches:
graph.getLink(firstLink.id) ===
graph.linkStore.getLink(firstLink.id),

Check failure on line 148 in browser_tests/tests/changeTracker.spec.ts

View workflow job for this annotation

GitHub Actions / lint-and-format

Property 'linkStore' does not exist on type 'LGraph'. Did you mean 'linkStoreKey'?
floatingLinkRequiresExplicitProjection:
graph.getLink(floatingLink.id) !==
graph.linkStore.getFloatingLink(floatingLink.id) &&

Check failure on line 151 in browser_tests/tests/changeTracker.spec.ts

View workflow job for this annotation

GitHub Actions / lint-and-format

Property 'linkStore' does not exist on type 'LGraph'. Did you mean 'linkStoreKey'?
graph.linkStore.getFloatingLink(floatingLink.id) ===

Check failure on line 152 in browser_tests/tests/changeTracker.spec.ts

View workflow job for this annotation

GitHub Actions / lint-and-format

Property 'linkStore' does not exist on type 'LGraph'. Did you mean 'linkStoreKey'?
graph.floatingLinks.get(floatingLink.id),
rerouteMatches:
graph.getReroute(reroute.id) ===
graph.linkStore.getReroute(reroute.id)

Check failure on line 156 in browser_tests/tests/changeTracker.spec.ts

View workflow job for this annotation

GitHub Actions / lint-and-format

Property 'linkStore' does not exist on type 'LGraph'. Did you mean 'linkStoreKey'?
}
})

expect(parity.normalLinkMatches).toBe(true)
expect(parity.floatingLinkRequiresExplicitProjection).toBe(true)
expect(parity.rerouteMatches).toBe(true)
})
})

test('Can group multiple change actions into a single transaction', async ({
Expand Down
4 changes: 2 additions & 2 deletions browser_tests/tests/subgraph-rename-dialog.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,9 +87,9 @@ test.describe('Subgraph Slot Rename Dialog', { tag: '@subgraph' }, () => {
// Get the current value in the prompt dialog
const dialogValue = await comfyPage.page.inputValue(SELECTORS.promptDialog)

// This should show the current label (RENAMED_NAME), not the original name
// This should show the current renamed value and stay aligned with slot identity.
expect(dialogValue).toBe(RENAMED_NAME)
expect(dialogValue).not.toBe(afterFirstRename.name) // Should not show the original slot.name
expect(dialogValue).toBe(afterFirstRename.name)

// Complete the second rename to ensure everything still works
await comfyPage.page.fill(SELECTORS.promptDialog, '')
Expand Down
Loading
Loading