fix: improvements

This commit is contained in:
2026-03-28 22:20:19 +01:00
parent cbd8f1568b
commit 223336c606
10 changed files with 506 additions and 249 deletions

View File

@@ -9,6 +9,13 @@ import type { ResolvedContentResult, SourceRenderingLogicContext } from '@/lib/g
import { getConfigContent, getConfigType, getConfigTypeId, type ConfigTypeId } from '@/lib/graph/rendering'
import { isReachable, resolveExtendsRef, getTemplateRefs, type NodeLike } from '@/lib/graph/templateRefs'
// Module-level cache for resolved Nunjucks output.
// Keyed by a fingerprint of all inputs (template contents + variable/data values + function bodies).
// Avoids re-running the Nunjucks environment when the same inputs are seen again (e.g. undo/redo,
// multiple rendering nodes sharing the same config, rapid edits cycling back to a prior value).
const MAX_RESOLVE_CACHE = 200
const resolveCache = new Map<string, ResolvedContentResult>()
export async function getResolvedContentForConfig(context: SourceRenderingLogicContext): Promise<ResolvedContentResult> {
const { nodes, edges, sourceNodeId, renderNodeId } = context
const srcId = sourceNodeId
@@ -107,6 +114,24 @@ export async function getResolvedContentForConfig(context: SourceRenderingLogicC
}
}
// Build cache key from all inputs that affect the resolved output.
const allTemplateContents = [...configIdsUsed]
.map((cid) => {
const node = nodes.find((n) => n.id === cid)
return getConfigContent((node?.data ?? undefined) as Record<string, unknown> | undefined)
})
.join('\x00')
const allFunctionBodies = [...functionIdsToRegister]
.map((fid) => {
const node = nodes.find((n) => n.id === fid)
return ((node?.data as Record<string, unknown>)?.body as string) ?? ''
})
.join('\x00')
const cacheKey = `${srcId}\x01${JSON.stringify(nunjucksContext)}\x01${allTemplateContents}\x01${allFunctionBodies}`
const cached = resolveCache.get(cacheKey)
if (cached) return cached
const env = new nunjucks.Environment([configLoader], { autoescape: false })
const formatFilterResult = (r: unknown): string => {
@@ -248,7 +273,13 @@ export async function getResolvedContentForConfig(context: SourceRenderingLogicC
return
}
const resolved = afterNunjucks.replace(/(\r?\n)(\s*\r?\n)+/g, '$1').trim()
resolve({ resolved, outputTypeId })
const result: ResolvedContentResult = { resolved, outputTypeId }
// FIFO eviction when cache is full
if (resolveCache.size >= MAX_RESOLVE_CACHE) {
resolveCache.delete(resolveCache.keys().next().value as string)
}
resolveCache.set(cacheKey, result)
resolve(result)
})
})
}

View File

@@ -56,6 +56,7 @@
*/
import { useCallback, useDeferredValue, useEffect, useMemo, useRef, useState } from 'react'
import { useShallow } from 'zustand/react/shallow'
import { useAbstractNode } from '@/lib/graph/abstractNode'
import { useSyncConnectionStatus } from '@/lib/graph/nodeLifecycle'
import { useCanvasStore, dispatchCanvasCommand } from '@/app/canvas/canvasStore'
@@ -175,8 +176,11 @@ export function useRenderingNodeState(
// Subscribe to store graph so we re-render when any node/edge changes (e.g. ascendant data).
// Context only exposes a ref, so we wouldn't re-render when another node updates otherwise.
const storeNodes = useCanvasStore((s) => s.graph.nodes)
const storeEdges = useCanvasStore((s) => s.graph.edges)
// Single combined subscription (vs two separate) halves listener overhead; useShallow prevents
// re-renders when non-graph slices (ui/path) change.
const { storeNodes, storeEdges } = useCanvasStore(
useShallow((s) => ({ storeNodes: s.graph.nodes, storeEdges: s.graph.edges }))
)
const nodes = storeNodes.length > 0 ? storeNodes : contextNodes
const edges = storeEdges.length > 0 ? storeEdges : contextEdges