Skip to content

fix(architecture): document backend-shared exception for SoftMotion axis helpers - #937

Merged
dcoutinho1328 merged 4 commits into
developmentfrom
fix/softmotion-architecture-layering
Jul 16, 2026
Merged

fix(architecture): document backend-shared exception for SoftMotion axis helpers#937
dcoutinho1328 merged 4 commits into
developmentfrom
fix/softmotion-architecture-layering

Conversation

@dcoutinho1328

@dcoutinho1328 dcoutinho1328 commented Jul 16, 2026

Copy link
Copy Markdown
Collaborator

Summary

development's architecture / Architecture Validation check has been failing since PR #928 ("feat: CODESYS-compatible SoftMotion") merged: it added imports of generate-softmotion.ts's pure axis-naming helpers (sanitizeAxisName, softMotionAxisNames, serializeSoftMotionAxisGlobalsToST, isValidIecIdentifier) from three frontend/services/ and frontend/store/ files, which the layer rules forbid importing from backend-shared directly. Because branch protection is strict (requires the branch up to date + all checks green), this currently blocks merging any PR into development.

Found while shipping an unrelated PLCopen import/export PR (openplc-editor #936) — traced the failure to this pre-existing, unrelated violation. Matching fix in openplc-web: https://github.com/Autonomy-Logic/openplc-web/pull/602

Fix

Documented the three call sites as KNOWN_EXCEPTIONS entries in src/__architecture__/validate.ts, the same escape hatch already used for the ladder/FBD slice's components dependency. Each entry explains exactly which pure helper is needed and why. No behavior change — generate-softmotion.ts itself is untouched.

Validation

  • npx tsx src/__architecture__/validate.ts — passes (0 violations)
  • eslint src/__architecture__/validate.ts — clean

Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added automated SoftMotion artifact generation for configured EtherCAT CiA 402 axes.
    • Added axis reference declarations, drive bindings, scaling, and PLC task wiring.
    • Improved axis-name validation, sanitization, discovery, and duplicate handling.
    • Centralized EtherCAT and SoftMotion utilities for consistent use across the application.
  • Bug Fixes

    • Surface comparisons now ignore differences in test files, preventing test-only changes from triggering reported mismatches.
  • Tests

    • Added coverage for SoftMotion axis naming, validation, discovery, and generated declarations.

…xis helpers

PR #928 introduced imports of generate-softmotion.ts's pure axis-naming
helpers (sanitizeAxisName, softMotionAxisNames, serializeSoftMotionAxisGlobalsToST,
isValidIecIdentifier) from three services/store files, violating the
services/store -> backend-shared layer rule and failing
`validate:arch` on development for every PR since.

Documented as KNOWN_EXCEPTIONS entries, mirroring the existing
ladder-slice/fbd-slice precedent: the pure discovery logic isn't
duplicated, only reached across a layer boundary those files can't move.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

Changes

SoftMotion axis pipeline

Layer / File(s) Summary
Centralize axis discovery and naming
src/middleware/shared/utils/ethercat/*, src/middleware/shared/utils/ethercat/__tests__/*
Shared utilities now normalize names, discover CiA 402 axes, expose bindings, serialize globals, and provide centralized exports and tests.
Generate SoftMotion PLC artifacts
src/backend/shared/ethercat/generate-softmotion.ts, src/backend/shared/ethercat/__tests__/generate-softmotion.test.ts, src/backend/shared/utils/PLC/__tests__/preprocess-pous.test.ts, src/backend/shared/ethercat/enrich-device-data.ts
Backend generation creates globals, a cyclic bridge POU, axis externals, and task wiring from shared axis plans.
Rewire shared EtherCAT consumers
src/frontend/components/_features/..., src/frontend/services/st-lsp/*, src/frontend/store/slices/shared/slice.ts
Frontend and LSP consumers import EtherCAT helpers from the shared middleware utilities.

Surface comparison filtering

Layer / File(s) Summary
Exclude tests from surface hashes
scripts/compare-surfaces.py
Recursive and mapped surface comparisons omit test directories and common test filename patterns.

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

Sequence Diagram(s)

sequenceDiagram
  participant PLCProject
  participant collectAxes
  participant generateSoftMotionArtifacts
  participant UserPOUs
  participant PLCTasks
  PLCProject->>collectAxes: discover configured CiA 402 axes
  collectAxes-->>generateSoftMotionArtifacts: return axis plans
  generateSoftMotionArtifacts->>PLCProject: add globals and bridge POU
  generateSoftMotionArtifacts->>UserPOUs: inject axis externals
  generateSoftMotionArtifacts->>PLCTasks: prepend bridge instance
Loading

Possibly related PRs

Suggested reviewers: thiagoralves, joaogsp

Poem

I hop through names, now IEC-clean,
And wire each axis in the machine.
A bridge runs first, the taskbeats flow,
While test files drift where tests may go.
Squeak, the SoftMotion paths now glow!

🚥 Pre-merge checks | ✅ 2 | ❌ 3

❌ Failed checks (3 warnings)

Check name Status Explanation Resolution
Title check ⚠️ Warning The title focuses on documenting a backend-shared exception, but the PR mainly moves SoftMotion helpers and excludes test files from surface comparison. Rename it to reflect the main change, such as moving SoftMotion axis helpers to shared utilities and excluding test files from surface comparison.
Description check ⚠️ Warning It includes a solid summary, fix, and validation, but omits the template's Pull request info, References, and DOD checklist sections. Add the missing template sections: pull request info, issue/Jira references, and the DOD checklist with status for the required items.
Docstring Coverage ⚠️ Warning Docstring coverage is 75.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
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/softmotion-architecture-layering

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.

@dcoutinho1328

Copy link
Copy Markdown
Collaborator Author

PR #937 Review

Summary

Adds three KNOWN_EXCEPTIONS entries to src/__architecture__/validate.ts to document a pre-existing layer-rule violation introduced by PR #928, unblocking validate:arch on development. Byte-identical fix to openplc-web #602.

Checklist Results

  • Correctness — each entry's justification matches the actual call site.
  • Scope — no behavior change; only the architecture allowlist.
  • Precedent — mirrors the existing components-exception entries.
  • Verified locallynpx tsx src/__architecture__/validate.ts passes 0 violations; eslint on the changed file is clean.

Verdict

  • Comment only (self-authored PR)
  • No blocking issues.

@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.

🧹 Nitpick comments (1)
src/__architecture__/validate.ts (1)

282-293: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Restrict these exceptions to the intended import path.

Because validate() approves exceptions by destination layer only (exceptions.includes(toLayer)), each entry currently permits any future backend-shared import from these files—not just the documented generate-softmotion helpers. Match the exception against the specific module path, or add a regression test that rejects unrelated backend-shared imports.

🤖 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/__architecture__/validate.ts` around lines 282 - 293, Update validate()
exception handling so the listed frontend files permit only their documented
generate-softmotion/backend-shared module imports, not every backend-shared
destination. Match exceptions against the specific import module path while
preserving the existing layer validation for other entries, and add or update
regression coverage to reject unrelated backend-shared imports.
🤖 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.

Nitpick comments:
In `@src/__architecture__/validate.ts`:
- Around line 282-293: Update validate() exception handling so the listed
frontend files permit only their documented generate-softmotion/backend-shared
module imports, not every backend-shared destination. Match exceptions against
the specific import module path while preserving the existing layer validation
for other entries, and add or update regression coverage to reject unrelated
backend-shared imports.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: c66b8b14-073c-44f9-a720-4b911155e33b

📥 Commits

Reviewing files that changed from the base of the PR and between 8fdb4f6 and b1a60bf.

📒 Files selected for processing (1)
  • src/__architecture__/validate.ts

dcoutinho1328 and others added 3 commits July 16, 2026 13:38
…ed/utils

PR #928 introduced imports of generate-softmotion.ts's pure axis-naming
helpers (sanitizeAxisName, softMotionAxisNames, serializeSoftMotionAxisGlobalsToST,
isValidIecIdentifier) and cia402.ts directly from three frontend/services/
and frontend/store/ files, plus two frontend/components/ call sites (missed
by the checker's @root/ alias blind spot) — all violating the
services/store/components -> backend-shared layer rule and failing
validate:arch on development for every PR since.

Rather than special-casing the violation with a KNOWN_EXCEPTIONS entry,
relocated the pure logic to middleware/shared/utils/ethercat/ — the
existing home for domain logic reachable from every layer including the
compile pipeline (same relationship target-capabilities/ and library/
already have to backend/shared/compile/pipeline.ts). backend/shared/ethercat/
generate-softmotion.ts now only owns the actual codegen
(generateSoftMotionArtifacts/injectAxisExternals), importing collectAxes
from the new utils module.

Also fixed a pre-existing prettier violation from the same PR
(preprocess-pous.test.ts) that was failing the format check on development.
(openplc-web's matching softmotion-e2e.test.ts fix does not apply here —
that test is web-adapter-specific and doesn't exist in this repo.)

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
compare-surfaces.py required byte-identical test files across both repos,
same as program files. This is stricter than necessary and already broken
in practice: openplc-web's backend/shared/ethercat/__tests__/softmotion-e2e.test.ts
(added by #928) is genuinely web-adapter-specific (imports
middleware/adapters/web/transpile-from-port + bundled-stlibs, neither of
which exists in this repo) and has never had an editor counterpart — this
has been failing the required Shared Surface Sync check on every PR since
#928 merged, independent of anything in this branch.

Test files are allowed to diverge (platform-specific mocks, adapter-only
e2e suites) without indicating a real program-file drift — the same
exclusion the architecture validator (__architecture__/validate.ts) already
applies to its own scan. Now only program files are compared.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@dcoutinho1328

Copy link
Copy Markdown
Collaborator Author

PR #937 Review (updated again)

Additional fix

Same as openplc-web #602: found that sync / Shared Surface Sync (required) couldn't pass even with the architecture fix, because it also demanded byte-identical test files, and backend/shared/ethercat/__tests__/softmotion-e2e.test.ts (web-only, adapter-specific) has never had an editor counterpart — a pre-existing gap from #928, not something this branch introduced.

Updated scripts/compare-surfaces.py (byte-identical fix to the web copy) to exclude __tests__/ and *.test.ts(x)/*.spec.ts(x) from the shared-surface comparison, matching __architecture__/validate.ts's own exclusion. Verified locally: match: true, 0 diffs against openplc-web's matching branch.

Verdict

  • Comment only (self-authored PR)
  • No blocking issues.

@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: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/backend/shared/ethercat/generate-softmotion.ts (1)

147-166: 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Schedule the bridge for every task that can execute axis-using POUs.

The generated instance is bound only to tasks[0]. In multi-task projects, POUs on other tasks read stale axis feedback and their commands are not bridged on that task’s scan. Generate a uniquely named bridge instance per relevant task, preserving execution before that task’s user instances, and cover the multi-task case.

🤖 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/backend/shared/ethercat/generate-softmotion.ts` around lines 147 - 166,
Update the bridge-instance generation in the project transformation to create a
uniquely named SM3 bridge instance for every task that can execute axis-using
POUs, rather than binding only to tasks[0]. Prepend each generated bridge
instance to that task’s user instances while preserving task-local execution
order, retain the fallback-task behavior, and add coverage for multi-task
projects.
🧹 Nitpick comments (1)
scripts/compare-surfaces.py (1)

76-77: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Use a tuple directly with endswith.

In Python, str.endswith() natively accepts a tuple of strings, which is more concise and slightly faster than using any() with a generator expression.

♻️ Proposed refactor
-    name = path.name
-    return any(name.endswith(suffix) for suffix in (".test.ts", ".test.tsx", ".spec.ts", ".spec.tsx"))
+    return path.name.endswith((".test.ts", ".test.tsx", ".spec.ts", ".spec.tsx"))
🤖 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 `@scripts/compare-surfaces.py` around lines 76 - 77, Update the suffix check in
the visible path-filtering function to pass the existing suffix tuple directly
to name.endswith, removing the any() generator while preserving the same test
and spec extensions.
🤖 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/middleware/shared/utils/ethercat/softmotion-axis-naming.ts`:
- Around line 45-59: Update sanitizeAxisName and isValidIecIdentifier to reject
IEC 61131-3 reserved words case-insensitively, including PROGRAM, VAR, and
END_VAR. Apply the reserved-word check before accepting or returning an axis
name, while preserving the existing character and leading-character validation
for non-reserved identifiers.

---

Outside diff comments:
In `@src/backend/shared/ethercat/generate-softmotion.ts`:
- Around line 147-166: Update the bridge-instance generation in the project
transformation to create a uniquely named SM3 bridge instance for every task
that can execute axis-using POUs, rather than binding only to tasks[0]. Prepend
each generated bridge instance to that task’s user instances while preserving
task-local execution order, retain the fallback-task behavior, and add coverage
for multi-task projects.

---

Nitpick comments:
In `@scripts/compare-surfaces.py`:
- Around line 76-77: Update the suffix check in the visible path-filtering
function to pass the existing suffix tuple directly to name.endswith, removing
the any() generator while preserving the same test and spec extensions.
🪄 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: 47abcc36-2813-4bea-9967-cb9aea4625bc

📥 Commits

Reviewing files that changed from the base of the PR and between b1a60bf and fb0da44.

📒 Files selected for processing (15)
  • scripts/compare-surfaces.py
  • src/backend/shared/ethercat/__tests__/generate-softmotion.test.ts
  • src/backend/shared/ethercat/enrich-device-data.ts
  • src/backend/shared/ethercat/generate-softmotion.ts
  • src/backend/shared/utils/PLC/__tests__/preprocess-pous.test.ts
  • src/frontend/components/_features/[workspace]/editor/device/ethercat/components/cia402-axis-tab.tsx
  • src/frontend/components/_features/[workspace]/editor/device/ethercat/index.tsx
  • src/frontend/services/st-lsp/goto-definition-redirect.ts
  • src/frontend/services/st-lsp/project-sync.ts
  • src/frontend/store/slices/shared/slice.ts
  • src/middleware/shared/utils/ethercat/__tests__/cia402.test.ts
  • src/middleware/shared/utils/ethercat/__tests__/softmotion-axis-naming.test.ts
  • src/middleware/shared/utils/ethercat/cia402.ts
  • src/middleware/shared/utils/ethercat/index.ts
  • src/middleware/shared/utils/ethercat/softmotion-axis-naming.ts
💤 Files with no reviewable changes (1)
  • src/backend/shared/ethercat/tests/generate-softmotion.test.ts

Comment on lines +45 to +59
/** Sanitize a device name into a valid IEC 61131-3 identifier. */
export function sanitizeAxisName(name: string): string {
let s = name.replace(/[^A-Za-z0-9_]/g, '_')
if (!/^[A-Za-z_]/.test(s)) s = `_${s}`
return s
}

/**
* True when `name` is already a valid IEC 61131-3 identifier — a letter or
* underscore followed by letters, digits, or underscores. A SoftMotion drive's
* name IS the axis variable name used in `MC_*(Axis := <name>)`, so it must
* satisfy this (no spaces, hyphens, or leading digits).
*/
export function isValidIecIdentifier(name: string): boolean {
return /^[A-Za-z_][A-Za-z0-9_]*$/.test(name)

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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Locate existing IEC identifier/keyword validation and relevant tests.
rg -n -C3 --type=ts '\b(isValidIecIdentifier|reserved.*word|IEC.*keyword|END_VAR|VAR_GLOBAL)\b' src

Repository: Autonomy-Logic/openplc-editor

Length of output: 50386


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- softmotion-axis-naming.ts ---'
sed -n '1,220p' src/middleware/shared/utils/ethercat/softmotion-axis-naming.ts

echo
echo '--- keyword-aware validators ---'
rg -n -C2 --type=ts 'keyword|reserved|identifier|isValidIecIdentifier' src/middleware src/backend src/frontend | head -n 200

Repository: Autonomy-Logic/openplc-editor

Length of output: 23269


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- exact file ---'
cat -n src/middleware/shared/utils/ethercat/softmotion-axis-naming.ts

echo
echo '--- potential keyword lists / reserved-word helpers ---'
rg -n --type=ts '\b(END_VAR|PROGRAM|FUNCTION_BLOCK|VAR_GLOBAL|VAR_INPUT|VAR_OUTPUT|VAR_IN_OUT|VAR_EXTERNAL|VAR_TEMP|CLASS|METHOD|ACTION|isReserved|reservedWords|keywords?)\b' src | head -n 200

Repository: Autonomy-Logic/openplc-editor

Length of output: 33529


🏁 Script executed:

#!/bin/bash
set -euo pipefail
sed -n '388,410p' src/frontend/store/slices/shared/slice.ts

Repository: Autonomy-Logic/openplc-editor

Length of output: 1463


Reject IEC reserved words too. sanitizeAxisName and isValidIecIdentifier still allow identifiers like PROGRAM, VAR, and END_VAR, which can be emitted into VAR_GLOBAL and fail ST compilation. Add a case-insensitive reserved-word check before accepting the axis name.

🤖 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/middleware/shared/utils/ethercat/softmotion-axis-naming.ts` around lines
45 - 59, Update sanitizeAxisName and isValidIecIdentifier to reject IEC 61131-3
reserved words case-insensitively, including PROGRAM, VAR, and END_VAR. Apply
the reserved-word check before accepting or returning an axis name, while
preserving the existing character and leading-character validation for
non-reserved identifiers.

@dcoutinho1328
dcoutinho1328 merged commit acf9fd5 into development Jul 16, 2026
14 checks passed
@dcoutinho1328
dcoutinho1328 deleted the fix/softmotion-architecture-layering branch July 16, 2026 19:19
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