feat: add event-driven group membership updates on insert/delete/move

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Guilhem
2026-02-25 10:16:41 +00:00
parent f8a53a817d
commit 936a2882b5
2 changed files with 61 additions and 0 deletions
@@ -54,6 +54,7 @@
} from '../agentToolUtils'
import { loadFlowModuleState } from '../flowStateUtils.svelte'
import { getNoteEditorContext } from '$lib/components/graph/noteEditor.svelte'
import { getGroupEditorContext } from '$lib/components/graph/groupEditor.svelte'
interface Props {
sidebarSize?: number | undefined
@@ -125,6 +126,7 @@
// Get NoteEditor context for note position updates
const noteEditorContext = getNoteEditorContext()
const groupEditorContext = getGroupEditorContext()
$effect(() => {
if (!moveManager.movingModuleId) return
@@ -569,6 +571,7 @@
selectNextId(id)
removeAtId(flowStore.val.value.modules, id)
}
groupEditorContext?.groupEditor.removeNode(id)
refreshStateStore(flowStore)
onDelete?.(id)
delete flowStateStore.val[id]
@@ -629,6 +632,11 @@
}
targetModules.splice(insertIndex, 0, ...removedModules)
selectionManager.selectByIds(removedModules.map((m) => m.id))
for (const m of removedModules) {
groupEditorContext?.groupEditor.handleNodeMoved(
m.id, detail.sourceId, detail.targetId
)
}
} else {
let indexToRemove = originalModules.findIndex((m) => moveManager.movingModuleId == m.id)
let [removedModule] = originalModules.splice(indexToRemove, 1)
@@ -639,6 +647,9 @@
}
targetModules.splice(insertIndex, 0, removedModule)
selectionManager.selectId(removedModule.id)
groupEditorContext?.groupEditor.handleNodeMoved(
removedModule.id, detail.sourceId, detail.targetId
)
}
moveManager.clearMoving()
} else {
@@ -676,6 +687,7 @@
toolKind
)
const id = targetModules[index].id
groupEditorContext?.groupEditor.addInsertedNode(id, detail.sourceId, detail.targetId)
selectionManager.selectId(id)
if (detail.inlineScript?.instructions) {
@@ -195,6 +195,55 @@ export class GroupEditor {
isAvailable(): boolean {
return !!this.flowStore.val.value
}
/** Remove a deleted node from all groups. Removes empty groups. */
removeNode(nodeId: string): void {
const groups = this.getGroups()
let changed = false
for (const group of groups) {
const idx = group.module_ids.indexOf(nodeId)
if (idx !== -1) {
group.module_ids.splice(idx, 1)
changed = true
}
}
if (changed) {
this.setGroups(groups.filter((g) => g.module_ids.length > 0))
}
}
/** Add a newly inserted node to the group that contains both its neighbors. */
addInsertedNode(newNodeId: string, sourceId?: string, targetId?: string): void {
if (!sourceId || !targetId) return
const groups = this.getGroups()
for (const group of groups) {
if (group.module_ids.includes(sourceId) && group.module_ids.includes(targetId)) {
group.module_ids.push(newNodeId)
this.setGroups(groups)
return
}
}
}
/** Handle a node that was moved to a new position in the flow. */
handleNodeMoved(movedId: string, sourceId?: string, targetId?: string): void {
const groups = this.getGroups()
const currentGroup = groups.find((g) => g.module_ids.includes(movedId))
if (currentGroup) {
// Was in a group — only keep if BOTH neighbors are in the same group
const sourceInGroup = sourceId ? currentGroup.module_ids.includes(sourceId) : false
const targetInGroup = targetId ? currentGroup.module_ids.includes(targetId) : false
if (!(sourceInGroup && targetInGroup)) {
// Moved to boundary or outside the group — remove
currentGroup.module_ids = currentGroup.module_ids.filter((id) => id !== movedId)
this.setGroups(groups.filter((g) => g.module_ids.length > 0))
}
} else {
// Wasn't in a group — check if moved into one
this.addInsertedNode(movedId, sourceId, targetId)
}
}
}
export type GroupEditorContext = {