fix: track subgraph history#2141
fix: track subgraph history#2141maxy-shpfy wants to merge 1 commit into04-21-fix_annotations_in_configuration_section_respect_ignore_keys_listfrom
Conversation
🎩 PreviewA preview build has been created at: |
|
Warning This pull request is not mergeable via GitHub because a downstack PR is open. Once all requirements are satisfied, merge this PR as a stack on Graphite.
This stack of pull requests is managed by Graphite. Learn more about stacking. |
f53b532 to
1c65536
Compare
| if (!active) return false; | ||
| const task = active.tasks.find((t) => t.$id === taskEntityId); | ||
| return task?.subgraphSpec !== undefined; | ||
| } |
There was a problem hiding this comment.
🤖 This is an AI-generated code review comment.
** silently ignores its spec parameter**
The method now unconditionally uses this.activeSpec instead of the passed spec argument. All current callers happen to pass the active spec, so behaviour is correct today — but the public API is now misleading. A future caller that passes a different spec (e.g. a secondary view or prefetch path) will silently search the wrong spec with no type error.
Consider either dropping the parameter entirely and updating callers, or using the passed spec directly:
isTaskSubgraph(spec: ComponentSpec, taskEntityId: string): boolean {
const task = spec.tasks.find((t) => t.$id === taskEntityId);
return task?.subgraphSpec !== undefined;
}| case "outputs": | ||
| return spec.outputs.map(outputModelToSpec); | ||
| case "implementation": | ||
| return serializer.serialize(spec).implementation; |
There was a problem hiding this comment.
🤖 This is an AI-generated code review comment.
Proxy implementation getter triggers full serialization on every access
case "implementation":
return serializer.serialize(spec).implementation;serializer.serialize(spec) recursively serializes the entire spec (all tasks, bindings, arguments) every time .implementation is accessed on the proxy. The proxy is created fresh on each access to task.resolvedComponentSpec (a new Proxy object is returned each time), so MobX observer components can trigger this serialization repeatedly per render cycle.
In practice the main consumers only need .implementation to type-check (e.g. isSubgraph, isGraphImplementation). A lighter alternative: since we already know a subgraphSpec exists when this proxy is created, we can return a sentinel graph implementation directly:
case "implementation":
// Subgraph proxies always have a graph implementation.
// Full serialization is deferred to serializer.serialize() callers.
return { graph: serializer.buildGraph(spec) };Or, if the full serialized implementation is genuinely needed here, consider caching via a MobX @computed on the Task model rather than re-running on every proxy property access.
| if (argIdx >= 0) { | ||
| parentTask.setArgument(newName, parentTask.arguments[argIdx].value); | ||
| parentTask.removeArgumentByName(oldName); | ||
| } |
There was a problem hiding this comment.
🤖 This is an AI-generated code review comment.
Renamed argument drifts to end of task's argument list
parentTask.setArgument(newName, parentTask.arguments[argIdx].value);
parentTask.removeArgumentByName(oldName);setArgument appends at the end when newName doesn't already exist, then removeArgumentByName removes the old entry. The renamed argument moves from its original position to the end of parentTask.arguments. This changes the argument ordering in the serialized YAML on every input rename (minor, but affects output stability).
An in-place rename preserves the position:
if (argIdx >= 0) {
const arg = parentTask.arguments[argIdx];
parentTask.arguments[argIdx] = { ...arg, name: newName };
}(Wrapping this in a @modelAction on Task, e.g. renameArgument(oldName, newName), would be cleaner.)
|
@camielvs This is fixed in downstream PR |
| export function createComponentSpecProxy( | ||
| spec: ComponentSpec, | ||
| ): ComponentSpecJson { | ||
| return new Proxy({} as ComponentSpecJson, { |
There was a problem hiding this comment.
to make this more readable can we extract the Proxy value into a variable?
| */ | ||
| export function collectIdStack(spec: ComponentSpec): string[] { | ||
| const ids: string[] = []; | ||
| collectIds(spec, ids); |
There was a problem hiding this comment.
interesting to move away from an explicit return. Is that because of the recursion now introduced?
camielvs
left a comment
There was a problem hiding this comment.
lgtm. nice to see a lot of deleted (simplified/reused) code



Description
Subgraph specs are now stored as live
ComponentSpecmodel instances directly on theTaskentity (task.subgraphSpec) rather than as serialized JSON embedded intask.componentRef.spec. A newtask.resolvedComponentSpeccomputed property provides a unified read path that returns either a lazy proxy over the live subgraph model or the plain JSON spec fromcomponentRef.spec.Key changes:
Taskgains asubgraphSpec: ComponentSpec | undefinedprop and asetSubgraphSpecaction.setComponentRefnow automatically promotes any inline graph spec JSON to a live model viadeserializeSubgraphSpec, keeping raw JSON out of the runtime model tree.YamlDeserializercallsmaybeDeserializeSubgraphbefore generating a task's$id, so subgraph entity IDs are always allocated before the parent task ID — preserving thecollectIdStack/ReplayIdGeneratorordering contract.collectIdStackrecurses intotask.subgraphSpecbefore pushing the parent task's$id, matching the new deserializer ordering.JsonSerializerre-attaches the serialized subgraph intocomponentRef.specat serialization time, so the wire format is unchanged.createComponentSpecProxyprovides a lazy ES Proxy over a liveComponentSpecthat presents asComponentSpecJson, used byresolvedComponentSpecto avoid upfront serialization cost.NavigationStoreno longer maintains a separatenestedSpecsmap or asyncNestedSpecsreaction. Navigation into subgraphs now directly returnstask.subgraphSpec, andgetSpecAtDepthwalks the live model tree. A newparentContextcomputed property exposes the parent spec and task ID when navigating inside a subgraph.renameInput,renameOutput,deleteInput,deleteOutput) now accept an optionalParentContextand propagate port renames and deletions to the parent spec's bindings and task arguments via newio.parentPropagationhelpers.deleteNodeonNodeTypeManifestnow accepts an optionalParentContextso input and output node deletions can propagate to the parent spec.collectValidationIssues,unpackSubgraph,createSubgraph, and all UI components (TaskDetails,RootNode,SubgraphNode,PipelineTreeContent, resolution components, etc.) are updated to usetask.subgraphSpecandtask.resolvedComponentSpecinstead of castingtask.componentRef.spec.isSubgraphTaskutility function and relatednestedSpecslookups are removed in favour of the directtask.subgraphSpec !== undefinedcheck.Related Issue and Pull requests
Type of Change
Checklist
Screenshots (if applicable)
Screen Recording 2026-04-22 at 10.21.26 PM.mov (uploaded via Graphite)
Test Instructions
componentSpecProxy,persistentUndo,io.parentPropagation, andcollectIssuesto confirm ID replay round-trips and parent propagation work correctly for specs with subgraphs.Additional Comments