Release 4.2.9#960
Merged
Merged
Conversation
Port the byte-identical shared transpiler changes from openplc-web's ws1-corpus-divergences work: graph-based ComputeConnectionTypes pin-type inference (new walker/connection-types.ts), blockOverloads / isOfType helpers, CR-aware pySplitLines, and the ld/fbd typeContext plumbing. Supersedes the TO_<TYPE> name-suffix conversion stopgap in pou-graphical with the connection-driven mechanism (190/190 oracle parity). Keeps backend/shared byte-identical with web. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Mirror web's comparison feature on the editor side. transpileToSt (the platform port) and compileForDebugger now run BOTH the legacy xml2st subprocess and the new in-process TS transpiler on every compile, log a comparison verdict, and use the old engine's output as authoritative unless OPENPLC_USE_NEW_TRANSPILER flips it. The non-authoritative engine's failure is non-fatal. Adds an editor-local compare helper mirroring web's compare-transpiler-output. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ants Byte-identical mirror of openplc-web (PR #528) — keeps the shared backend/shared/ surface in sync (ci-sync). See the web commit for rationale: the transpiler now resolves block signatures from each placed block's node.data.variant instead of the bundled std_block_catalog.json (deleted), and adds __XWORD to the type lattice and base types. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…le engine The in-process JSON->ST transpiler is byte-identical to xml2st across the parity corpus, so drop the dual-run + comparison and the OPENPLC_USE_NEW_TRANSPILER flag, and stop bundling/downloading the xml2st binary. Tasks now emit in priority order to match the old XML generator's output (D1). XmlGenerator is kept for the "Export Project as XML" feature only. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The discriminated `pou.body` union already narrows `value` on `language`, so `as RFBody` / `as RFFbdBody` trip `no-unnecessary-type-assertion` and fail the lint check (pre-existing on this branch). Remove the casts and the now-unused type imports. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Wrap an over-length return statement so the Format Check passes (it only started running now that the lint gate is green again). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…-corpus-divergences
…ing (#830) (#891) * fix(transpiler): emit parallel set/reset coils under one shared IF Parallel coils branch off the same energization path, so the rung condition must be evaluated once and applied to every branch. A separate IF per coil re-evaluates the condition between assignments, which is wrong when the condition reads a coil an earlier branch just set (e.g. NOT(coils1) ... coils1 := TRUE flips coils2's condition). xml2st has the same bug; fixed here on the in-process engine. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(transpiler): group non-adjacent parallel coils; dedup identical paths Extends the parallel-coil grouping to the issue #836 topologies: SET/RESET coils sharing one source now collapse into a single IF even when a merge-fed coil sorts between them, so a contact feeding multiple coils is evaluated once instead of re-read between coil assignments. Also dedups byte-identical parallel paths (X OR X -> X) so the merged condition reads cleanly. Sequential coils (distinct sources) keep separate IFs by design. Deliberate divergence from xml2st (same class of fix as D1). Zero output change across the 193-project LD corpus; covered by a 3-topology regression test matching the maintainer's expected output. Closes #836 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(transpiler): emit block output writes right after the block call Chained block calls in a rung emitted all calls first, then all output assignments — so a downstream block read a variable before the upstream block's write was emitted (Output stayed 0). Couple each output-write sink (coil / output / inOut variable fed solely by a block) to its block: emit it immediately after the actual call, eager or lazy, via emitBlockConsumers (reusing the coil-grouping sweep). Robust to blocks pulled through a later block's EN expression, which the sink-slot approach missed. Deliberate divergence from xml2st (same class as #836). 10/193 LD corpus fixtures corrected (incl. real 4- and 5-block chains), 183 unchanged; #836 grouping + 301 integration tests still pass. Closes #830 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Address CodeRabbit review on #885: - compileForDebugger ran mkdir/writeFile for program.st outside any try, so a disk/permission failure rejected without posting the stop message or closing _mainProcessPort (hung debugger). Wrap them in try/catch matching the sibling steps. - Drop async from transpileToSt/computeMd5 (no await; require-await warnings), returning Promise.resolve — matches the sibling library-build port. Editor-only (backend/editor); no shared-surface change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…-corpus-divergences
…web) Byte-identical mirror of the shared SoftMotion surface from openplc-web (PR to be released together): - cia402.ts: recognize CiA 402 drives + resolve objects to IEC addresses - generate-softmotion.ts: compile-time AXIS_REF_SM3 globals + PDO scalars + per-scan SM_Drive_GenericDS402 bridge + VAR_EXTERNAL injection - enrichDeviceData tags recognized drives; open-plc/esi-types carry the Cia402AxisConfig; preprocessPous runs the codegen in every compile path - shared tests (cia402, generate-softmotion, preprocess-pous) pass under the editor's jest - binary-versions: strucpp v0.5.13 -> v0.5.14 (ships the SM3 library + FB inout copy-back + composite shared globals) The editor loads plcopen-softmotion.stlib via its bundled-libs directory scan (no bundled-stlibs.ts change needed). Requires strucpp release v0.5.14 (STruCpp PR #194). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…divider The Explorer and Source Control contexts each mounted their own ResizablePanel (different ids), so every context switch made react-resizable-panels rebuild the layout from defaultSize and re-normalize it (the group's defaultSizes sum to less than 100), inflating the captured getSize() value on every switch until the 32% fixed point. The left slot is now a single always-mounted ResizablePanel owned by the workspace screen; only its children swap, so the width is preserved natively and the size-capture plumbing is gone. The activity-bar divider under the context buttons was rendered unconditionally, leaving a stray separator on desktop where the buttons are hidden (no version control capability). It now renders only when at least one context button is present. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015fpthmAmQoNX8WMYyb68Qi
…-issues fix: keep sidebar size on context switch and hide stray activity-bar divider
Unmodified LD/FBD POUs showed as Modified in Source Control, drag-away-and-restore produced permanent phantom diffs, and every project open silently corrupted linked variable pins. - Persist the raw ladder/FBD flow (Zod-validated, not Zod-stripped) so unedited POUs round-trip byte-identical against HEAD - Make focus/blur drag-lock updates transient so clicking a node never dirties the flow - Canonicalize rung serialization: strip hasDivergence render state, normalize draggable, derive reactFlowViewport from content bounds - Reuse variable-pin node identities across layout rebuilds so drag-away-and-restore keeps ids, numericIds, and edge order stable - Validate variable pins against their block pin type — fixes a pre-existing bug that flagged every linked pin wrongVariable and rewrote its payload as broken-* on each project open and drag-stop (stale flags now self-heal) - Derive changed-rung indexes from a semantic node/edge diff instead of byte equality (openplc-web only) - Fix stale diff-view HEAD cache: prune entries per-path on save, refetch per-file instead of per-map, and resolve commit-with-diff-open to the new HEAD Note: projects saved with the previous serialization show a one-time Modified diff per graphical POU on first save; committing it once migrates the file. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WYE8gdREZQNYQeLfFuuZ9T
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WYE8gdREZQNYQeLfFuuZ9T
- Merge instead of replace headContent on diff fetch failure so a transient error does not evict other files' cached HEAD entries - Fetch node/rung fresh inside block.tsx focus/blur handlers (matches contact/coil) instead of closing over render-scope values - Rename loop-local previousNodes to previousLinkedNodes in diagram/index.ts to stop shadowing the identity-source parameter Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WYE8gdREZQNYQeLfFuuZ9T
…ource-control-drift fix: eliminate phantom source-control diffs in graphical editors
…riables
A variable with an interface class (input/output/inOut/external/temp)
and a physical location ("AT") is invalid IEC 61131-3. The editor let
users create one anyway — the location cell's selected branch skipped
the read-only guard — saved it, and on reopen the ST parser rejected
the declaration, silently discarding every variable of the POU
(GitHub issue #904).
- variables-table: honor the read-only rule in the location cell's
selected branch so a location can't be attached to an
interface-class variable
- store validation: reject a location update on an interface-class
variable, clear the location when the class changes to one, and
strip it on create, so non-table callers keep the same invariant
- project load: push a warning (surfaced in the console panel) when a
POU fails to parse instead of silently loading it with no variables
- parser: include the offending line and a repair hint in the
located-declaration error so the code-view repair path is actionable
Fixes #904 (DOPE-445)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FsPXB6bqvFU4RLBQ6snwDp
When a project with unsaved changes was open and the user triggered File -> Open, the save-changes modal's 'open-project' case discarded the picker result, so the selected project never loaded. Dispatch handleOpenProjectResponse with the result, mirroring the accelerator handler and start-screen flows (DOPE-447). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DQFVaMMwCZ77YYJKHSaWRM
The code-mode pass in handleOpenProjectResponse ran after the auto-open block had already added and activated a default table-mode model for the same POU (it prefers "main" — exactly the POU most likely to carry the unparseable variables). addModel no-ops on duplicates and setEditor early-returns when the name matches the active editor, so the preserved raw text never reached the model and the code view showed an empty VAR/END_VAR skeleton instead of the declarations to repair. Route the raw text through editorActions.updateModelVariablesForName, which updates whichever object holds the POU — the active editor or the stored model. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FsPXB6bqvFU4RLBQ6snwDp
fix: File -> Open did nothing when open project had unsaved changes (DOPE-447)
The fallback POU for unparseable variables carries the raw declarations in a top-level variablesText field, which the save serializer treats as authoritative over the structured variables. The commit-to-table guard looked for the field inside pou.interface (the old backend location), so it never cleared it — after repairing the declarations in code view and switching to table view, any table-mode save wrote the stale broken text back to disk, resurrecting the corruption on reopen. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FsPXB6bqvFU4RLBQ6snwDp
fix: located Output-class variable in main PROGRAM wipes all POU variables on reopen (DOPE-445)
…or of openplc-web) Byte-identical mirror of the openplc-web SoftMotion axis UI: - SoftMotion icon (teal rotary-motion tile) marking a recognized CiA 402 SoftMotion drive in the project tree. - Cia402AxisTab: CODESYS-style axis config — enable toggle, increments↔units scaling, the CiA 402 object→IEC-address mapping table, and a real-time feedback panel. The device name is the axis name used in MC_*(Axis := …). - project tree + explorer wire the `softMotionDrive` leaf lang when cia402.enabled; the EtherCAT device editor gains a "SoftMotion Axis" tab. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…channel mappings (mirror of openplc-web) Byte-identical mirror of the openplc-web SoftMotion UX fixes: 1. Rename works for SoftMotion drives (isEthercatDevice now covers the softMotionDrive leaf lang). 2. Drive names are enforced to valid IEC identifiers — sanitized at add-time, validated on rename (isValidIecIdentifier), since the name is the axis variable in generated code. 3. Channel Mappings tab hidden for SoftMotion drives (PDO mappings are internal); editor defaults to the SoftMotion Axis tab. 4. SoftMotion Axis tab simplified (dropped the enable toggle and the application-code reference blurb). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… openplc-web) Byte-identical mirror. The LSP now synthesizes a `VAR_GLOBAL <axis> : AXIS_REF_SM3` document and injects the same axis VAR_EXTERNAL the compiler generates (via the shared injectAxisExternals, now covering function blocks too), so editor code that names a SoftMotion axis — `MC_Power(Axis := X_Axis)` — resolves instead of flagging the axis as undeclared. Also fixes the compile gap where axis references inside function blocks didn't get their VAR_EXTERNAL. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…RNAL injection (mirror of openplc-web) Byte-identical mirror. The LSP now surfaces each axis as a bare top-level VAR_GLOBAL block (ambient global — resolves with no VAR_EXTERNAL), POUs are serialised verbatim (no injected declarations shifting line numbers, so go-to-definition stays correct), and go-to-definition on an axis redirects to the owning drive's device editor. VAR_EXTERNAL injection remains compile-only. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…olve (mirror of openplc-web) Byte-identical mirror. The LSP now sends the project's configuration-level globals as a CONFIGURATION VAR_GLOBAL doc (the level the compiler emits at, and the only form strucpp matches a VAR_EXTERNAL against), so a POU's VAR_EXTERNAL no longer falsely errors. Extracted a shared reconcileSyntheticDoc engine used by all three synthesized docs (data types, resource globals, softmotion axes), and go-to-definition on a user global opens the Resource editor. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace all 25 bare useOpenPLCStore() whole-store subscriptions across the graphical editor (ladder/FBD node atoms, RungBody, RungHeader, Rung, LadderEditor, FBDBody, element-editor modals, autocompletes, useCopyPaste, usePouSnapshot) with: - narrowest-path selectors for render reads (pou-scoped find() selectors for flows, specific modal keys, primitives where possible) - useOpenPLCStore.getState() at event time for handler-only reads, which removes re-render fan-out and stale-closure risk at once (the onDrop dep-array workarounds for freshly installed libraries are now moot) - stable action-group selects Store writes, flow persistence, and the compile pipeline input are untouched; save-without-edit produces a zero source-control diff. Live validation at 73 mounted rung instances (vs 2026-07-10 baseline): typing is now ~2 commits/keystroke local to the focused node (baseline: 26-42 whole-editor commits/sec with ONE rung), idle is a flat 0 commits/5s (baseline: sporadic 62-345 spikes), modal open/close no longer re-renders the canvases (worst task 61ms vs a 470ms full-canvas pass). Root cause C1 of DOPE-446. Fixes typing/idle/modal re-render storms; rung-duplication scaling (C5) is DOPE-489, memo barriers are DOPE-488. Mirror of the openplc-web commit (src/frontend byte-identical). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S3FiQAY3P1gJDnGhuHbAab
…dit-hot-path-clones
connection-types powerRail fold + dead deferred-resolve removal + EN dedup guard; drop unused isOfType; remove write-only SyntheticVar origin fields; block-library isRecord export + function-block-instance mapping; pou-graphical shared isRecord + first-wins dedup note. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KNxKRqXuzVYSvfmUsvg2Ly
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017RGT8nUsyY26HXFSuTBFLz
…ergences fix(transpiler): byte-parity + library-agnostic block resolution (mirror of web #528)
…dit-hot-path-clones
…h-clones perf(graphical-editor): remove edit hot-path deep clones + debounce flow write-back (DOPE-490)
…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
…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
perf(graphical-editor): scope per-edit scans + hot-path cleanups (DOPE-491)
…bers in LSP scope (#950) * fix(graphical): bind dotted box refs as constants + resolve globals in LSP scope query Byte-identical companion to the openplc-editor PR (shared frontend surface). 1. Dotted / non-identifier box entries (member access like some_global_complex.structureVar, typed literals like T#500ms, reserved words) are bound to the block verbatim as a constant/reference instead of being routed to createVariable, whose isLegalIdentifier check rejects them with an Illegal Variable Name toast and discards the entry. New variable NAMES still reject those characters. Applied to all four box-commit sites (fbd/ladder autocomplete + block). 2. serializePouScopeForQuery re-emits the POU's VAR_EXTERNAL variables as plain VAR in the throwaway scope-query doc, so strucpp resolves globals and their struct/array members without a matching VAR_GLOBAL — restoring LSP autocomplete + validation for global variables in LD and FBD. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(graphical): accept LSP Field kind so struct members autocomplete/validate Struct-typed variables (local or via a global VAR_EXTERNAL) never offered their members in the LD/FBD variable boxes, e.g. `test_complex.structureBool` for a BOOL box. Root cause: strucpp returns STRUCT members with CompletionItemKind.Field (5), while graphical-scope only accepted Variable (6) — the kind FUNCTION_BLOCK instance members come back as. So struct fields were dropped by the kind filter before the type filter ran, in both the completion and validation paths. Accept Field (5) alongside Variable (6) via isValueCompletionKind at all three call sites (completion, member-drill, type resolution). Verified live in the browser: `<struct>.` now lists all fields in an ANY box and narrows to the type-compatible field (structureBool) in a BOOL box. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore: remove xml2st references + fix autocomplete box sizing xml2st cleanup (the tool was retired; the in-process ST transpiler replaced it): - Drop stale build/CI/config references (ci-build step name, ci-unit-tests comment, .gitattributes entry, devcontainer binary check). - Rewrite every source/test comment that named xml2st to reference the ST transpiler / STruC++ as appropriate (no behavior change). - Rename the exported PLCopen <addData> namespace openplc.org/xml2st/library-blocks -> openplc.org/library-blocks (nothing reads it back on import; xml2st was its only consumer). plc.xml generation itself is unchanged — it is the PLCopen-conversion / Export-as-XML flow. Autocomplete box sizing: - The graphical (LD/FBD) variable-box autocomplete had a fixed w-36 width, so long suggestions (e.g. struct member refs like some_global_complex.structureVar) were cropped. It now grows to fit content up to max-w-[16rem] and wraps long names (break-all) to multiple lines so the full text is always visible. Byte-identical across the shared frontend surface (compare-surfaces: match, 0 diffs). tsc/lint/prettier clean; changed test suites pass; autocomplete sizing verified live in the browser. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(graphical): reserve scrollbar gutter in autocomplete so widest name never wraps When the suggestion list overflows and the vertical scrollbar appears, it stole horizontal space the content-based auto-size didn't reserve, wrapping the last character of the widest name onto a new line. Add `scrollbar-gutter: stable` to the scroll container so the reserved gutter is included in the auto-size. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(graphical): stop autocomplete rows collapsing/overlapping in Safari Safari-specific rendering of the LD/FBD variable-box dropdown: - `w-max` pins the popover to the widest item's full single-line width (clamped by min/max). Safari's shrink-to-fit otherwise collapsed the width toward min-content when items use `break-all`, wrapping names unnecessarily. - `shrink-0` on the item rows, the "Add variable"/new-block rows, and the variables container. Safari vertically shrinks flex-column children, so a wrapped name's second line overflowed its box and rendered on top of the "Add variable" button below. Keeping full height pushes siblings down instead. Chrome was unaffected (sizes to max-content and doesn't shrink these rows); verified no Chrome regression. Safari behavior to be confirmed by the reporter. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(graphical): use break-words (not break-all) so Safari sizes the dropdown Root cause of the Safari-only cropping: `word-break: break-all` collapses WebKit's max-content computation, so the auto-sized dropdown came out too narrow and wrapped names (even short ones), and the wrapped line overlapped "Add variable". `overflow-wrap: break-word` (Tailwind `break-words`) does NOT collapse max-content — it still breaks over-long names — so the popover sizes to the full name (clamped to [9rem, 16rem]) in both Chrome and Safari. Dropped the now-unneeded `w-max` (its interaction with min-width under WebKit was part of the problem); Radix's `min-width: max-content` wrapper drives the sizing. `shrink-0` rows retained so a genuinely long name that wraps still pushes "Add variable" down instead of overlapping it. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…952) Deleting a global variable that any POU declares as VAR_EXTERNAL was silently refused by the store guard, but the global-variables editor swallowed the failure response — the delete button just did nothing, with no warning or message (odd UX). Now the editor detects the referencing POUs up front and shows a confirmation modal naming them. On confirm, deleteVariable is called with force: true, which removes the global AND the matching VAR_EXTERNAL declaration from every referencing POU. Graphical nodes that still reference the deleted global go unresolved for the user to fix (we only remove the external declarations, not diagram nodes). - project slice: deleteVariable gains a force flag; without it, global deletion blocked by external refs returns the referencing POU names in data.referencingPous; with it, cascades the external-decl removal. - global-variables editor: surfaces the block as a confirmation modal instead of a silent no-op. - tests: assert referencingPous is reported (unforced) and that force cascades the removal across every referencing POU while retaining unrelated locals. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a "User Management" screen under the project tree's Device folder, shown only while connected to a runtime. Lets an admin list, create, edit, and delete runtime accounts; a normal user can edit only its own account. - Shared frontend (editor + web, byte-identical): new `plc-user-management` editor variant, gated Device-tree leaf + Users icon, and a single-table screen with per-row edit/delete icons (OPC-UA style). - Reusable RuntimeUserModal (create | edit | bootstrap). The first-user dialog is refactored to use it. Edit mode shows a masked password placeholder and only sends a password when the field is actually edited (dirty-tracked) — an untouched form never resets a password. Editing your own password requires the current password. - RuntimePort gains listUsers / whoAmI / updateUser / deleteUser and a role on createUser; editor adapter + IPC channels implement them. create-user is sent unauthenticated for first-user bootstrap and authenticated (admin) afterwards. - UI role-gates actions (admin sees create/delete + role selector); the runtime remains the real authorization boundary. - Tests: adapter methods (100%), tabs factory, and the modal's dirty-password / current-password rules. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…c copy (DOPE-505) The LSP transport built its connection from 'vscode-jsonrpc/browser' (direct dep, 8.2.1) while message types (InitializeRequest, DidOpen...) came from 'vscode-languageserver-protocol' with its own nested 8.2.0. ParameterStructures is compared by identity, so under a real pnpm node_modules layout the initialize handshake threw "Unknown parameter structure byName" and crashed project load. Import the transport through 'vscode-languageserver-protocol/browser' so the connection and the message types share one vscode-jsonrpc copy in every layout (npm, pnpm, bundled). Also clears the 14 dual-MessageConnection tsc errors. Mirror of the openplc-web commit (shared src/frontend). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017Zbz8hMi93XvA1RenC9bXN
…pc-single-copy fix(lsp): resolve connection and message types from one vscode-jsonrpc copy (DOPE-505)
Same-major bump (11 minor releases) bringing measured perf wins on ladder/FBD hot paths: skip-measurement rendering for predefined dimensions (12.8.5), faster node/edge insertion (12.9), FlowRenderer re-render eliminations (12.10), XYDrag only for draggable nodes + imperative viewport (12.11.2). Prerequisite for the ladder single-instance-per-POU refactor. @xyflow/system 0.0.35 -> 0.0.79 rides along. Two type-level breaks fixed in shared frontend: - HandleProps.id widened to string|null in system 0.0.79 - pinned id?: string in CustomHandleProps (ladder + fbd handle.tsx) and typed buildHandle returns; our handles always carry explicit ids. - OnNodeDrag event is now native MouseEvent|TouchEvent - drag handlers retyped in ladder rung body.tsx and fbd index.tsx; handleNodeDrag guards non-mouse events. One behavior fix: removed nodeExtent from the ladder ReactFlowPanel. Node positions belong to the ladder layout engine; xyflow >=12.9 no longer recomputes drag-clamped positionAbsolute when the extent later grows, so a mid-drag clamp against the stale extent froze nodes at wrong positions (rendered corruption + "Maximum update depth" crash on drag-out-of-parallel). 12.0.1 masked this by unconditionally re-clamping all nodes on every extent change. translateExtent (viewport lock) stays. Mirror of the openplc-web commit (shared src/frontend byte-identical). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017Zbz8hMi93XvA1RenC9bXN
Mirror of the openplc-web commit. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017Zbz8hMi93XvA1RenC9bXN
…upgrade chore(deps): upgrade @xyflow/react 12.0.1 → 12.11.2 (DOPE-492)
Addresses review feedback on the User Management feature: 1. First-user setup now stays connected — the bootstrap dialog no longer tears down the just-established session when it closes on success. 4. Authenticated create-user accepts the runtime's 201 (was treated as an error, so the dialog showed the raw response and stayed open). 5. The self-edit "Current password" field now renders last (after Confirm password) and only once the password is actually being changed, so it doesn't shove the field the user just clicked. 7. Gate the User Management tree leaf on runtime version ≥ v4.1.9 (isUserManagementCapableRuntime); the connected runtime version is now stored in runtimeConnection and set on connect. Also: edit-icon tooltip now reads "Edit user" (icon no longer swallows the button title), "New User" label centered, and changing your own password signs you out to force a fresh login with the new credentials. Tests: version-gate helper, device-slice runtimeVersion, existing modal and adapter suites updated. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The bootstrap dialog was still disconnecting on the success close. Reading runtimeConnection.connectionStatus in the close handler was stale: handleSubmit sets it to 'connected', but RuntimeUserModal then calls onOpenChange(false) synchronously before a re-render, so the handler saw the old 'connecting' value and reverted to 'disconnected'. Track success with a ref instead (set in handleSubmit, reset when the dialog opens) so the close handler reliably distinguishes a successful connect from a genuine cancel. Restores the pre-refactor behavior where the success path closed via a controlled prop change that never triggered the cancel logic. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Bug 3: the PlusIcon strokes `inherit` with no stroke color set, so it
rendered invisibly — its empty box pushed the "New User" label off-center.
Give it `stroke-current` (white on the brand button) so it shows as a
proper "+ New User".
Bug 6 follow-up: changing your own password signs you out, but the User
Management tab stayed open and any action then crashed with
"users.map is not a function". Two guards:
- When not connected, render a neutral placeholder instead of the table
and actions (which would hit the runtime unauthenticated).
- listUsers coerces a non-array payload to [] (the runtime returns an
existence-only {"msg":"Users found"} object when the token is invalid),
in the editor adapter and the screen, so the table can never receive a
non-array.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The tab atom maps fileDerivation.type to an icon and defaults to the IL icon when the type isn't handled — so the User Management tab showed the IL glyph. Add a 'user-management' case using the same UsersIcon as the project-tree leaf. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
feat: runtime User Management screen with RBAC
Bump APP_VERSION (the single source of truth the About dialog renders, shared byte-for-byte with openplc-web) AND package.json.version together so the desktop binary and the About dialog agree. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
chore(release): bump version to 4.2.9
Contributor
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. 🗂️ Base branches to auto review (1)
Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Promote
development→mainfor the 4.2.9 release.Highlights since the last release:
APP_VERSION+package.json, in sync — About dialog and binary agree).After merge, tag
v4.2.9onmainto trigger the Build and Release workflow.🤖 Generated with Claude Code