Skip to content

perf(graphical-editor): scope per-edit scans + hot-path cleanups (DOPE-491) - #949

Merged
JoaoGSP merged 2 commits into
developmentfrom
fix/dope-491-hot-path-scans
Jul 21, 2026
Merged

perf(graphical-editor): scope per-edit scans + hot-path cleanups (DOPE-491)#949
JoaoGSP merged 2 commits into
developmentfrom
fix/dope-491-hot-path-scans

Conversation

@JoaoGSP

@JoaoGSP JoaoGSP commented Jul 21, 2026

Copy link
Copy Markdown
Member

Summary

Perf 5/5 of the DOPE-446 series (DOPE-491) — eliminates the remaining measured hot-path costs after Perf 1–4:

  • Variable sync scoped + batched (sync-nodes-with-variables.ts): the util now collects corrections and calls a batched updateNodes(updates[]) slice action once — one store commit per sweep instead of one produce() per mismatched node — plus an optional rungId scope. Ladder add/drag-stop are rung-scoped; remove stays flow-scoped (block removal can delete variables referenced by other rungs). Full-sweep paths retained (per the DOPE-477 caution): variable-table commit, type change, project-open relink, save-time relink.
  • FBD per-node render scans (getFBDPouVariablesRungNodeAndEdges): WeakMap-cached per-rung lookup maps + variable-name index; signature and semantics unchanged for all callers. Render pass drops from O(N²) to O(N) amortized.
  • Debug styling memos (ladder + FBD): extracted pure state computation with adjacency maps (no find/filter inside the DFS), deps narrowed to pouType + hasProgramInstance, and a new useContentStable hook keeps styledNodes/styledEdges identities across polls that don't change the rung — the rung skips re-render entirely.
  • FBD mouse-move storm: mousePosition/insideViewport state → refs read at paste time (~75 commits/s of idle overhead removed).
  • Debug poll: single setDebugValues commit per poll cycle (replaces the two separate setters).

The optional canEditorZoom/canEditorPannodrag/nowheel item is deferred until the React Flow upgrade, as the card specifies.

Validation

  • tsc, ESLint, Prettier clean; full jest suite green (5,351 tests)
  • Byte-stability gate (manual, both apps): passed — save-without-edit → zero diff; edit one element → only that POU diffs, with the untouched rung verified byte-identical; variable-table edits still full-sweep

Mirror of https://github.com/Autonomy-Logic/openplc-web/pull/611

🤖 Generated with Claude Code

https://claude.ai/code/session_017RGT8nUsyY26HXFSuTBFLz

Summary by CodeRabbit

  • New Features
    • Added batched node updates for Ladder and FBD to apply multiple variable changes in one pass.
  • Bug Fixes
    • Prevented graphical flows from being incorrectly marked as modified after synchronization.
    • Improved variable binding validation, including clearer wrong/broken handling and recovery.
  • Performance / UX
    • More efficient debugger green-edge/flow highlighting.
    • Reduced unnecessary canvas re-renders during pointer movement.
    • Improved copy/paste alignment with pointer tracking.
  • Tests
    • Expanded coverage for batch updates, node-variable synchronization, debug polling, and edge/flow-state behavior.

…rites (DOPE-491)

- syncNodesWithVariables(FBD): batched updateNodes callback (one store
  commit per sweep) + optional rungId scoping; ladder add/drag-stop are
  rung-scoped, remove stays flow-scoped (block removal can delete
  variables referenced by other rungs); variable-table edits, project
  open and save relink keep the full sweep
- getFBDPouVariablesRungNodeAndEdges: WeakMap-cached per-rung lookups
  (node-by-id, edges-by-source/target) + variable name index — per-node
  render cost drops from O(nodes+edges) to O(1) amortized
- ladder/FBD debug styling: pure state computation with adjacency maps,
  deps narrowed to pouType + hasProgramInstance, content-stable guard so
  polls that don't change a rung keep styledNodes/styledEdges identity
- FBD mouse tracking: state -> refs read at paste time; no re-renders on
  canvas mouse travel
- debug poll: single setDebugValues commit per poll cycle (replaces
  setDebugBoolValues + setDebugNonBoolValues)

Mirror of openplc-web fix/dope-491-hot-path-scans

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017RGT8nUsyY26HXFSuTBFLz
@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: a2819767-e5c4-421a-895e-407e7cb8b938

📥 Commits

Reviewing files that changed from the base of the PR and between 4eda412 and 01b84a3.

📒 Files selected for processing (2)
  • src/frontend/hooks/__tests__/use-content-stable.test.ts
  • src/frontend/store/__tests__/fbd-slice.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/frontend/store/tests/fbd-slice.test.ts

Walkthrough

The PR batches ladder and FBD node synchronization, consolidates debugger value updates, stabilizes debugger edge rendering, adds FBD lookup caches, and moves FBD clipboard pointer tracking from state to refs.

Changes

Graphical synchronization

Layer / File(s) Summary
Batch synchronization contracts and corrections
src/frontend/utils/graphical/sync-nodes-with-variables.ts, src/frontend/utils/graphical/__tests__/sync-nodes-with-variables.test.ts
Node corrections are accumulated into typed batches, scoped by editor and optional rung, with updated validation behavior and coverage.
Batch update store actions
src/frontend/store/slices/{ladder,fbd}/*, src/frontend/store/__tests__/*-slice.test.ts
Ladder and FBD stores apply valid node replacements in one action, skip invalid targets, and mark affected flows updated.
Synchronization caller migration
src/frontend/components/_molecules/graphical-editor/ladder/rung/body.tsx, src/frontend/components/_molecules/variables-table/selectable-cell.tsx, src/frontend/components/_organisms/variables-editor/index.tsx, src/frontend/services/save-actions.ts, src/frontend/store/slices/shared/slice.ts
Graphical editor, variable editor, save, and shared-store paths now pass batch update actions.
Ladder and FBD debugger state rendering
src/frontend/components/_molecules/graphical-editor/{fbd,ladder}/**, src/frontend/hooks/use-content-stable.ts
Debugger output and recursive edge states use extracted context helpers and stable map references for styled nodes and edges.
FBD lookup and clipboard interaction
src/frontend/components/_atoms/graphical-editor/fbd/utils/utils.ts, src/frontend/components/_molecules/graphical-editor/fbd/fbd-utils/useCopyPaste.ts
FBD lookups use cached indexes, and paste handling reads pointer position and viewport state from refs.
Consolidated debug-value updates
src/frontend/store/slices/workspace/*, src/frontend/hooks/useDebugPolling.ts, src/frontend/store/__tests__/workspace-slice.test.ts
Boolean and non-boolean debug values are merged through a single optional-map action and polling commit.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant DebugPolling
  participant WorkspaceActions
  participant FBDBody
  participant RungBody
  DebugPolling->>WorkspaceActions: setDebugValues(boolValues, nonBoolValues)
  WorkspaceActions->>FBDBody: update debug maps
  WorkspaceActions->>RungBody: update debug maps
  FBDBody->>FBDBody: compute stable edge states
  RungBody->>RungBody: compute stable edge and node states
Loading

Possibly related PRs

Suggested labels: enhancement

Poem

I’m a rabbit with nodes in a row,
Batch updates make diagrams glow.
Green edges flow, caches hop fast,
Refs keep pointer jitters past.
Debug maps merge with a cheerful thump! 🐇

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately reflects the PR’s main graphical-editor performance work.
Description check ✅ Passed The description covers the summary, references, and validation well, but it omits the template’s DOD checklist section.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/dope-491-hot-path-scans

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install failed. For unrecoverable errors, disable the tool in CodeRabbit configuration.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
src/frontend/hooks/use-content-stable.ts (1)

18-24: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Optional: add unit tests for the new hook/comparator. useContentStable/mapsEqual now underpin both ladder and FBD debug-edge stabilization, so a couple of tests (content-equal keeps identity, content-diff swaps reference, null↔value transitions) would lock in the identity-stability contract. Want me to draft them?

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/frontend/hooks/use-content-stable.ts` around lines 18 - 24, Add unit
tests for useContentStable and its mapsEqual comparator covering content-equal
values retaining the previous reference, content-different values replacing it,
and null-to-value and value-to-null transitions. Keep the tests focused on the
hook’s identity-stability contract.
src/frontend/components/_molecules/graphical-editor/fbd/index.tsx (1)

70-199: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoff

Optional: consider extracting the shared debug-edge recursion. determineEdgeState/isPassThroughNode (and much of getNodeOutputState) here closely mirror the ladder implementation in src/frontend/components/_molecules/graphical-editor/ladder/rung/body.tsx. The block-output key building and getNodeOutputState arity differ between the two, so a shared helper would need parameterization — worth it only if you expect these to keep evolving together. Otherwise leaving them separate is fine.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/frontend/components/_molecules/graphical-editor/fbd/index.tsx` around
lines 70 - 199, The debug-edge recursion and pass-through handling in
computeFBDEdgeStates duplicate the ladder implementation, so optionally extract
their shared logic into a reusable helper while parameterizing differing
getNodeOutputState signatures and block-output key construction. Keep
FBD-specific and ladder-specific behavior intact; if shared behavior is not
expected to evolve together, leave the implementations separate.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/frontend/store/__tests__/fbd-slice.test.ts`:
- Around line 270-301: Make the dirty-state checks in the updateNodes tests
meaningful by resetting the flow’s updated flag to false after setup and before
each updateNodes batch. In the successful batch test, assert updated becomes
true afterward; in the invalid-entry test, also assert it remains false while
the existing node data is unchanged. Update the tests around the updateNodes
cases without altering production behavior.

In `@src/frontend/store/slices/ladder/types.ts`:
- Around line 112-113: Update the updateNodes contract in
src/frontend/store/slices/ladder/types.ts lines 112-113 and
src/frontend/store/slices/fbd/types.ts lines 64-65 to return { ok: boolean;
message?: string }. In src/frontend/store/slices/fbd/slice.ts lines 152-167,
have updateNodes count successfully applied entries during the single store
commit and return an outcome indicating whether any update was applied, with an
optional message when none were applied.

---

Nitpick comments:
In `@src/frontend/components/_molecules/graphical-editor/fbd/index.tsx`:
- Around line 70-199: The debug-edge recursion and pass-through handling in
computeFBDEdgeStates duplicate the ladder implementation, so optionally extract
their shared logic into a reusable helper while parameterizing differing
getNodeOutputState signatures and block-output key construction. Keep
FBD-specific and ladder-specific behavior intact; if shared behavior is not
expected to evolve together, leave the implementations separate.

In `@src/frontend/hooks/use-content-stable.ts`:
- Around line 18-24: Add unit tests for useContentStable and its mapsEqual
comparator covering content-equal values retaining the previous reference,
content-different values replacing it, and null-to-value and value-to-null
transitions. Keep the tests focused on the hook’s identity-stability contract.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: a88993b3-be93-4bf0-a035-b940f3e9d0b1

📥 Commits

Reviewing files that changed from the base of the PR and between 417158d and 4eda412.

📒 Files selected for processing (21)
  • src/frontend/components/_atoms/graphical-editor/fbd/utils/utils.ts
  • src/frontend/components/_molecules/graphical-editor/fbd/fbd-utils/useCopyPaste.ts
  • src/frontend/components/_molecules/graphical-editor/fbd/index.tsx
  • src/frontend/components/_molecules/graphical-editor/ladder/rung/body.tsx
  • src/frontend/components/_molecules/variables-table/selectable-cell.tsx
  • src/frontend/components/_organisms/variables-editor/index.tsx
  • src/frontend/hooks/use-content-stable.ts
  • src/frontend/hooks/useDebugPolling.ts
  • src/frontend/services/save-actions.ts
  • src/frontend/store/__tests__/fbd-slice.test.ts
  • src/frontend/store/__tests__/ladder-slice.test.ts
  • src/frontend/store/__tests__/workspace-slice.test.ts
  • src/frontend/store/slices/fbd/slice.ts
  • src/frontend/store/slices/fbd/types.ts
  • src/frontend/store/slices/ladder/slice.ts
  • src/frontend/store/slices/ladder/types.ts
  • src/frontend/store/slices/shared/slice.ts
  • src/frontend/store/slices/workspace/slice.ts
  • src/frontend/store/slices/workspace/types.ts
  • src/frontend/utils/graphical/__tests__/sync-nodes-with-variables.test.ts
  • src/frontend/utils/graphical/sync-nodes-with-variables.ts

Comment thread src/frontend/store/__tests__/fbd-slice.test.ts
Comment thread src/frontend/store/slices/ladder/types.ts
…ontent-stable

Addresses CodeRabbit review: reset the FBD flow's updated flag before
each updateNodes batch so the assertions actually prove the action's
behavior, and add unit tests locking in the useContentStable/mapsEqual
identity-stability contract.

Mirror of openplc-web fix/dope-491-hot-path-scans

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017RGT8nUsyY26HXFSuTBFLz
@JoaoGSP

JoaoGSP commented Jul 21, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai On the two nitpicks: unit tests for useContentStable/mapsEqual were added in 01b84a3 (content-equal keeps identity, content-diff swaps reference, null↔value transitions, plus comparator edge cases). The shared debug-recursion extraction is intentionally skipped — as the review itself notes, the ladder and FBD implementations differ in getNodeOutputState arity, composite-key construction, pass-through handling, and forced-value support, so parameterizing a shared helper is a poor tradeoff; they were kept byte-faithful to the per-editor originals on purpose.

@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

git show --stat --oneline 01b84a3c3
git show --format=fuller --find-renames --find-copies -- 01b84a3c3 -- \
  'src/frontend/hooks/use-content-stable.ts' \
  'src/frontend/hooks/**' \
  'src/frontend/components/_molecules/graphical-editor/ladder/rung/body.tsx' \
  'src/frontend/components/_molecules/graphical-editor/fbd/index.tsx'

echo '--- Hook/test files ---'
fd -i 'use-content-stable' src/frontend

echo '--- Debug helper declarations/call contexts ---'
rg -n -C 5 'computeRungDebugStates|computeFBDEdgeStates|getNodeOutputState|forced' \
  src/frontend/components/_molecules/graphical-editor/ladder/rung/body.tsx \
  src/frontend/components/_molecules/graphical-editor/fbd/index.tsx

Length of output: 18273


@JoaoGSP Confirmed. Commit 01b84a3c3 adds focused coverage for the mapsEqual content contract and useContentStable reference-stability behavior, including the null transitions and repeated equivalent updates.

I also agree that the ladder and FBD recursion has material semantic differences—not merely superficial duplication—so a shared helper would introduce an unhelpful abstraction. I’m withdrawing that nitpick.

🐇💭


✏️ Learnings added
Learnt from: JoaoGSP
URL: https://github.com/Autonomy-Logic/openplc-editor/pull/949

Timestamp: 2026-07-21T20:46:35.827Z
Learning: The graphical-editor debug-state recursion intentionally remains separate in `src/frontend/components/_molecules/graphical-editor/ladder/rung/body.tsx` and `src/frontend/components/_molecules/graphical-editor/fbd/index.tsx`: ladder computes node-input states and passes input flow to its output resolver, while FBD has distinct pass-through and forced-value behavior. Do not recommend extracting a shared recursive helper unless their semantics converge.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

@JoaoGSP
JoaoGSP merged commit 634ae95 into development Jul 21, 2026
20 checks passed
@JoaoGSP
JoaoGSP deleted the fix/dope-491-hot-path-scans branch July 21, 2026 20:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant