Skip to content

refactor(editor): Migrate node state consumers to document store facade (no-changelog)#26367

Closed
r00gm wants to merge 19 commits intomasterfrom
experiment/facade-first
Closed

refactor(editor): Migrate node state consumers to document store facade (no-changelog)#26367
r00gm wants to merge 19 commits intomasterfrom
experiment/facade-first

Conversation

@r00gm
Copy link
Contributor

@r00gm r00gm commented Feb 28, 2026

Summary

  • Migrates all node state reads/writes from workflowsStore/workflowState to the workflowDocumentStore facade (Strangler Fig pattern)
  • Covers ~100+ call sites across 55+ files: NDV, canvas, AI assistant, credentials, settings, and utility composables
  • The facade currently delegates to the old store — no behavioral change, purely a caller migration
  • Prepares for PR 7 where state ownership flips to the document store

PRs included (as commits)

  1. PR 1useWorkflowDocumentNodes facade + ESLint guards
  2. PR 2 — Migrate store-level read consumers (16 files)
  3. PR 3 — Migrate useCanvasOperations.ts reads (50 sites)
  4. PR 4 — Migrate useCanvasOperations.ts writes (8 sites)
  5. PR 5 — Migrate all remaining consumers (~53 files)
  6. PR 6 — Migrate WorkflowCanvas nodes param to document store

Test plan

  • E2E tests pass (this draft PR)
  • Manual smoke test: create/edit/delete nodes, canvas renders, NDV opens, credentials work
  • Typecheck passes (pnpm typecheck ✅)
  • Build passes (pnpm build ✅)

🤖 Generated with Claude Code

workflowsStore.workflow.nodes,
(node) => node.type === updateInformation.key,
) as INodeUi;
const nodeType = deps.getNodeType(latestNode.type);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

setLastNodeParameters uses deps.getNodeType(latestNode.type) before checking latestNode; if findLast(...) returns undefined, latestNode.type will throw and the later if (latestNode) cannot prevent it.

Details

✨ AI Reasoning
​The code searches for the latest node matching a type. If none exists, the search returns no node. The current control flow still reads a property from that missing node before the later check, so the later check cannot prevent the failure.

🔧 How do I fix it?
Trace execution paths carefully. Ensure precondition checks happen before using values, validate ranges before checking impossible conditions, and don't check for states that the code has already ruled out.

Reply @AikidoSec feedback: [FEEDBACK] to get better review comments in the future.
Reply @AikidoSec ignore: [REASON] to ignore this issue.
More info

}
}

function setLastNodeParameters(updateInformation: IUpdateInformation): void {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add an early guard for missing latestNode in setLastNodeParameters (return if latestNode is undefined) to avoid using latestNode.type before confirming it exists and to flatten the control flow.

Details

✨ AI Reasoning
​setLastNodeParameters reads latestNode via findLast but uses latestNode.type before confirming latestNode exists, then checks latestNode later. An early return when latestNode is undefined (or otherwise invalid) avoids doing work and prevents a possible runtime error, and flattens the control flow.

🔧 How do I fix it?
Place parameter validation and guard clauses at the function start. Use early returns to reduce nesting levels and improve readability.

Reply @AikidoSec feedback: [FEEDBACK] to get better review comments in the future.
Reply @AikidoSec ignore: [REASON] to ignore this issue.
More info

// Apply methods — CRDT entry point in Phase C. Today they just delegate.
// -----------------------------------------------------------------------

function applySetNodes(nodes: INodeUi[]) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'applySetNodes' purpose is unclear; rename or document whether it applies a CRDT operation, proxies to workflowsStore, or both.

Details

✨ AI Reasoning
​A developer reading useWorkflowDocumentNodes should be able to quickly understand what each public helper does. The newly added function named 'applySetNodes' delegates to workflowsStore.setNodes but the 'apply' prefix is vague without additional documentation. Even with the nearby comment indicating 'CRDT entry point', the function name does not make its role explicit (e.g., applying a CRDT operation vs applying a local change). This ambiguity makes it harder to reason about when to call this function vs other setters.

🔧 How do I fix it?
Use descriptive verb-noun function names, add docstrings explaining the function's purpose, or provide meaningful return type hints.

Reply @AikidoSec feedback: [FEEDBACK] to get better review comments in the future.
Reply @AikidoSec ignore: [REASON] to ignore this issue.
More info

@r00gm r00gm force-pushed the experiment/facade-first branch from fee7b84 to adafbfa Compare February 28, 2026 10:07
}

function setLastNodeParameters(updateInformation: IUpdateInformation): void {
const latestNode = findLast(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

setLastNodeParameters: findLast(...) can return undefined, but latestNode is cast to INodeUi and latestNode.type is accessed unconditionally, causing a guaranteed runtime error when no node matches.

Details

✨ AI Reasoning
​The function looks up the most recent node of a given type and then derives parameters from its node type definition. However, the lookup can legitimately return no node. In that case, the code still dereferences properties on the missing result, so the function would throw before it can safely return.

🔧 How do I fix it?
Trace execution paths carefully. Ensure precondition checks happen before using values, validate ranges before checking impossible conditions, and don't check for states that the code has already ruled out.

Reply @AikidoSec feedback: [FEEDBACK] to get better review comments in the future.
Reply @AikidoSec ignore: [REASON] to ignore this issue.
More info

const workflowDocumentTags = useWorkflowDocumentTags();
const workflowDocumentPinData = useWorkflowDocumentPinData();
const workflowDocumentTimestamps = useWorkflowDocumentTimestamps();
const nodeTypesStore = useNodeTypesStore();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The workflow document store now calls useUIStore().markStateDirty and depends on useNodeTypesStore; this mixes document persistence with UI side-effects. Move UI notifications out of the store (emit events or use a decoupled callback).

Details

✨ AI Reasoning
​A workflow document store factory was extended to directly import and call UI-side mutation functions (markStateDirty) and to pull node-type resolution from the node types store. This couples persistence/document store logic to UI side effects and node-type resolution, mixing responsibilities: document lifecycle/state management should not directly perform UI mutations or import UI-level stores. The added onStateDirty callback calling a UI mutation and direct dependency on node types increases coupling and makes the store responsible both for document state and triggering UI behavior.

🔧 How do I fix it?
Split classes that handle database, HTTP, and UI concerns into separate, focused classes.

Reply @AikidoSec feedback: [FEEDBACK] to get better review comments in the future.
Reply @AikidoSec ignore: [REASON] to ignore this issue.
More info

export function useCanvasOperations() {
const rootStore = useRootStore();
const workflowsStore = useWorkflowsStore();
const workflowDocumentStore = computed(() =>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a new workflowDocumentStore computed and many document-store call sites inside this already very large composable; this increases cross-cutting responsibilities and makes the module harder to maintain.

Details

✨ AI Reasoning
​The modifications add a new reactive computed (workflowDocumentStore) and replace many direct workflowsStore node lookups with calls into the document store throughout the same large module. This increases the module's surface area and mixes multiple sources of truth (workflowsStore and workflowDocumentStore) within an already large composable, making navigation and reasoning harder. The change centralizes migration logic inside this very large file rather than extracting a focused facade or smaller helpers, which worsens maintainability for a file that already contains a lot of responsibilities (node operations, connection ops, workspace lifecycle, import/export, telemetry, UI interactions).

🔧 How do I fix it?
Split large files into smaller, focused modules. Each file should have a single responsibility.

Reply @AikidoSec feedback: [FEEDBACK] to get better review comments in the future.
Reply @AikidoSec ignore: [REASON] to ignore this issue.
More info

Comment on lines +233 to +247
function updateNodeProperties(updateInformation: INodeUpdatePropertiesInformation): void {
const nodeIndex = workflowsStore.workflow.nodes.findIndex(
(node) => node.name === updateInformation.name,
);

if (nodeIndex !== -1) {
for (const key of Object.keys(updateInformation.properties)) {
const typedKey = key as keyof INodeUpdatePropertiesInformation['properties'];
const property = updateInformation.properties[typedKey];

const changed = updateNodeAtIndex(nodeIndex, { [key]: property });

if (changed) {
void onStateDirty.trigger();
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updateNodeProperties nests the main update logic in if (nodeIndex !== -1) { ... }. Use an early return when the nodeIndex is -1 to reduce nesting (e.g., if (nodeIndex === -1) return;).

Show fix
Suggested change
function updateNodeProperties(updateInformation: INodeUpdatePropertiesInformation): void {
const nodeIndex = workflowsStore.workflow.nodes.findIndex(
(node) => node.name === updateInformation.name,
);
if (nodeIndex !== -1) {
for (const key of Object.keys(updateInformation.properties)) {
const typedKey = key as keyof INodeUpdatePropertiesInformation['properties'];
const property = updateInformation.properties[typedKey];
const changed = updateNodeAtIndex(nodeIndex, { [key]: property });
if (changed) {
void onStateDirty.trigger();
}
function updateNodeProperties(updateInformation: INodeUpdatePropertiesInformation): void {
if (nodeIndex === -1) return;
for (const key of Object.keys(updateInformation.properties)) {
const typedKey = key as keyof INodeUpdatePropertiesInformation['properties'];
const property = updateInformation.properties[typedKey];
const changed = updateNodeAtIndex(nodeIndex, { [key]: property });
if (changed) {
void onStateDirty.trigger();
Details

✨ AI Reasoning
​The function's primary work (iterating over properties and updating nodes) is entirely inside a conditional that checks node existence: if (nodeIndex !== -1) { ... }. This nests the main logic one level deeper than needed. Using an early return when the node is not found (i.e., if (nodeIndex === -1) return;) would remove the extra nesting and make the function's intent clearer. The change was introduced by this PR (new file), so it is a newly introduced opportunity for simplification.

Reply @AikidoSec feedback: [FEEDBACK] to get better review comments in the future.
Reply @AikidoSec ignore: [REASON] to ignore this issue.
More info

@n8n-assistant n8n-assistant bot added the n8n team Authored by the n8n team label Feb 28, 2026
@codecov
Copy link

codecov bot commented Feb 28, 2026

Bundle Report

Changes will increase total bundle size by 14.27kB (0.03%) ⬆️. This is within the configured threshold ✅

Detailed changes
Bundle name Size Change
editor-ui-esm 42.41MB 14.27kB (0.03%) ⬆️

Affected Assets, Files, and Routes:

view changes for bundle: editor-ui-esm

Assets Changed:

Asset Name Size Change Total Size Change (%)
assets/ParameterInputList-*.js 515 bytes 1.47MB 0.04%
assets/index-*.js 1.02kB 1.14MB 0.09%
assets/users.store-*.js 8.62kB 1.07MB 0.81%
assets/RunData-*.js -42 bytes 1.06MB -0.0%
assets/useCanvasMapping-*.js 170 bytes 442.81kB 0.04%
assets/NodeView-*.js 406 bytes 147.02kB 0.28%
assets/usePostMessageHandler-*.js 144 bytes 124.45kB 0.12%
assets/canvas.eventBus-*.js 268 bytes 116.07kB 0.23%
assets/VirtualSchema-*.js 127 bytes 91.74kB 0.14%
assets/useCanvasOperations-*.js 992 bytes 91.69kB 1.09%
assets/NodeSettings-*.js 191 bytes 82.88kB 0.23%
assets/TriggerPanel-*.js 103 bytes 57.24kB 0.18%
assets/NodeDetailsView-*.js 16 bytes 43.8kB 0.04%
assets/NodeDetailsViewV2-*.js 16 bytes 39.2kB 0.04%
assets/useLogsTreeExpand-*.js 252 bytes 38.48kB 0.66%
assets/useRunWorkflow-*.js 648 bytes 26.64kB 2.49%
assets/usePushConnection-*.js 104 bytes 24.5kB 0.43%
assets/CanvasRunWorkflowButton-*.js 170 bytes 22.37kB 0.77%
assets/assistant.store-*.js 248 bytes 19.06kB 1.32%
assets/SettingsLogStreamingView-*.js 59 bytes 17.29kB 0.34%
assets/ContactAdministratorToInstall-*.js 249 bytes 5.54kB 4.71%
assets/useExecutionDebugging-*.js 14 bytes 5.2kB 0.27%
assets/SetupWorkflowCredentialsButton-*.js -26 bytes 4.42kB -0.59%

Files in assets/index-*.js:

  • ./src/app/components/MainHeader/WorkflowPublishModal.vue → Total Size: 369 bytes

  • ./src/app/components/FromAiParametersModal.vue → Total Size: 362 bytes

  • ./src/app/composables/useToolParameters.ts → Total Size: 7.39kB

  • ./src/app/composables/useWorkflowUpdate.ts → Total Size: 9.09kB

Files in assets/users.store-*.js:

  • ./src/app/stores/focusPanel.store.ts → Total Size: 4.22kB

  • ./src/app/stores/workflowDocument.store.ts → Total Size: 2.92kB

  • ./src/app/composables/useWorkflowSaving.ts → Total Size: 12.22kB

  • ./src/app/composables/useWorkflowHelpers.ts → Total Size: 24.31kB

  • ./src/app/stores/ui.store.ts → Total Size: 17.16kB

  • ./src/app/composables/useNodeHelpers.ts → Total Size: 23.23kB

Files in assets/NodeView-*.js:

  • ./src/app/components/FocusSidebar.vue → Total Size: 324 bytes

  • ./src/app/components/FocusPanel.vue → Total Size: 316 bytes

Files in assets/VirtualSchema-*.js:

  • ./src/app/composables/useNodeExecution.ts → Total Size: 9.08kB

  • ./src/app/components/NodeExecuteButton.vue → Total Size: 159 bytes

Files in assets/useCanvasOperations-*.js:

  • ./src/app/composables/useCanvasOperations.ts → Total Size: 73.22kB

Files in assets/useRunWorkflow-*.js:

  • ./src/app/composables/useRunWorkflow.ts → Total Size: 14.0kB

  • ./src/app/composables/useNodeDirtiness.ts → Total Size: 6.23kB

Files in assets/usePushConnection-*.js:

  • ./src/app/composables/usePushConnection/handlers/executionFinished.ts → Total Size: 12.4kB

Files in assets/CanvasRunWorkflowButton-*.js:

  • ./src/app/composables/useWorkflowExtraction.ts → Total Size: 12.93kB

@blacksmith-sh

This comment has been minimized.

@r00gm r00gm force-pushed the experiment/facade-first branch from bf27bac to f51a49f Compare March 2, 2026 07:29
workflowsStore.workflow.nodes,
(node) => node.type === updateInformation.key,
) as INodeUi;
const nodeType = deps.getNodeType(latestNode.type);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

setLastNodeParameters dereferences latestNode.type even though findLast can return undefined, so it can crash before the nodeType null-check runs.

Details

✨ AI Reasoning
​The code searches for a latest node matching a type key, but does not handle the case where no such node exists. Immediately after the search, it accesses a property on the result without checking it is defined, so the guard that follows cannot prevent a crash.

🔧 How do I fix it?
Trace execution paths carefully. Ensure precondition checks happen before using values, validate ranges before checking impossible conditions, and don't check for states that the code has already ruled out.

Reply @AikidoSec feedback: [FEEDBACK] to get better review comments in the future.
Reply @AikidoSec ignore: [REASON] to ignore this issue.
More info


// --- Composable ---

export function useWorkflowDocumentNodes(deps: WorkflowDocumentNodesDeps) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

useWorkflowDocumentNodes mixes node state mutations, event orchestration, and metadata bookkeeping. Split persistence (read/write) from event/metadata responsibilities to reduce coupling and improve testability.

Details

✨ AI Reasoning
​A new composable was added exposing a document-facing API (useWorkflowDocumentNodes) that both directly mutates the underlying workflowsStore (nodes, workflowObject.setNodes, nodeMetadata) and implements event hooks (onNodesChange/onStateDirty) and higher-level helpers (setLastNodeParameters, resetParametersLastUpdatedAt). This groups direct persistence/manipulation, event orchestration, and metadata bookkeeping into one module. Grouping these distinct responsibilities makes the module harder to test, evolve, and swap parts (e.g., CRDT/replication logic vs local store updates) and increases coupling between state mutation and side-effect coordination. Separating the pure read/write API from event/metadata orchestration would keep single responsibility per module.

🔧 How do I fix it?
Split classes that handle database, HTTP, and UI concerns into separate, focused classes.

Reply @AikidoSec feedback: [FEEDBACK] to get better review comments in the future.
Reply @AikidoSec ignore: [REASON] to ignore this issue.
More info

const i18n = useI18n();
const canvasStore = useCanvasStore();

const workflowDocumentStore = computed(() =>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added computed 'workflowDocumentStore' inside a large helpers file. This injects document-store access across many helpers, further coupling concerns in a monolithic file; consider splitting responsibilities.

Details

✨ AI Reasoning
​useNodeHelpers.ts is already a long file with many helper responsibilities. Introducing a computed 'workflowDocumentStore' that is then used across multiple helper functions spreads document-store concerns into the helper module, increasing cognitive load and mixing concerns that would be clearer if separated.

🔧 How do I fix it?
Split large files into smaller, focused modules. Each file should have a single responsibility.

Reply @AikidoSec feedback: [FEEDBACK] to get better review comments in the future.
Reply @AikidoSec ignore: [REASON] to ignore this issue.
More info

}
}

function setNodeValue(updateInformation: IUpdateInformation): void {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function 'setNodeValue' has a vague name—unclear whether it sets parameters, position, or other properties; document intent or use a clearer name (e.g., setNodeProperty or setNodeField) to clarify its contract.

Details

✨ AI Reasoning
​A function was added that sets an arbitrary key/value on a node, also triggering side-effects (state dirty event and metadata timestamp update). The function name uses a very generic term ('setNodeValue') that does not make it immediately clear whether it targets parameters, position, or general node properties. Because it performs both data mutation and metadata bookkeeping, its purpose is not self-evident from the name alone and would benefit from a more specific name or documentation so callers understand its contract without reading the implementation.

🔧 How do I fix it?
Use descriptive verb-noun function names, add docstrings explaining the function's purpose, or provide meaningful return type hints.

Reply @AikidoSec feedback: [FEEDBACK] to get better review comments in the future.
Reply @AikidoSec ignore: [REASON] to ignore this issue.
More info

@blacksmith-sh

This comment has been minimized.

r00gm and others added 12 commits March 2, 2026 12:18
… (no-changelog)

Create the nodes composable for the workflowDocument store as the first
step of the Strangler Fig migration (PR 1). The facade implements
node-mutation methods directly against workflowsStore, with deps
injection (getNodeType callback) and onStateDirty event for decoupled
side-effects. ESLint no-restricted-syntax warn rules guard against
direct workflowsStore node access during migration.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
…o-changelog)

Redirect read-only node access (allNodes, getNodeByName, getNodeById,
getNodes) from workflowsStore to the workflowDocumentStore facade in
16 consumer files. This is the second step of the Strangler Fig
migration toward useWorkflowDocumentNodes owning node state.
…elog)

- Fix variable shadowing in useCanvasOperations.copyNodes (TS2448)
- Add missing `computed` import in useInstallNode (TS2304)
- Add eslint-disable for createEventHook<void>() in useWorkflowDocumentNodes
- Add workflowsStore.allNodes fallback in useWorkflowSetupState and
  useWorkflowHelpers for backward compatibility during migration
- Fix useWorkflowSetupState tests: mock injectWorkflowDocumentStore
- Fix useCanvasOperations tests: add synchronous document store mock
  (async vi.mock factories do not intercept transitive dependency imports)
…tore (no-changelog)

Migrate SetupWorkflowCredentialsButton, useChatState, useContextMenuItems,
and useWorkflowSetupState to read node state from workflowDocumentStore.
Fix test suites to provide real document stores instead of Proxy mocks.
…-changelog)

Remove unused imports (computed in useChatState.test, workflowsStore in
SetupWorkflowCredentialsButton). Reconnect allNodes as a getter from
workflow.nodes in builder.store.test to fix 15 pre-existing test failures
caused by mockedStore detaching computed properties.
…odals (no-changelog)

Composables called from modals (rendered outside WorkflowLayout) cannot
use injectWorkflowDocumentStore() since the provide/inject tree doesn't
reach AppModals. Switch useWorkflowExtraction to the computed-from-store
pattern (same as useCanvasOperations) so the document store resolves
regardless of component tree position. Fix related test setups.
r00gm added 2 commits March 2, 2026 12:18
… store access (no-changelog)

After resetWorkspace() clears the workflow ID, the computed document
store in useCanvasOperations returns undefined, making addNode() a
no-op during template import. Move setWorkflowId() before
importTemplate() in both openWorkflowTemplate and
openWorkflowTemplateFromJSON so nodes are properly added.

Also switch useSetupWorkflowCredentialsModalState from inject to
computed pattern since it runs inside a teleported modal.
…gelog)

Fix test failures caused by migration from workflowsStore to
workflowDocumentStore. Add document store mocks to SQLEditor,
useReviewChanges, and LogsPanel tests so node resolution and
expression evaluation work correctly through the new store layer.
@r00gm r00gm force-pushed the experiment/facade-first branch from f64629f to 93ef5c7 Compare March 2, 2026 11:30
{ trackHistory = false } = {},
) {
const node = workflowsStore.getNodeById(id);
const node = workflowDocumentStore.value?.findNode(id);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replaced direct node lookup with optional workflowDocumentStore.value?.findNode — if the document store is undefined this update becomes a silent no-op. Consider preserving previous behavior or explicitly handling missing store.

Details

✨ AI Reasoning
​An update changed direct node lookup and mutation to use workflowDocumentStore.value?.findNode and optional chaining for set operations. If the computed document store is undefined (workflowId not set), previously-intended updates now silently no-op instead of acting on workflowsStore. This can mask failures and alter behavior during migration.

🔧 How do I fix it?
Remove debugging statements like console.log, debugger, dd(), or logic bypasses like || true. Keep legitimate logging for monitoring and error handling.

Reply @AikidoSec feedback: [FEEDBACK] to get better review comments in the future.
Reply @AikidoSec ignore: [REASON] to ignore this issue.
More info


workflowsStore.removeNodeExecutionDataById(id);
workflowsStore.removeNodeById(id);
workflowDocumentStore.value?.removeNodeById(id);
Copy link
Contributor

@aikido-pr-checks aikido-pr-checks bot Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Switched removal to workflowDocumentStore.value?.removeNodeById — when document store is undefined this will silently skip deletion. Handle missing store instead of relying on optional chaining.

Suggested change
workflowDocumentStore.value?.removeNodeById(id);
workflowDocumentStore.value.removeNodeById(id);
Details

✨ AI Reasoning
​The deleteNode implementation was changed to call workflowDocumentStore.value?.removeNodeById. When the computed document store resolves to undefined, node deletion will be skipped silently whereas earlier it always removed the node from workflowsStore. This can leave stale nodes and diverge behavior during migration.

Reply @AikidoSec feedback: [FEEDBACK] to get better review comments in the future.
Reply @AikidoSec ignore: [REASON] to ignore this issue.
More info

}

workflowsStore.addNode(nodeData);
workflowDocumentStore.value?.addNode(nodeData);
Copy link
Contributor

@aikido-pr-checks aikido-pr-checks bot Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed node addition to workflowDocumentStore.value?.addNode — this becomes a silent no-op if document store is undefined. Ensure adds fallback or explicitly error instead of optional chaining.

Show fix
Suggested change
workflowDocumentStore.value?.addNode(nodeData);
if (!workflowDocumentStore.value) {
throw new Error(i18n.baseText('nodeViewV2.showError.failedToCreateNode'));
}
workflowDocumentStore.value.addNode(nodeData);
Details

✨ AI Reasoning
​addNode now calls workflowDocumentStore.value?.addNode. If the computed document store is undefined, adding nodes silently does nothing whereas previous code always added to workflowsStore. This can break node creation flows in contexts lacking a resolved document store.

Reply @AikidoSec feedback: [FEEDBACK] to get better review comments in the future.
Reply @AikidoSec ignore: [REASON] to ignore this issue.
More info

normalize: false,
});
return NodeViewUtils.getNewNodePosition(
workflowDocumentStore.value?.allNodes ?? [],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Switched from workflowsStore.allNodes to workflowDocumentStore.value?.allNodes — when the document store is undefined callers get empty lists and may miscompute node positions. Consider explicit fallback or guard.

Details

✨ AI Reasoning
​Several node-position and layout helpers now pass workflowDocumentStore.value?.allNodes (or use it) instead of workflowsStore.allNodes. If the document store is undefined, callers receive an empty array, which can cause positioning logic to behave incorrectly without explicit error handling.

🔧 How do I fix it?
Remove debugging statements like console.log, debugger, dd(), or logic bypasses like || true. Keep legitimate logging for monitoring and error handling.

Reply @AikidoSec feedback: [FEEDBACK] to get better review comments in the future.
Reply @AikidoSec ignore: [REASON] to ignore this issue.
More info

// Filter the downstream nodes to find candidates that need to be shifted right.
.filter((name) => {
const node = workflowsStore.getNodeByName(name);
const node = workflowDocumentStore.value?.findNodeByName(name);
Copy link
Contributor

@aikido-pr-checks aikido-pr-checks bot Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replaced workflowsStore.getNodeByName with workflowDocumentStore.value?.findNodeByName — when document store is undefined this causes nodes to be ignored silently. Add explicit handling or fallback to workflowsStore.

Suggested change
const node = workflowDocumentStore.value?.findNodeByName(name);
const node = workflowDocumentStore.value?.findNodeByName(name) ?? workflowsStore.getNodeByName(name);
Details

✨ AI Reasoning
​Functions that collect nodes to move/shift now resolve nodes via workflowDocumentStore.value?.findNodeByName. If the document store is undefined some downstream logic will skip nodes quietly, changing layout adjustments compared to prior behavior.

Reply @AikidoSec feedback: [FEEDBACK] to get better review comments in the future.
Reply @AikidoSec ignore: [REASON] to ignore this issue.
More info

Comment on lines +234 to +248
function updateNodeProperties(updateInformation: INodeUpdatePropertiesInformation): void {
const nodeIndex = workflowsStore.workflow.nodes.findIndex(
(node) => node.name === updateInformation.name,
);

if (nodeIndex !== -1) {
for (const key of Object.keys(updateInformation.properties)) {
const typedKey = key as keyof INodeUpdatePropertiesInformation['properties'];
const property = updateInformation.properties[typedKey];

const changed = updateNodeAtIndex(nodeIndex, { [key]: property });

if (changed) {
void onStateDirty.trigger();
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updateNodeProperties wraps the main update loop in if (nodeIndex !== -1) { ... }. Use an early guard (return if nodeIndex === -1) to flatten the function and reduce nesting.

Show fix
Suggested change
function updateNodeProperties(updateInformation: INodeUpdatePropertiesInformation): void {
const nodeIndex = workflowsStore.workflow.nodes.findIndex(
(node) => node.name === updateInformation.name,
);
if (nodeIndex !== -1) {
for (const key of Object.keys(updateInformation.properties)) {
const typedKey = key as keyof INodeUpdatePropertiesInformation['properties'];
const property = updateInformation.properties[typedKey];
const changed = updateNodeAtIndex(nodeIndex, { [key]: property });
if (changed) {
void onStateDirty.trigger();
}
function updateNodeProperties(updateInformation: INodeUpdatePropertiesInformation): void {
if (nodeIndex === -1) {
return;
}
for (const key of Object.keys(updateInformation.properties)) {
const typedKey = key as keyof INodeUpdatePropertiesInformation['properties'];
const property = updateInformation.properties[typedKey];
const changed = updateNodeAtIndex(nodeIndex, { [key]: property });
if (changed) {
void onStateDirty.trigger();
Details

✨ AI Reasoning
​updateNodeProperties currently checks nodeIndex !== -1 and then places all update logic inside that block. The main behavior (updating properties) is nested inside the if, which increases indentation and hides the primary path. An early guard (if nodeIndex === -1 return) would flatten the function, improving readability and making the update loop the obvious fall-through behavior. This is a straightforward structural simplification without behavioral change.

Reply @AikidoSec feedback: [FEEDBACK] to get better review comments in the future.
Reply @AikidoSec ignore: [REASON] to ignore this issue.
More info

r00gm added 5 commits March 2, 2026 13:38
…gelog)

Provide WorkflowDocumentStoreKey in tests that render components or
composables using injectWorkflowDocumentStore(). Without it, node
lookups return undefined causing 44 test failures across 8 files.
…changelog)

Provide WorkflowDocumentStoreKey in FocusSidebar.test.ts so findNode
resolves correctly. Fix consistent-type-imports lint error in
useNodeMention.test.ts by using vi.importActual instead of
importOriginal with inline type annotation.
…angelog)

Provide WorkflowDocumentStoreKey in FocusPanel.test.ts for findNode.
Mock injectWorkflowDocumentStore in useNodeExecution.test.ts and
assert on document store's updateNodeProperties instead of
workflowState's.
…-changelog)

The NDV store resolves workflowDocumentStore from workflowsStore.workflowId.
Without a workflow ID, activeNode is always null, causing the assistant
floating button to be hidden and offset assertions to fail.
@r00gm r00gm force-pushed the experiment/facade-first branch from e985282 to ccb039f Compare March 2, 2026 14:31
@r00gm r00gm closed this Mar 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

n8n team Authored by the n8n team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant