fix(modbus): make IO Group size edits visible and reject invalid lengths (DOPE-439) - #1004
fix(modbus): make IO Group size edits visible and reject invalid lengths (DOPE-439)#1004JulioSergioFS wants to merge 1 commit into
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (5)
WalkthroughThe change adds shared Modbus I/O group length utilities, normalizes lengths in the project store, validates editor input, and displays complete address ranges and lengths in the remote-device table. ChangesModbus I/O group length handling
Estimated code review effort: 3 (Moderate) | ~25 minutes Sequence Diagram(s)sequenceDiagram
participant RemoteDeviceEditor
participant validateIOGroupLength
participant ProjectStore
RemoteDeviceEditor->>validateIOGroupLength: Validate function code and length input
validateIOGroupLength-->>RemoteDeviceEditor: Return validation result
RemoteDeviceEditor->>ProjectStore: Submit validated group length
ProjectStore-->>RemoteDeviceEditor: Persist normalized group and points
Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
Review — approved from my side; nothing to changeReviewed together with the web sync PR Autonomy-Logic/openplc-web#663 — all 5 files byte-identical, so this applies to both. I read this against the ticket text (DOPE-439: "When editing an already-existing IO Group, it is not possible to change its size", High) rather than just against the diff. Verified: 5/5 mirrored files byte-identical ( The two claims in the description, checked
The part I found most interestingTwo of the three remaining reproduce paths weren't store bugs at all — they were observability bugs. The table had no Length column, and the Address cell rendered only the first point's Two decisions I'd have flagged if they weren't already handled
Normalising the persisted One coordination noteThis PR and the DOPE-440 pair both touch Review assisted by Claude Code. |
Pull request info
Description of the changes proposed
The ticket's literal root cause —
updateIOGrouponlyObject.assigning themetadata and never regenerating
ioPoints— was already fixed inb81f7ee5a(v4.2.8). This PR closes the three remaining paths by which the ticket still
reproduced, none of which were in the store:
the Address cell showed only the first point's
iecLocation— which doesnot move when the group grows. Testing the ticket on a collapsed row read as
"nothing changed". Adds a Length column between Offset and Function Code,
and turns Address into a span (
%IW0 – %IW3), which also makes theproject-wide address recompaction legible.
readOnlyplusopacity-50and no help text. Correct Modbus semantics (Write Single Coil /Write Single Register address exactly one element), but the UI never said so.
Adds an inline hint that explains the rule and points at FC 15 / FC 16.
parseInt(length, 10) || 1does notcatch
-5(the|| 1only covers0andNaN), so the group ended up withzero I/O points and
len: -5reached the generated Modbus master config.Adds input validation bounded by each function code's PDU limit (FC 3/4 = 125,
FC 16 = 123, FC 1/2 = 2000, FC 15 = 1968), and a floor-only clamp as a
store invariant on both
addIOGroupandupdateIOGroup.New pure util
src/frontend/utils/modbus/io-group.tsholds the rules(
clampIOGroupLength,validateIOGroupLength,formatIOGroupAddressRange,isSingleElementFunctionCode), replacing thefunctionCode === '5' || === '6'literal that was duplicated in the component.
Decisions worth flagging in review
maximum in the store would silently truncate a pre-existing FC 3 group of
length 200 the moment a user edited only its name — data loss introduced by
a bug fix. The maximum is an input rule; a test pins this.
length, not just the point count,because that field is what
generate-modbus-master-config.tsships to theruntime as
len. Two things fall out for free: switching a group to FC 5/6forces
length: 1even if a caller forgets, and a bad length loaded from anold project file self-heals on first edit.
ModbusIOGroupSchema.lengthis deliberately left asz.number(). An.int().min(1)refinement failsPLCRemoteDeviceSchema.safeParse, and theload path responds by dropping the whole remote device with no
diagnostic — trading a bad number for a vanished device. Normalization
belongs in the store. A safe load-time coercion
(
z.number().transform(...)) is filed separately.Tooltipatom: the field lives inside a RadixModal, so a tooltip means a portal nested in a portal withz-[999]stacking to reason about — and a permanently locked control deserves an
always-visible reason, not a hover-discoverable one.
Out of scope (filed separately)
variable bound to them. Orphaning is deliberate policy — see the
renameAliascomment inproject/slice.ts— so what's missing is a warning,not a data-model change.
the group; strictly worse than the shrink case, and should reuse the same
modal.
ModbusIOPoint.idis${groupName}_${i}, so two same-named groups producecolliding ids (React keys, and the
sourceRefused by the alias-uniquenessgate).
visible what DOPE-440 fixes.
Verification
Scoped runs only, Node v22:
tsc --noEmit,npm run validate:arch, eslint and prettier clean in both repos.scripts/compare-surfaces.py:total_diffs: 0across 1029 files.Manual, in the running app: created an FC 3 group of length 4 (
%IW0 – %IW3) plusa second of length 2 (
%IW4 – %IW5); grew the first to 6 → reads%IW0 – %IW5with the sibling sliding to
%IW6 – %IW7, aliasest0–t3intact and the twonew slots blank;
-5,0,3.5and126each block Save with a reason;switching to FC 5 snaps Length to 1 with the hint and yields
%QX0.0; thegenerated master config has
len >= 1and never falls back to%MW0.Summary by CodeRabbit
New Features
Bug Fixes