From 74d285bda9fb09f8e017da9573c3013759af6585 Mon Sep 17 00:00:00 2001 From: Yourz Date: Sat, 21 Feb 2026 14:48:57 +0800 Subject: [PATCH] fix: resolve bookmark lookup for subgraph blueprints (#9057) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Bookmarked subgraph blueprint nodes were not appearing in the favorites tree because `buildBookmarkTree` looked up nodes only in `nodeDefsByName`, which excludes subgraph blueprints. ## Changes - **What**: Added `allNodeDefsByName` computed in `nodeDefStore` that includes both regular nodes and subgraph blueprints. Updated `nodeBookmarkStore.buildBookmarkTree` to use it for bookmark resolution. ## Review Focus - `allNodeDefsByName` iterates over `nodeDefs` (which merges `subgraphBlueprints` + `nodeDefsByName`). Confirm this doesn't introduce performance concerns for large node sets. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9057-fix-resolve-bookmark-lookup-for-subgraph-blueprints-30e6d73d365081259bcae9d0c197de43) by [Unito](https://www.unito.io) --- src/stores/nodeBookmarkStore.ts | 2 +- src/stores/nodeDefStore.test.ts | 52 +++++++++++++++++++++++++++++++++ src/stores/nodeDefStore.ts | 9 ++++++ 3 files changed, 62 insertions(+), 1 deletion(-) diff --git a/src/stores/nodeBookmarkStore.ts b/src/stores/nodeBookmarkStore.ts index 7ddabdd777..75ba717ed4 100644 --- a/src/stores/nodeBookmarkStore.ts +++ b/src/stores/nodeBookmarkStore.ts @@ -53,7 +53,7 @@ export const useNodeBookmarkStore = defineStore('nodeBookmark', () => { const parts = bookmark.split('/') const name = parts.pop() ?? '' const category = parts.join('/') - const srcNodeDef = nodeDefStore.nodeDefsByName[name] + const srcNodeDef = nodeDefStore.allNodeDefsByName[name] if (!srcNodeDef) { return null } diff --git a/src/stores/nodeDefStore.test.ts b/src/stores/nodeDefStore.test.ts index 242b457b6e..666c8bdb01 100644 --- a/src/stores/nodeDefStore.test.ts +++ b/src/stores/nodeDefStore.test.ts @@ -279,6 +279,58 @@ describe('useNodeDefStore', () => { }) }) + describe('allNodeDefsByName', () => { + it('should include all node defs by name', () => { + const node1 = createMockNodeDef({ name: 'Node1' }) + const node2 = createMockNodeDef({ name: 'Node2' }) + + store.updateNodeDefs([node1, node2]) + + expect(store.allNodeDefsByName).toHaveProperty('Node1') + expect(store.allNodeDefsByName).toHaveProperty('Node2') + expect(store.allNodeDefsByName['Node1'].name).toBe('Node1') + expect(store.allNodeDefsByName['Node2'].name).toBe('Node2') + }) + + it('should include deprecated and experimental nodes', () => { + const normal = createMockNodeDef({ name: 'Normal' }) + const deprecated = createMockNodeDef({ + name: 'Deprecated', + deprecated: true + }) + const experimental = createMockNodeDef({ + name: 'Experimental', + experimental: true + }) + + store.updateNodeDefs([normal, deprecated, experimental]) + + expect(store.allNodeDefsByName).toHaveProperty('Normal') + expect(store.allNodeDefsByName).toHaveProperty('Deprecated') + expect(store.allNodeDefsByName).toHaveProperty('Experimental') + }) + + it('should include nodes filtered out of visibleNodeDefs', () => { + const normal = createMockNodeDef({ + name: 'Normal', + deprecated: false + }) + const deprecated = createMockNodeDef({ + name: 'Deprecated', + deprecated: true + }) + + store.updateNodeDefs([normal, deprecated]) + + // visibleNodeDefs filters out deprecated by default + expect(store.visibleNodeDefs).toHaveLength(1) + + // allNodeDefsByName includes all + expect(store.allNodeDefsByName).toHaveProperty('Normal') + expect(store.allNodeDefsByName).toHaveProperty('Deprecated') + }) + }) + describe('performance', () => { it('should perform single traversal for multiple filters', () => { let filterCallCount = 0 diff --git a/src/stores/nodeDefStore.ts b/src/stores/nodeDefStore.ts index 3b95539814..754c432f49 100644 --- a/src/stores/nodeDefStore.ts +++ b/src/stores/nodeDefStore.ts @@ -371,6 +371,14 @@ export const useNodeDefStore = defineStore('nodeDef', () => { } return types }) + const allNodeDefsByName = computed(() => { + const map: Record = {} + for (const nodeDef of nodeDefs.value) { + map[nodeDef.name] = nodeDef + } + return map + }) + const visibleNodeDefs = computed(() => { return nodeDefs.value.filter((nodeDef) => nodeDefFilters.value.every((filter) => filter.predicate(nodeDef)) @@ -496,6 +504,7 @@ export const useNodeDefStore = defineStore('nodeDef', () => { return { nodeDefsByName, nodeDefsByDisplayName, + allNodeDefsByName, showDeprecated, showExperimental, showDevOnly,