Skip to content

Commit 458b00c

Browse files
DrJKLampagent
authored andcommitted
refactor: bake LayoutSource at instantiation, retire ambient source
Retargeted squash of the layout-source stack onto the pruned attachment rework: useLayoutMutations(source) bakes the source when instantiated, operations carry their source explicitly, the store stamps the session actor on operations that do not carry one, and the ambient setSource/setActor/getCurrentSource/getCurrentActor knobs are removed. DOM height convention is passed explicitly through batchUpdateNodeBounds options instead of sniffed from an ambient DOM source.
1 parent a43634e commit 458b00c

31 files changed

Lines changed: 466 additions & 678 deletions

docs/adr/0003-crdt-based-layout-system.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -156,9 +156,9 @@ serializable command shape every mutation goes through.
156156

157157
Entity geometry registers and unregisters with the entity that owns it
158158
(`LGraph.add` / `LGraph.remove`) rather than being seeded per graph on renderer
159-
entry. All three entity types key by `makeScopedLayoutKey(rootGraphId, id)`, so
160-
a root graph's teardown is one `clearGraph`; graphs sharing that bucket drop
161-
their entries individually through `unregisterAllGraphLayout`.
159+
entry. All three entity types key by `makeScopedLayoutKey(rootGraphId, id)`, and
160+
every graph — root or nested — drops its entries individually through
161+
`unregisterAllGraphLayout`.
162162

163163
## Notes
164164

docs/architecture/node-data-store.md

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -169,13 +169,11 @@ it as `configure()` applies the real values. This follows the precedent
169169
already set by `LGraph.createReroute`.
170170

171171
Geometry now leaves the store the same way: `LGraph.remove` unregisters one
172-
entry, and bulk teardown goes through one of two paths. A root graph's `clear`
173-
calls `layoutStore.clearGraph(graphId)`, beside the five peer stores cleared
174-
there, because layout is keyed by `rootGraphId` and so has a bucket to wipe.
175-
Graphs that share the root's bucket — subgraphs, unconfigured graphs, and the
176-
orphaned subgraph release — call `unregisterAllGraphLayout` to drop entries
177-
individually, mirroring `unregisterAllNodeStates` exactly. Both live in a
178-
dedicated module so no teardown path re-derives the store writes by hand.
172+
entry, and every bulk teardown — a root graph's `clear`, subgraphs,
173+
unconfigured graphs, and the orphaned subgraph release — calls
174+
`unregisterAllGraphLayout` to drop entries individually, mirroring
175+
`unregisterAllNodeStates` exactly. It lives in a dedicated module so no
176+
teardown path re-derives the store writes by hand.
179177

180178
`useVueNodeLifecycle` is gone; `GraphCanvas` owns only the Layout↔LiteGraph
181179
sync lifecycle, and while the Vue renderer is on it drops view-scoped slot and

src/composables/graph/useArrangeNodes.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ interface ArrangeOptions {
165165

166166
export function useArrangeNodes() {
167167
const { selectedNodes, hasMultipleSelection } = useSelectionState()
168-
const mutations = useLayoutMutations()
168+
const mutations = useLayoutMutations(LayoutSource.Canvas)
169169
const workflowStore = useWorkflowStore()
170170
const canvasStore = useCanvasStore()
171171

@@ -180,7 +180,6 @@ export function useArrangeNodes() {
180180
const updates = computeArrangement(selectedNodes.value, layout, gap)
181181
if (updates.length === 0) return
182182

183-
mutations.setSource(LayoutSource.Canvas)
184183
mutations.batchMoveNodes(rootGraphId, updates)
185184
app.canvas?.setDirty(true, true)
186185
if (captureUndo) {

src/lib/litegraph/src/LGraph.test.ts

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import type { LGraphCanvas } from '@/lib/litegraph/src/LGraphCanvas'
99
import type { Subgraph } from '@/lib/litegraph/src/litegraph'
1010
import { useLayoutMutations } from '@/renderer/core/layout/operations/layoutMutations'
1111
import { layoutStore } from '@/renderer/core/layout/store/layoutStore'
12+
import { LayoutSource } from '@/renderer/core/layout/types'
1213
import {
1314
LGraph,
1415
LGraphGroup,
@@ -1730,12 +1731,16 @@ describe('node layout registration', () => {
17301731
node.id = toNodeId(42)
17311732
node.pos = [10, 20]
17321733
node.size = [100, 80]
1733-
useLayoutMutations().createNode(graph.rootGraph.id, node.id, {
1734-
position: { x: 300, y: 400 },
1735-
size: { width: 220, height: 160 },
1736-
zIndex: 1,
1737-
visible: true
1738-
})
1734+
useLayoutMutations(LayoutSource.Canvas).createNode(
1735+
graph.rootGraph.id,
1736+
node.id,
1737+
{
1738+
position: { x: 300, y: 400 },
1739+
size: { width: 220, height: 160 },
1740+
zIndex: 1,
1741+
visible: true
1742+
}
1743+
)
17391744

17401745
graph.add(node)
17411746

src/lib/litegraph/src/LGraphCanvas.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4348,11 +4348,12 @@ export class LGraphCanvas implements CustomEventDispatcher<LGraphCanvasEventMap>
43484348
}))
43494349

43504350
const rootGraphId = graph.rootGraph.id
4351-
if (newPositions.length) layoutStore.setSource(LayoutSource.Canvas)
4352-
layoutStore.batchUpdateNodeBounds(rootGraphId, newPositions)
4351+
layoutStore.batchUpdateNodeBounds(rootGraphId, newPositions, {
4352+
source: LayoutSource.Canvas
4353+
})
43534354

43544355
// Bring cloned/pasted nodes to front so they render above the originals
4355-
const { setNodeZIndex } = useLayoutMutations()
4356+
const { setNodeZIndex } = useLayoutMutations(LayoutSource.Canvas)
43564357
for (const { nodeId } of newPositions) {
43574358
setNodeZIndex(rootGraphId, nodeId, layoutStore.allocateZIndex())
43584359
}
@@ -4987,7 +4988,7 @@ export class LGraphCanvas implements CustomEventDispatcher<LGraphCanvasEventMap>
49874988
const i = graph._nodes.indexOf(node)
49884989
if (i == -1) return
49894990

4990-
canvasLayoutMutations().bringNodeToFront(graph.rootGraph.id, node.id)
4991+
canvasLayoutMutations.bringNodeToFront(graph.rootGraph.id, node.id)
49914992

49924993
graph._nodes.splice(i, 1)
49934994
graph._nodes.push(node)

src/lib/litegraph/src/LGraphGroup.test.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import type { LGraphCanvas } from '@/lib/litegraph/src/litegraph'
66
import { LGraph, LGraphGroup, LGraphNode } from '@/lib/litegraph/src/litegraph'
77
import { useLayoutMutations } from '@/renderer/core/layout/operations/layoutMutations'
88
import { layoutStore } from '@/renderer/core/layout/store/layoutStore'
9+
import { LayoutSource } from '@/renderer/core/layout/types'
910
import type { GroupId } from '@/types/groupId'
1011
import { toGroupId } from '@/types/groupId'
1112
import * as colorUtil from '@/utils/colorUtil'
@@ -285,7 +286,10 @@ describe('group layout in layoutStore', () => {
285286
test('keeps geometry locally when the store entry is gone', () => {
286287
const graph = new LGraph()
287288
const group = addedGroup(graph, toGroupId(809))
288-
useLayoutMutations().deleteGroup(graph.rootGraph.id, group.id)
289+
useLayoutMutations(LayoutSource.Canvas).deleteGroup(
290+
graph.rootGraph.id,
291+
group.id
292+
)
289293

290294
group.pos = [11, 22]
291295

@@ -379,7 +383,7 @@ describe('group layout in layoutStore', () => {
379383
type: 'setGroupBounds',
380384
actor: 'test',
381385
timestamp: 1,
382-
source: layoutStore.getCurrentSource(),
386+
source: LayoutSource.Canvas,
383387
entity: 'group',
384388
graphId: graph.rootGraph.id,
385389
groupId: group.id,

src/lib/litegraph/src/LGraphNode.test.ts

Lines changed: 71 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import type {
1010
} from '@/lib/litegraph/src/litegraph'
1111
import type { Rect } from '@/lib/litegraph/src/interfaces'
1212
import { layoutStore } from '@/renderer/core/layout/store/layoutStore'
13+
import { LayoutSource } from '@/renderer/core/layout/types'
1314
import type { LGraphCanvas } from '@/lib/litegraph/src/LGraphCanvas'
1415
import type { CanvasPointerEvent } from '@/lib/litegraph/src/types/events'
1516
import { BaseWidget } from '@/lib/litegraph/src/widgets/BaseWidget'
@@ -880,12 +881,16 @@ describe('layout geometry projection', () => {
880881
const graph = new LGraph()
881882
const node = new LGraphNode('test')
882883
graph.add(node)
883-
layoutStore.batchUpdateNodeBounds(graph.rootGraph.id, [
884-
{
885-
nodeId: node.id,
886-
bounds: { x: 30, y: 40, width: 200, height: 80 }
887-
}
888-
])
884+
layoutStore.batchUpdateNodeBounds(
885+
graph.rootGraph.id,
886+
[
887+
{
888+
nodeId: node.id,
889+
bounds: { x: 30, y: 40, width: 200, height: 80 }
890+
}
891+
],
892+
{ source: LayoutSource.Canvas }
893+
)
889894

890895
node.move(5, 10)
891896

@@ -898,12 +903,16 @@ describe('layout geometry projection', () => {
898903
const graph = new LGraph()
899904
const node = new LGraphNode('test')
900905
graph.add(node)
901-
layoutStore.batchUpdateNodeBounds(graph.rootGraph.id, [
902-
{
903-
nodeId: node.id,
904-
bounds: { x: 103, y: 97, width: 200, height: 80 }
905-
}
906-
])
906+
layoutStore.batchUpdateNodeBounds(
907+
graph.rootGraph.id,
908+
[
909+
{
910+
nodeId: node.id,
911+
bounds: { x: 103, y: 97, width: 200, height: 80 }
912+
}
913+
],
914+
{ source: LayoutSource.Canvas }
915+
)
907916

908917
node.snapToGrid(20)
909918

@@ -916,12 +925,16 @@ describe('layout geometry projection', () => {
916925
const graph = new LGraph()
917926
const node = new LGraphNode('test')
918927
graph.add(node)
919-
layoutStore.batchUpdateNodeBounds(graph.rootGraph.id, [
920-
{
921-
nodeId: node.id,
922-
bounds: { x: 30, y: 40, width: 200, height: 80 }
923-
}
924-
])
928+
layoutStore.batchUpdateNodeBounds(
929+
graph.rootGraph.id,
930+
[
931+
{
932+
nodeId: node.id,
933+
bounds: { x: 30, y: 40, width: 200, height: 80 }
934+
}
935+
],
936+
{ source: LayoutSource.Canvas }
937+
)
925938

926939
graph.remove(node)
927940
graph.add(node)
@@ -943,12 +956,16 @@ describe('layout geometry projection', () => {
943956
const pos = node.pos
944957
const size = node.size
945958

946-
layoutStore.batchUpdateNodeBounds(graph.rootGraph.id, [
947-
{
948-
nodeId: node.id,
949-
bounds: { x: 30, y: 40, width: 200, height: 80 }
950-
}
951-
])
959+
layoutStore.batchUpdateNodeBounds(
960+
graph.rootGraph.id,
961+
[
962+
{
963+
nodeId: node.id,
964+
bounds: { x: 30, y: 40, width: 200, height: 80 }
965+
}
966+
],
967+
{ source: LayoutSource.Canvas }
968+
)
952969
pos[0] = 50
953970
size[1] = 90
954971

@@ -970,12 +987,16 @@ describe('layout geometry projection', () => {
970987
node.pos = [10, 20]
971988
node.size = [100, 50]
972989
graph.add(node)
973-
layoutStore.batchUpdateNodeBounds(graph.rootGraph.id, [
974-
{
975-
nodeId: node.id,
976-
bounds: { x: 30, y: 40, width: 200, height: 80 }
977-
}
978-
])
990+
layoutStore.batchUpdateNodeBounds(
991+
graph.rootGraph.id,
992+
[
993+
{
994+
nodeId: node.id,
995+
bounds: { x: 30, y: 40, width: 200, height: 80 }
996+
}
997+
],
998+
{ source: LayoutSource.Canvas }
999+
)
9791000

9801001
node.pos = [50, 60]
9811002

@@ -988,12 +1009,16 @@ describe('layout geometry projection', () => {
9881009
node.pos = [10, 20]
9891010
node.size = [100, 50]
9901011
graph.add(node)
991-
layoutStore.batchUpdateNodeBounds(graph.rootGraph.id, [
992-
{
993-
nodeId: node.id,
994-
bounds: { x: 30, y: 40, width: 200, height: 80 }
995-
}
996-
])
1012+
layoutStore.batchUpdateNodeBounds(
1013+
graph.rootGraph.id,
1014+
[
1015+
{
1016+
nodeId: node.id,
1017+
bounds: { x: 30, y: 40, width: 200, height: 80 }
1018+
}
1019+
],
1020+
{ source: LayoutSource.Canvas }
1021+
)
9971022

9981023
node.size = [300, 90]
9991024

@@ -1006,12 +1031,16 @@ describe('layout geometry projection', () => {
10061031
node.pos = [10, 20]
10071032
node.size = [100, 50]
10081033
graph.add(node)
1009-
layoutStore.batchUpdateNodeBounds(graph.rootGraph.id, [
1010-
{
1011-
nodeId: node.id,
1012-
bounds: { x: 30, y: 40, width: 200, height: 80 }
1013-
}
1014-
])
1034+
layoutStore.batchUpdateNodeBounds(
1035+
graph.rootGraph.id,
1036+
[
1037+
{
1038+
nodeId: node.id,
1039+
bounds: { x: 30, y: 40, width: 200, height: 80 }
1040+
}
1041+
],
1042+
{ source: LayoutSource.Canvas }
1043+
)
10151044

10161045
node.pos = [50, 60]
10171046
node.setSize([node.size[0], 120])

src/lib/litegraph/src/Reroute.store.test.ts

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import { enableSubgraphNodeCreation } from '@/lib/litegraph/src/subgraph/__fixtu
2121
import type { SerialisableGraph } from '@/lib/litegraph/src/types/serialisation'
2222
import { useLayoutMutations } from '@/renderer/core/layout/operations/layoutMutations'
2323
import { layoutStore } from '@/renderer/core/layout/store/layoutStore'
24+
import { LayoutSource } from '@/renderer/core/layout/types'
2425
import { useRerouteStore } from '@/stores/rerouteStore'
2526
import { graphScopeOf } from '@/types/graphScopeId'
2627
import { toRerouteId } from '@/types/rerouteId'
@@ -62,10 +63,14 @@ describe('Reroute ↔ rerouteStore integration', () => {
6263
const incumbent = new Reroute(toRerouteId(1), graph, [0, 0])
6364
vi.spyOn(console, 'error').mockImplementation(() => {})
6465
useRerouteStore().registerReroute(graphScopeOf(graph), incumbent._chain)
65-
useLayoutMutations().createReroute(graph.rootGraph.id, incumbent.id, {
66-
x: 0,
67-
y: 0
68-
})
66+
useLayoutMutations(LayoutSource.Canvas).createReroute(
67+
graph.rootGraph.id,
68+
incumbent.id,
69+
{
70+
x: 0,
71+
y: 0
72+
}
73+
)
6974

7075
const collision = new Reroute(incumbent.id, graph, [10, 10])
7176

@@ -421,10 +426,14 @@ describe('Reroute position lives only in layoutStore', () => {
421426

422427
// Move it in the store only. A mirrored copy on the class could not see
423428
// this without a synchronisation step.
424-
useLayoutMutations().moveReroute(graph.rootGraph.id, reroute.id, {
425-
x: 300,
426-
y: 400
427-
})
429+
useLayoutMutations(LayoutSource.Canvas).moveReroute(
430+
graph.rootGraph.id,
431+
reroute.id,
432+
{
433+
x: 300,
434+
y: 400
435+
}
436+
)
428437

429438
expect([...reroute.pos]).toEqual([300, 400])
430439
expect(reroute.pos).toBe(pos)

src/renderer/core/canvas/canvasStore.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import type {
1515
} from '@/lib/litegraph/src/litegraph'
1616
import { promoteRecommendedWidgets } from '@/core/graph/subgraph/promotionUtils'
1717
import { useLayoutMutations } from '@/renderer/core/layout/operations/layoutMutations'
18+
import { LayoutSource } from '@/renderer/core/layout/types'
1819
import { app } from '@/scripts/app'
1920
import type { NodeId } from '@/types/nodeId'
2021
import { isLGraphGroup, isLGraphNode, isReroute } from '@/utils/litegraphUtil'
@@ -179,7 +180,7 @@ export const useCanvasStore = defineStore('canvas', () => {
179180
isGhostPlacing.value = e.detail.active
180181
const graphId = rootGraphId.value
181182
if (e.detail.active && graphId) {
182-
const mutations = useLayoutMutations()
183+
const mutations = useLayoutMutations(LayoutSource.Canvas)
183184
mutations.bringNodeToFront(graphId, e.detail.nodeId)
184185
}
185186
}

src/renderer/core/canvas/litegraph/arrangeForLegacyRender.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ describe('arrangeForLegacyRender', () => {
5151
const graph = new LGraph()
5252
const first = addedNode(graph)
5353
const second = addedNode(graph)
54-
const mutations = canvasLayoutMutations()
54+
const mutations = canvasLayoutMutations
5555
mutations.setNodeZIndex(graph.id, first.id, 2)
5656
mutations.setNodeZIndex(graph.id, second.id, 1)
5757

0 commit comments

Comments
 (0)