v4.2.0-rc1 — STruC++ compiler + Vendor Plugin Packages#748
Open
thiagoralves wants to merge 243 commits into
Open
v4.2.0-rc1 — STruC++ compiler + Vendor Plugin Packages#748thiagoralves wants to merge 243 commits into
thiagoralves wants to merge 243 commits into
Conversation
Add PackageManagerModule for importing, listing, and uninstalling VPP packages. Extend HardwareModule to merge VPP boards into available boards list and support package-relative preview images. Add VPP fallback to CompilerModule's board runtime resolution. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add VPP domain types (VppMetadata, PackageManifest, InstalledPackage, IoMappingEntry, etc.) to shared port types. Create PackagePort interface for platform-agnostic package management. Create editor package adapter that delegates to IPC bridge. Extend DevicePort.getPreviewImage with optional packagePath. Add hasPackageManager capability flag. Wire packages port into editor platform. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add package manager IPC handlers in main process for import, list, uninstall, and manifest operations. Add corresponding renderer bridge methods. Update getPreviewImage to support optional packagePath for VPP boards. Add "Board Package Manager..." menu item to both Darwin and default menu templates. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add vendorScreenData field to device configuration schema. Add setVendorScreenData action to device slice. Add plc-vendor-screen and plc-package-manager editor model variants. Add vendor-screen and package-manager tab element types with creation helpers. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add frontend utility for collecting used IEC addresses across all IO sources (pins, remote devices, vendor modules) and generating next available addresses. Pure frontend code with no backend dependencies. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Create VendorScreenRenderer with section renderer and layout components (module-slots, io-table, form, unsupported). Create PackageManagerEditor using usePlatform() hooks instead of direct IPC calls. Create VendorScreenEditor wrapper. All components use platform-agnostic imports for cross-platform reuse. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add vendor-screen and package-manager tab icons and type handling. Add vendor screen leaves to project tree when VPP board is selected. Route plc-vendor-screen and plc-package-manager editor types in workspace screen. Subscribe to package manager open/boards-updated events via packages port. Pass packagePath for VPP board previews. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1. Add "Install additional boards..." option at the end of the Device dropdown that opens the Package Manager tab. 2. Replace emoji icons in debugger-message modal with the existing WarningIcon SVG component. 3. Use screen names as-is from the manifest instead of applying camelCase splitting (they are already human-readable). 4. Add vendor IO mapping aliases to the POU variable location dropdown by creating buildVendorIoOptionGroups utility and vendorIoSelectors hook, integrated into both variables-table and global-variables-table. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove separator and dark mode color override to match the original VPP branch styling for the install boards dropdown option. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Restore the border-t separator before the install option matching the original branch, and add a + prefix to the label text. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Detailed investigation of why compilation fails with large PLC projects (32K+ debug variables). Documents the root causes across the full pipeline (xml2st, MatIEC, runtime, editor) and proposes 6 fix strategies ranging from quick platform-aware limits to a full dynamic registration architecture. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
# Conflicts: # src/types/PLC/devices/configuration.ts
…calability-large-projects
Comprehensive 8-phase migration plan covering the replacement of iec2c (MatIEC) with STruC++ for ST-to-C++ compilation, redesigning the debugger for scalability (hierarchical variable addressing instead of flat array expansion), and adapting both Arduino and Runtime v4 backends. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace fragile file:../strucpp approach with the same download mechanism used for matiec and xml2st: version tracked in binary-versions.json, downloaded from GitHub Releases via scripts/download-binaries.ts. Runtime headers come from the same release artifact as the compiler, ensuring strict version coupling. No local copies stored in the repo. Also documents required changes to STruC++ release workflow (add npm pack step to produce .tgz artifact). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add strucpp v0.2.4 entry to binary-versions.json - Extend scripts/download-binaries.ts to download the STruC++ npm tarball from GitHub Releases, install it into node_modules, and extract runtime headers + .stlib libraries to resources/strucpp/ - Add resources/strucpp/ to .gitignore - Update Phase 1 docs to reflect simplified approach: no compile wrapper needed, editor calls compile() directly, Arduino runtime navigates STruC++ structures dynamically Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove all references to the TypeScript glue code generator and compile wrapper. The new design is simpler: - Phase 2: Arduino sketch is fully static C++ that navigates STruC++ structures dynamically (locatedVars[], ConfigurationInstance, etc.) - Phase 4: compiler-module.ts calls compile() directly, no wrapper - Phase 6: v4_compat.cpp is a static shim, same philosophy Configuration name is always Config0 (hardcoded by OpenPLC), so the sketch can instantiate Configuration_Config0 directly. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Reorder phases so the editor compiler pipeline (code generation) comes before the Arduino runtime (code consumption). This lets us validate STruC++ output before building the runtime against it. New phase order: Phase 1: STruC++ dependency infrastructure (done) Phase 2: Editor compiler pipeline (wire compile() into compiler-module) Phase 3: Arduino runtime adaptation (sketch + openplc.h) Phase 4: Debugger (deferred) Phase 5-7: Runtime v4 (renumbered from 6-8) Also fix download-binaries.ts to use --no-save when installing strucpp to avoid polluting package.json with temp paths. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Update all phases to reflect that this branch fully replaces MatIEC with no backward compatibility: - Phase 2: Remove dual pipeline routing and compiler_backend field. MatIEC pipeline is deleted, not preserved alongside STruC++. - Phase 3: openplc.h MatIEC declarations are removed, not guarded. - Phase 4: Debugger designed from clean slate, no old format support. - Phase 5: compile.sh has no MatIEC fallback branch. - Phase 6: No legacy single-thread model for MatIEC .so files. - Phase 7: No flat-index backward compatibility layer. - Overview: Replace "Dual Pipeline Coexistence" with "Full Replacement". Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace the iec2c binary invocation with STruC++ compile() for ST-to-C++ compilation. This is a clean break -- no dual pipeline, no backward compatibility with MatIEC. Changes: - compiler-module.ts: Remove handleTranspileSTtoC (iec2c), handleGenerateDebugFiles, handleGenerateGlueVars, handlePatchGeneratedFiles, checkIec2cAvailability. Add handleCompileSTtoCpp using STruC++ compile(). Update copyStaticFiles to copy STruC++ runtime headers. MD5 hash computed directly from program.st content. - binary-versions.json: Remove matiec entry - download-binaries.ts: Remove matiec download logic - compiler-adapter.ts: Update inferStage for STruC++ messages - jest.config.json: Add transformIgnorePatterns for strucpp ESM - Tests updated: all 37 tests pass Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add hals.json cxx_flags update and end-to-end compilation test to Phase 3 docs, since both require the Arduino sketch to exist. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The Arduino sketch is a Phase 3 artifact. The pipeline should not fail at the static file copy step -- it should proceed through STruC++ compilation and only fail at arduino-cli (which requires the sketch). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace MatIEC's {{ ... }} embedded C syntax with STruC++'s
{external ... } pragma in both Python and C++ POU code generators.
STruC++ uses {external ... } to pass C/C++ code through AS-IS to the
generated output. The content inside is identical -- only the delimiter
syntax changes.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Create StrucppBaremetal.ino: static sketch that dynamically navigates STruC++ runtime structures -- walks locatedVars[] for I/O binding, discovers tasks via Configuration/Resource/Task, computes GCD for scan cycle timing, schedules programs with per-task divisors. - Clean up openplc.h: remove MatIEC-specific declarations (config_init__, config_run__, glueVars, updateTime, common_ticktime__). Keep IEC types, buffer pointers, buffer size macros, and HAL functions. - Add -std=gnu++17 to cxx_flags for all 62 boards in hals.json. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
v0.2.5 adds iec_platform.hpp shim so runtime headers use <stdint.h> instead of <cstdint> on AVR targets where C++ standard library wrappers are not available. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
AVR GCC doesn't ship C++ standard library headers (<type_traits>, <algorithm>, <cstdint>, etc.) that STruC++ runtime headers require. Bundle avr-libstdcpp (GPLv3, header-only, from modm-io/avr-libstdcpp) and add -isystem include path for AVR boards during compilation. STruC++ runtime headers stay standard-compliant -- the AVR compatibility is handled entirely at the editor build pipeline level. Also bump strucpp to v0.2.6 (reverted AVR-specific changes from runtime). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
v0.2.7 guards <stdexcept> includes and throw statements with #ifndef __AVR__ so runtime headers compile on AVR targets where exceptions are not available. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The full avr-libstdcpp (GCC 10) is incompatible with Arduino's AVR GCC 7.3 due to c++config.h version mismatches. Replace with minimal custom headers that provide only what STruC++ runtime needs: - type_traits: integral_constant, enable_if, is_same, is_integral, is_floating_point, is_arithmetic, is_signed, is_unsigned, is_enum, is_class, is_base_of, is_convertible, underlying_type, etc. - utility: move, forward, swap - algorithm: min, max, find, copy, fill - array: std::array<T,N> - C wrappers: cstdint, cstddef, cstring, cstdlib, cmath, cstdio - Stubs: string, stdexcept, ostream, new (for headers that include them but guard usage with #ifndef __AVR__) Also fix: use -I (not -isystem) for AVR C++ headers -- GCC 7.3 treats -isystem headers as C linkage on AVR, causing "template with C linkage" errors. Also fix: rename StrucppBaremetal.ino to Baremetal.ino (Arduino requires .ino filename to match directory name). Also fix: #undef min/max in sketch to prevent Arduino macro conflicts with std::min/std::max and numeric_limits. Remaining issues to fix: - iec_located.hpp static_assert for 16-byte LocatedVar fails on AVR (pointers are 2 bytes, not 8) - Arduino binary.h defines B0-B7 macros conflicting with template params in iec_traits.hpp - iec_array.hpp uses C++17 deduction guides not supported by GCC 7.3 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Editor-side fixes for successful Arduino Mega compilation: Baremetal.ino: - #undef Arduino macros (min/max/abs/round/TIMER*) before STruC++ includes - Include openplc.h before generated.hpp to avoid IEC type ambiguity - Use ::IEC_BOOL etc. (global scope) in casts to disambiguate from strucpp:: - Configuration_CONFIG0 (STruC++ uppercases configuration names) - Define I/O buffer arrays (previously in MatIEC glueVars.c) - Provide sized operator delete stub (AVR virtual destructors need it) - Define __CURRENT_TIME_NS global for STruC++ time operations - Renamed to Baremetal.ino (Arduino requires filename = directory name) openplc.h: - Wrap HAL functions in extern "C" for proper C/C++ linkage avr-libstdcpp: - Add make_unsigned/make_signed, common_type to type_traits - Add std::nullptr_t to cstddef - Add strtoll/strtoull stubs to cstdlib (AVR libc lacks 64-bit strtol) - Add missing C functions to cstring and cstdlib - Add abs/trunc/log10 overloads to cmath compiler-module.ts: - Use -I (not -isystem) for avr-libstdcpp (GCC 7.3 treats -isystem as C linkage) Bump strucpp to v0.2.8 (AVR runtime header compatibility). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The runtime now loads VPP plugins exclusively from vpp_plugins.conf, which the editor owns and generates on every program upload when the target is a VPP runtime-v4 board. The file lists one entry per VPP plugin in the same CSV format as plugins.conf (name, .so path, enabled, type, config_path, venv_path), with deterministic paths that match what compile.sh produces and apply_vpp_plugin_conf() copies the config to: <name>,./build/vpp/lib<name>_plugin.so,1,1,./build/vpp/<name>.json, For non-VPP uploads (vanilla Runtime v4 targets), no vpp_plugins.conf is generated, so apply_vpp_plugin_conf() on the runtime side will delete any stale file, ensuring clean program-switch behaviour. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
The compiler module looks for STruC++ runtime headers at <resourcesPath>/strucpp/runtime/include/ in packaged mode, but the electron-builder config only shipped resources/bin/ and resources/sources/ as extraResources. The strucpp directory (populated by scripts/download-binaries.ts during postinstall) was never copied into the installed app, so any compile attempt failed with: STruC++ runtime headers not found at .../resources/strucpp/runtime/include Adds an extraResources entry per platform (mac/win/linux) mapping ./resources/strucpp -> ./strucpp, mirroring the bin and sources entries that were already correct. Note: the cosmetic "STruC++ available at version 0.0.0" log line is a separate strucpp-side issue — its getVersion() falls back to "0.0.0" when it can't read its own package.json at runtime, which happens because the package is bundled inside the editor's webpack output (and thus inside an asar archive) where the relative package.json lookup doesn't resolve. The module itself is loaded and functional; only the version string is wrong. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Picks up the version-baking fix so getVersion() reports the real version (instead of "0.0.0") when consumed from inside the editor's webpack + asar bundle. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
feat(vpp): backplane editor + per-slot module config
…e include
The HAL templates included board defines as
#include "../examples/Baremetal/defines.h"
relative to arduino.cpp in the build's src/ folder. That works
locally when avr-g++ resolves the include from the source file's
on-disk location, but it breaks on:
- Paths with spaces inside unquoted -I/-include argv (Windows
Parallels shared-folder mounts like C:\Mac\Home\... that surface
"PLC Progs", "OpenPLC Simulator" etc. in the build path).
- Any arduino-cli flow that copies arduino.cpp into its temp
sketch sandbox before preprocessing, where "../examples/..."
resolves to a sibling of the sandbox instead of the project.
The user-reported failure on a fresh Windows VM was the second
mode: arduino-cli failed with "fatal error: ../examples/Baremetal/
defines.h: No such file or directory" even though the file existed
at the correct project-relative path.
Fix: drop the directory-relative include entirely.
- All 29 HAL templates now `#include "defines.h"` directly.
- The editor writes defines.h to <build>/<board>/src/defines.h,
next to arduino.cpp.
arduino.cpp finds defines.h in the same directory. Sketch files
(Baremetal.ino, ModbusSlave.h) already use `#include "defines.h"`
and resolve it via the `-I src/` library include arduino-cli adds
when it recognises src/ as a library — same as before. No other
path adjustments needed.
The `examples/Baremetal/` directory is still produced (sketch +
hex output) but no longer hosts defines.h.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…cate The FBD editor reads its render data exclusively from `fbdFlows`, falling through to a `<span>No rung found for editor</span>` branch when the lookup misses. `pouActions.create` (and `.duplicate`) only pushed the new POU into `project.data.pous` — never into the flow slice — so a freshly-created FBD POU opened to that fallback string instead of an empty canvas. Ladder dodged the visible symptom because its editor unconditionally renders a "Create Rung" button when `rungs[]` is empty, but the missing flow entry was the same underlying gap. The new-project wizard works because `handleOpenProjectResponse` iterates all loaded POUs and seeds both flow slices. The "Add POU" path bypassed that, which is the reported regression in #759. Fix: after `createPou` succeeds in both `create` and `duplicate`, push the body value into the matching flow slice. For `duplicate`, override the flow's `name` field because the shallow-copied body still carries the source POU's name. Fixes #759 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Brings the ~33 development commits accumulated since v4.2.0-rc1 was cut back onto the release branch: EtherCAT scan + stats pipeline, AI chat-history persistence, ladder branches with nested parallels (#756), and the server-tree visibility fix. Substantive conflict resolutions: * `store/slices/device/{types,slice}.ts` + tests Both branches added different actions to the device slice (rc1: vendor-screen persistence; dev: ethercat status + polling toggle + temporary DHCP IP). Kept both sets verbatim. * `_features/.../device/configuration/board.tsx` * `_features/.../device/orchestrators/orchestrators-list.tsx` Both branches reworked the connected-runtime stats panel. Kept rc1's `<ScanCycleStats>` / `<EtherCATStats>` / `<PluginStatsPanel>` molecule layout (renders immediately on connect, no scan-count gate) and adopted dev's `setIncludeEthercatStatsInPolling` polling toggle so the global `useRuntimePolling` hook fetches EtherCAT status only while a stats screen is mounted. * `_molecules/ethercat-stats/index.tsx` Rewrote so the molecule consumes `runtimeConnection.ethercatStatus` from the device slice instead of self-fetching on its own 2 s timer. Extended the column set with the metrics dev's `EthercatStatsSection` introduced (Master State, Slave Count, Max Exchange, Recovery Attempts, WKC consecutive-error badge) so no new health info is lost moving from cards back to the table. * `_features/.../device/ethercat/components/ethercat-stats-section.tsx` Deleted — its render is now handled by the unified `<EtherCATStats>` molecule. * `_organisms/explorer/project.tsx` Combined both gating intents: kept rc1's `projectCaps.hasServers` library gate AND dropped the `capabilities.hasLocalSerialPorts` clause per dev's d257e2a ("show Servers branch on platforms without local serial ports"). * `_features/.../device/ethercat/index.tsx` Kept rc1's `collectUsedIecAddresses` import path (`backend/shared/utils/iec-address`) and the extra `vendorScreenData` argument it now takes; adopted dev's `Modal` imports and `unmatched`-devices tracking in `handleAddSelectedFromScan`. Test-fixture updates to match the now-richer `EtherCATCycleMetrics` shape (min_*, period_*, latency_* fields are mandatory) and the expanded `RuntimeConnection` state (`ethercatStatus` + `includeEthercatStatsInPolling`). `npx tsc --noEmit`, `npm run build:main`, and the device / shared / ethercat / ladder test suites (1061 tests) all pass locally. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
VPP packages can now declare REAL-typed analog channels (e.g. the
SLM-THM-4 thermocouple module, whose wire format is an IEEE-754 float
per channel). Those channels need to land in the runtime's 32-bit
image table, not the 16-bit one, so the plugin config now carries
separate dword-range mappings.
generate-vendor-plugin-config:
- Channels are bucketed by addressPrefix instead of type, so an
"analogInput" with %ID and one with %IW route to different
slot.io_mapping fields.
- New DWORD_ADDRESS_REGEX + parseDwordAddress + buildDwordRange.
- PluginSlotIoMapping gains analog_real_inputs / analog_real_outputs
(base_dword + count).
Plain-INT (%IW/%QW) channels behave exactly as before; the new buckets
are only populated when the manifest declares %ID/%QD channels.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
VPP packages can now ship modules whose channel layout depends on a
config-screen value (e.g. SLM-RP4 V/mA cards flipping between raw
UINT/%IW and REAL/%ID for engineering units). The manifest declares
`formatFieldId` + `channelsByFormat: { <value>: [channels...] }` on
`addressMapping`; the editor picks the right array per slot.
src/frontend/utils/vpp/resolve-module-channels.ts (new):
- Pure resolver that takes (moduleDef, slotConfig) and returns the
channel array. Falls back to `channels` when no format mechanism
is declared, or when channelsByFormat is missing the resolved key.
- 6 unit tests cover the resolution paths.
io-table-layout.tsx, module-slots-layout.tsx:
- Both layouts use the resolver and depend on a derived
`formatSelectionKey` so re-allocation fires when the user toggles
a slot's format selector (not only when the slot module list
itself changes).
- Modules without channelsByFormat fall back to the legacy `channels`
array, so every existing VPP behaves exactly as before.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The variable-location validator only allowed memory prefixes (%MD, %ML), so users mapping a REAL/DINT/LREAL to a VPP module's %ID/%QD or %IL/%QL address hit "Location is invalid - valid locations: %MD0" even though those prefixes are perfectly valid IEC 61131-3 addresses. Widens DWORD_LOCATION_REGEX and LWORD_LOCATION_REGEX to accept any of %[QIM]D / %[QIM]L. Updates the error message to list all three. The auto-increment-on-collision logic now preserves the user's prefix instead of forcing %MD/%ML, mirroring the existing pattern already used for word-sized variables. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When a module uses channelsByFormat (e.g. SLM-AI4-AO2-V flipped to engineering mode), the manifest's fallback `channels` array still carries the raw-mode prefixes (%IW/%QW), but the editor allocated %ID/%QD addresses in the io-mapping. The plugin-config generator was bucketing channels by the manifest's static channel.addressPrefix, so it dropped the channels into the wrong bucket and buildWordRange failed to parse them as %IW — net effect: no analog_real_inputs / analog_real_outputs block in the plugin config, the SLM plugin saw ai_real_count = ao_real_count = 0, and the REAL path silently never fired (AO writes had no effect, AI readbacks were stuck at 0). Now bucket by the io-mapping's actual iecAddress prefix, which is the authoritative resolved address. The manifest channel array is still iterated for channel-name -> io-entry lookup, but only the allocated address determines the bucket. Side effect: a single malformed address no longer kills the entire bucket — it gets silently dropped and the valid addresses are still emitted (partial success rather than all-or-nothing). Updated the related test to reflect the new behavior. Adds a regression test that mirrors the bug exactly: a module declaring %IW/%QW channels in the fallback array but with %ID/%QD addresses in the io-mapping. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Backplane Configuration screen gets two improvements that VPP
packages can opt into independently:
1. Selected-slot indicator. The slot row of the currently selected
slot now picks up the brand-tinted background, a bold label, and
a 3px inset left-border (the same pattern Library Manager uses
for its highlighted row). Applies to physical and stackable
variants — the user always knows which slot the detail pane is
showing.
2. Stackable backplane variant. New "stackable: true" flag on the
module-slots section. When set:
- Only populated slots are shown in the slot list.
- A "+ Add module" button at the bottom appends a new slot
populated with the first available module.
- The "-- Empty --" option is dropped from the module picker.
- A red "Remove module" button next to the picker opens a
confirmation modal; on accept the slot is removed and all
following slots shift up (slotsConfig keys and io-mapping
slot numbers are renumbered too so aliases survive).
Physical (default) behaviour is unchanged — vendors with a fixed
chassis still use the existing fixed-slots flow.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The red trigger button stood out next to the rest of the Backplane Configuration controls. Switched to the same neutral-outline style the Clear All Slots button uses, so the row of controls reads as a single visual group. The confirmation modal keeps its red accent on the destructive action button, which is consistent with similar confirmations elsewhere in the app. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Clear All Slots wipes the whole backplane and releases every allocated I/O address — destructive enough to warrant the same confirmation pattern Remove module already uses. The screen JSON action already had a `confirm` string declared but it was being ignored; the local button now routes through a confirmation modal when the action declares `confirm`, otherwise fires immediately (unchanged for vendors that don't opt in). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Each populated slot row now has a grab handle (DragHandleIcon, same
icon the ladder rungs use). Wrapping the slot list in a DndContext
+ SortableContext, the user can pick up a slot and drop it anywhere
in the list — the dragged module lands at the target index and
intermediate slots shift one position to make room.
Reorder mechanics:
- arrayMove semantics for slots; slotsConfig keys re-mapped to
follow each module's new position so per-slot module config
(e.g. data_format selector) sticks with its module.
- io-mapping entries' slot field is rewritten with the same
re-map so user-typed aliases stay attached to their modules
through the move. The io-mapping reallocator then re-assigns
IEC addresses on the next render in the new slot order.
- The selectedSlot follows the dragged module if it was the one
being dragged, or shifts by ±1 if it was within the affected
range, so the detail pane keeps showing the same module.
Empty slots are not draggable (no module to move); their drag-handle
column is reserved as inert whitespace to keep row layout aligned.
Applies to both physical and stackable variants.
Heads-up: IEC addresses (%IW, %ID, %IX, …) will renumber on reorder
because the auto-allocator iterates slots in order. Aliases survive
the renumber, so user-facing names are preserved, but the underlying
numeric addresses change. This matches the existing behaviour when a
slot is added/removed and is consistent with how the io-mapping
table is generated from scratch on every slots-array change.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…target capabilities
Phase 0 of the alias source-of-truth work. Replaces every UI / feature
check that switched on `boardInfo.compiler === ...` with a capability
flag read from the board's TargetCapabilities block.
New shared module (backend/shared/utils/target-capabilities):
- TargetCapabilities interface with 13 flags grouped into address
producers (pinMapping, vppIo, modbusTcpRemote, ethercat), server
protocols (modbusTcp/opcua/s7), debugger transports, and build /
runtime behavior (pythonFunctionBlocks, arduinoApiCompletions,
hasRuntimeStats, isInProcessSimulator, directUsbUpload).
- Presets for Simulator, Runtime v3, Runtime v4, Arduino-CLI.
- resolveTargetCapabilities(boardInfo) — single helper that all
capability-relevant call sites use. Reads boardInfo.capabilities
directly when present; falls back to the legacy `compiler` string
only for hals.json files that haven't been migrated yet.
- Pure and side-effect-free, lives in backend/shared so openplc-web
inherits it byte-identically.
hals.json:
- Explicit `capabilities` blocks on OpenPLC Simulator, Runtime v3,
Runtime v4. Required because v3 and v4 both use the same
`openplc-compiler` compiler value — the resolver's compiler-string
fallback only knows v4 by default.
- Simulator reports server / remote-IO support as true (no-ops at
the bytecode level) so projects authored for Runtime v4 don't get
nagged when the user switches to Simulator to test.
middleware/shared/ports/types.ts:
- BoardInfo gains an optional `capabilities` partial block. Web's
BoardInfo is structurally identical — same change ports verbatim.
Call-site migrations:
- frontend/utils/device.ts: predicates reimplemented over the
resolver. Public API unchanged; internals now compiler-string-free.
- workspace-activity-bar/default.tsx: isSimulatorBoard now reads
caps.isInProcessSimulator.
- device/configuration/board.tsx: server / remote-device warning on
target switch now driven by the new target's capabilities
(modbusTcpServer/opcuaServer/s7Server and modbusTcpRemote/ethercat)
instead of "is target v4 or simulator". Python-FB warning now reads
caps.pythonFunctionBlocks.
- device/orchestrators/orchestrators-list.tsx: simulator detection
moved to caps.isInProcessSimulator. Kept here for openplc-web
byte-parity.
- editor/monaco/index.tsx: Arduino API completions now gated by
caps.arduinoApiCompletions instead of the
board-name.includes('OpenPLC Runtime') hack.
Build infrastructure (compiler-module, hardware-module, compiler-adapter)
left alone — it correctly switches on `compiler` to pick a toolchain;
those checks aren't capability decisions and aren't part of this scope.
14 unit tests for resolveTargetCapabilities covering preset shapes,
back-compat compiler-string fallback, explicit-capabilities override,
partial-block merge, and the web orchestrator-device path (capabilities
without a compiler field). Existing device.ts tests pass unchanged.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…s pool
Phase 1 of the alias source-of-truth work. Replaces the two scattered
utilities (`collectUsedIecAddresses` + `generateIecAddress`) with a
single producer-only pool that target-scopes claims via the
capability matrix from Phase 0.
New module (`backend/shared/utils/iec-address`):
- address-pool.ts: `buildAddressPool(inputs, caps, options?)` walks
every producer that's active for the current target and emits
a structure with `byAddress`, `byPrefix` (sorted), and a
`conflicts` report when two producers claim the same address.
Reservation pass for pin-mapping (Arduino-style fixed addresses)
runs first so other allocators can't claim a pin-bound slot.
- `nextFreeAddress(pool, prefix, isBit, startFrom?, alsoUsed?)`
replaces `generateIecAddress`. The `alsoUsed` set lets bulk
allocators track in-flight picks without rebuilding the pool
between iterations.
- `ignoreSource` (regenerate this producer) and
`ignoreCapabilities` (the user is actively editing this producer
so caps gating doesn't apply) are the two opt-ins.
- 22 unit tests cover producer scoping, capability switching,
conflict reporting, allocation, and prefix sorting.
Migrations (all consumers off the old utilities):
- VPP io-table + module-slots layouts use the pool with
`ignoreSource: 'vpp-io'` (they're regenerating VPP claims) plus
`alsoUsed` for the in-flight reallocation batch.
- EtherCAT device-editor and the bus editor build a pool and read
its keys into a Set<string> (preserving the existing
`externalAddresses` API). Same code path on both sites.
- project/slice.ts `addIOGroup` rebuilds the pool with
`ignoreCapabilities: true` (the user is actively editing the
Modbus TCP producer, so target gating is intentionally bypassed).
- `generateIOPoints` simplified: walks `nextFreeAddress` instead of
the manual bit/word arithmetic; takes the pool + pending set.
Cleanups:
- frontend/utils/iec-address.ts + its tests deleted (the old
`generateIecAddress` is replaced by `nextFreeAddress`).
- backend/shared/utils/iec-address/collect-used-iec-addresses.ts
+ its tests deleted.
- `EtherCATChannelMapping.userEdited` field removed (dead since
addresses are editor-allocated; both the TS interface and the
Zod schema dropped, esi-parser stops setting it). Zod's default
"strip unknown keys" behaviour keeps legacy projects loadable.
No call sites changed behaviorally — every existing flow allocates
the same addresses it did before, with target-scoping now correctly
releasing claims when capabilities change.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase 2 of the alias source-of-truth work. Adds the alias registry
(a derived view over the address pool) and brings pin-mapping in
line with the other producers by renaming `pin.name` -> `pin.alias`.
The pool already attached aliases to claims in Phase 1; this phase
gives the editor a way to look them up.
Alias registry (backend/shared/utils/iec-address/alias-registry.ts):
- buildAliasRegistry(pool): pure function returning byAlias /
byAddress indexes plus a duplicateAliases list (first-wins).
- resolveAlias(reg, alias) — the address an alias currently
points to. Returns undefined when orphaned.
- aliasForAddress(reg, addr) — the alias attached to an address
(the auto-adopt path for variables).
- isAliasNameAvailable(reg, alias, ignoring?) — system-wide
uniqueness check; an `ignoring` SourceRef lets a producer
rename within itself without false positives.
- 14 unit tests cover indexing, duplicate detection, target
scoping, and the orphan path.
Pin.name -> Pin.alias rename (system-wide):
- devicePinSchema (backend/shared/types/PLC/devices/pin.ts) renames
the field and adds a Zod preprocess step that migrates legacy
projects on load: `{ name: "x" }` -> `{ alias: "x" }`. Older
project files self-upgrade with no user action.
- DevicePin interface in middleware/shared/ports/types.ts updated
in lockstep (byte-identical port shape for openplc-web).
- PoolPinMappingInput.pin.name -> .alias; pin-mapping claims now
feed the alias registry exactly like every other producer.
- frontend/store/slices/device:
* slice.ts new-pin defaults use `alias: ''` instead of
`name: ''`; updatePin handles the renamed key.
* validation/pins.ts: pinNameValidation -> pinAliasValidation,
checkIfPinNameExists -> checkIfPinAliasExists,
checkIfPinNameIsValid -> checkIfPinAliasIsValid (plus updated
error message strings).
* types.ts: PinUpdateResponse.data renamed to match.
- UI:
* pin-mapping-table.tsx column accessor + header label flipped
from "Name" to "Alias".
* variables-table + global-variables-table picker labels read
pin.alias (cell label format unchanged).
- Tests: 134 tests across device-pins-validation / device-slice /
device-types / address-pool / alias-registry pass under the new
names.
Full test suite: 3809 passing (the pre-existing use-runtime-polling
failure remains, unrelated to this work).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase 3 of the alias source-of-truth work. Variables become aware of
the alias registry: when bound to an alias they display the alias
name in the location cell, and any commit that lands on an
address-with-alias auto-adopts the alias.
Schema (backend/shared/types/PLC/open-plc.ts):
- PLCVariableSchema gains optional `alias`. The field is purely
a UI hint + Phase-4 sync anchor; the authoritative location for
compile is still `location`. Schema bump is additive — older
projects without the field load unchanged.
updateVariable (frontend/store/slices/project/slice.ts):
- Whenever `updates.location` is supplied, the action rebuilds the
alias registry from current store state and patches
`updates.alias` to whatever the new address has bound (or
undefined if nothing). This is the auto-adopt path. Re-committing
the same address re-runs the lookup, which intentionally drops
aliases that are now orphaned.
- The pool is built without `ignoreCapabilities` here: alias
resolution honours target scoping — switching to a target that
deactivates a producer correctly orphans variables bound to its
aliases.
Variable cells (variables-table + global-variables-table):
- When not in edit mode, display the bound alias name in place of
the raw IEC address; hover tooltip shows `alias -> address` so
the underlying binding is one mouseover away.
- When edit mode is open, the raw address is exposed so the user
can rebind. The picker still commits an address; the
auto-adopt in updateVariable patches the alias.
- global-variables-table now reads row.original.alias (the editable
cell already had row access; this just exposes it on the
destructure).
Full test suite: 3809 passing (the pre-existing use-runtime-polling
failure remains, unrelated).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…on site
Phase 4 of the alias source-of-truth work. Adds the pure sync
function and threads it through every site that mutates an alias-
producing source, the board switch, project load, and pre-compile.
Pure function (backend/shared/utils/iec-address/sync-variable-aliases.ts):
- syncVariableAliases(variables, registry) walks an array of
located variables and produces three outcomes:
* adopted — variable had no alias bound but its `location`
matches an aliased address. Self-upgrade path.
* refreshed — variable has a known alias whose address has
moved; `location` follows the alias.
* orphaned — variable has an alias the registry no longer
knows about. Keeps `location` and `alias` so
the user can decide; reported for the UI.
- Generic over any SyncableVariable shape so concrete subtypes
(PLCVariable) thread through without losing fields.
- 11 unit tests cover every outcome and the carry-through invariant.
Store action (projectActions.syncVariableAliases):
- Builds pool + registry from live state under the active target's
capabilities, then runs the pure function over every POU's
interface variables and the global variables.
- Returns a counts-only summary `{ adopted, refreshed, orphaned }`
so callers can log one-liners.
- Honors target scoping: switching to a target that disables a
producer correctly orphans the variables that depended on it.
Wired triggers:
- Project load (shared/slice.ts handleOpenProjectResponse): runs at
the end of load so older projects self-upgrade on first open.
Logs a single info-level summary when anything changed.
- Pre-compile (workspace-activity-bar): the projectData handed to
the compiler is now sync-fresh on both compile paths (regular
build + MD5-mismatch confirm path).
- VPP io-mapping reallocator effects in io-table-layout and
module-slots-layout (every setVendorScreenData('io-mapping') the
reallocator and the alias-edit handler emit).
- VPP alias-edit handlers (handleAliasChange in both layouts).
- Modbus TCP `updateIOPointAlias` action (project slice).
- EtherCAT `syncDevicesToStore` (ethercat-device-editor) — covers
channelMapping alias edits and bus re-scans.
- Pin-mapping table: pin alias / address edits.
- Board switch (board.tsx useEffect on deviceBoard) — covers every
code path that ends up calling setDeviceBoard (regular pick,
Python-warning confirm, V4-features-warning confirm).
The compile pipeline contract is unchanged — it still reads
`variable.location` verbatim. Sync just guarantees the location is
current before any read happens.
Full suite: 3820 passing (+12). Only pre-existing
use-runtime-polling failure remains.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase 5 of the alias source-of-truth work. Surfaces orphans to the
user and documents the new system in CLAUDE.md.
New hook (frontend/hooks/use-alias-registry.ts):
- useAliasRegistry() builds the registry from live store state
via a memoized selector. Inputs subscribed individually so
re-derivation only fires when one actually changes.
Variable cell orphan badge (variables-table + global-variables-table
editable-cell.tsx):
- When a variable carries an alias the registry no longer knows
about, the cell renders an amber warning glyph next to the
label (the alias name itself stays — the user sees what the
binding used to be, just flagged).
- Hover tooltip explains the state: "Alias 'X' is no longer
declared by any active source. Last known address: ...".
- Text colour shifts to amber so the cell is obvious in a long
variable table without dominating it.
CLAUDE.md:
- New "IEC address allocation + alias registry" section under
Important Patterns, summarising address-pool / alias-registry /
sync-variable-aliases and the producer set. Points future
contributors at backend/shared/utils/iec-address as the single
source of truth.
Full suite: 3820 passing (the pre-existing use-runtime-polling
failure remains, unrelated).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…to-adopt
Two issues surfaced after the alias feature shipped:
1. Variable cell flipped between alias-name and IEC-address on row
select/deselect — confusing because the same cell rendered
differently depending on focus state. Now consistent:
- GenericComboboxCell gains an optional `displayLabel` prop. When
supplied, it overrides the trigger's visible text without
changing the underlying value the combobox edits.
- Both variables-table and global-variables-table compute
`combinedLabel = "{alias} ({address})"` (or just the address
when no alias is bound) and render it in BOTH the selected
(combobox closed-state) and unselected (HighlightedText) paths.
- Tooltip simplified: orphan state still shows its full message,
non-orphan state shows `alias -> address`.
- Editing (combobox open) still operates on the raw address — the
user types/picks an address, the alias is patched in by the
auto-adopt path.
2. Auto-adopt on project load didn't actually fire — the user had to
visit the VPP screen and edit an alias to see variables pick up
their bound names. Root cause: `availableBoards` is loaded
asynchronously by workspace-screen, but the project-load handler
runs the sync before that completes. The cap-resolver returns an
empty capability block when the board info isn't available yet,
so the address pool excludes every producer and the alias registry
ends up empty — adoption walks zero entries.
Two-pronged fix:
- `syncVariableAliases({ ignoreCapabilities })` now accepts an
opt-in to skip cap gating. The project-load handler in
shared/slice.ts passes `ignoreCapabilities: true` so every alias
declared anywhere in the project data is registered, regardless
of which board the user picked.
- workspace-screen.tsx's board-loading effect re-runs the sync
(cap-gated this time) once availableBoards resolves, so orphan
detection surfaces correctly for the active target without
waiting for the user to navigate to device-config.
Existing producer-mutation triggers and the board-switch effect
continue running cap-gated by default — the bypass only applies
to the initial load.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…slice access, allocator + memo improvements Addresses the review feedback on #762. Architecture (the must-fix): - Moved `target-capabilities/` and `iec-address/` from `backend/shared/utils/` to `middleware/shared/utils/`. The arch validator now maps `middleware/shared/utils/` to the existing `utils` layer, so store / hooks / components can import them cleanly without violating store -> backend-shared rules. - validate:arch is green for this PR; only the two pre-existing violations in frontend/utils/{debug-tree-traversal,pou-helpers} remain. Allocator (byPrefix utilisation): - Rewrote `nextFreeAddress` to merge the pool's sorted byPrefix list with the alsoUsed set and find the lowest unclaimed slot in one walk. Better address space utilisation for sparse claims (reclaims gaps the previous candidate-by-candidate scan would eventually find but only after walking past them). - Added a gap-fill test (sparse %IW claims), a non-bit %ID test fixture, and `nextFreeAddress(pool, '%ID', false, 3)` startFrom coverage. Conflict surface: - syncVariableAliases now writes a `warning`-level entry to the console slice when `pool.conflicts` is non-empty. Includes the first 5 conflicting addresses with their source kinds. Cross-slice typing (no more casts): - New `ProjectSliceRoot = ProjectSlice & DeviceSlice & ConsoleSlice` in project/types.ts. - createProjectSlice typed as `StateCreator<ProjectSliceRoot, ...>` so getState() returns the cross-slice union directly. The three `getState() as unknown as { ... }` casts (updateVariable, syncVariableAliases, addIOGroup) and the stale `pins as Array<{ name?: ... }>` cast are all gone. - Project-slice tests now compose project + device + console slices in their makeStore() helper. Store-level alias registry cache: - use-alias-registry.ts swaps per-cell useMemo for a module-level single-entry cache keyed on input identity. N variable cells in the same render now share one pool build. console.info → console slice: - Project-load sync summary routes through consoleActions.addLog so it shows up in the in-app console alongside other renderer logs. DebuggerTransport drift: - middleware/shared/ports/types.ts now re-exports DebuggerTransport / TargetCapabilities from the utils module instead of redeclaring the union inline. Single source of truth. PLCVariable.alias in middleware: - The interface in middleware/shared/ports/types.ts was missing the alias field added to the Zod schema in Phase 3. Caught when the new integration tests asserted `vars[0].alias`. Added it with the same docstring as the Zod schema. New integration tests (sync-variable-aliases-action.test.ts): - 7 scenarios covering adopt, refresh, orphan, target switch, ignoreCapabilities bypass, POU + global atomic sync, and conflict routing to the console slice. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…s the project-load sync The ignoreCapabilities flag on buildAddressPool / syncVariableAliases was a timing workaround that contradicted the whole capabilities model: bypass mode let VPP / pin-mapping / EtherCAT claims leak into the pool on targets whose caps didn't include them, breaking allocation on target switch (e.g. VPP %IW0-%IW7 from a previous SLM-RP4 session biasing a plain Runtime v4 Modbus allocation). The real bug was call ordering at project load: handleOpenProjectResponse ran sync before availableBoards landed, so caps resolved empty. Move the sync into deviceActions.setAvailableOptions — it's the natural project-load sync point (capabilities are accurate by then) and an existing sync site for board-refresh from VPP package installs. addIOGroup's defensive bypass goes away too: in real flows the workspace screen always seeds boards before the user reaches the remote-device editor. Tests that previously relied on the bypass now seed a Runtime v4 board fixture. Add a producer-agnostic test in the pure sync layer: build one registry from VPP + Modbus + EtherCAT claims with each alias relocated to a new IEC address, and assert every bound variable follows its alias. The refresh path doesn't distinguish source kind, so this single test covers the invariant for every producer. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ruth feat(alias): variables bind to producer-declared aliases with target-scoped allocation
Contributor
There was a problem hiding this comment.
🧹 Nitpick comments (1)
CLAUDE.md (1)
293-294: 💤 Low valueConsider clarifying "byte-identical on openplc-web".
The phrase "byte-identical on openplc-web" may benefit from a brief clarification for readers unfamiliar with the relationship between the editor and web codebases. Does this mean the implementation is shared/copied, or that the behavior is equivalent?
Example clarification
-Located in `src/backend/shared/utils/iec-address/` (byte-identical on -openplc-web). Pure functions, no IPC, no electron coupling. +Located in `src/backend/shared/utils/iec-address/` (shared +implementation with openplc-web). Pure functions, no IPC, no electron coupling.or
-Located in `src/backend/shared/utils/iec-address/` (byte-identical on -openplc-web). Pure functions, no IPC, no electron coupling. +Located in `src/backend/shared/utils/iec-address/` (duplicated to +openplc-web for consistency). Pure functions, no IPC, no electron coupling.Note: The static analysis tool flagged "openplc-web" as a potential spelling error, but this is clearly a project name and can be safely ignored.
🤖 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 `@CLAUDE.md` around lines 293 - 294, Revise the sentence mentioning "byte-identical on openplc-web" to explicitly state whether the implementation files are copied/shared or merely behavior-equivalent; update the text near src/backend/shared/utils/iec-address/ to say e.g. "the implementation is copied from openplc-web (byte-identical)" or "the implementation matches openplc-web behavior (not copied)" and add a short parenthetical noting that "openplc-web" is a project name to avoid static-analysis spelling flags.
🤖 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 `@CLAUDE.md`:
- Around line 293-294: Revise the sentence mentioning "byte-identical on
openplc-web" to explicitly state whether the implementation files are
copied/shared or merely behavior-equivalent; update the text near
src/backend/shared/utils/iec-address/ to say e.g. "the implementation is copied
from openplc-web (byte-identical)" or "the implementation matches openplc-web
behavior (not copied)" and add a short parenthetical noting that "openplc-web"
is a project name to avoid static-analysis spelling flags.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 8789590c-2ddd-407e-867a-92149311d265
⛔ Files ignored due to path filters (30)
resources/sources/boards/hals.jsonis excluded by!resources/**resources/sources/hal/arduino_opta.cppis excluded by!resources/**resources/sources/hal/controllino_maxi.cppis excluded by!resources/**resources/sources/hal/controllino_maxi_automation.cppis excluded by!resources/**resources/sources/hal/controllino_mega.cppis excluded by!resources/**resources/sources/hal/controllino_micro.cppis excluded by!resources/**resources/sources/hal/controllino_mini.cppis excluded by!resources/**resources/sources/hal/edge_control.cppis excluded by!resources/**resources/sources/hal/esp32.cppis excluded by!resources/**resources/sources/hal/esp8266.cppis excluded by!resources/**resources/sources/hal/fx3u-14-WS3U.cppis excluded by!resources/**resources/sources/hal/fx3u-14.cppis excluded by!resources/**resources/sources/hal/fx3u-24MR-DT.cppis excluded by!resources/**resources/sources/hal/giga.cppis excluded by!resources/**resources/sources/hal/iruinoVEA.cppis excluded by!resources/**resources/sources/hal/jaguar.cppis excluded by!resources/**resources/sources/hal/machine_control.cppis excluded by!resources/**resources/sources/hal/mega_due.cppis excluded by!resources/**resources/sources/hal/mkr.cppis excluded by!resources/**resources/sources/hal/nano_every.cppis excluded by!resources/**resources/sources/hal/p1am.cppis excluded by!resources/**resources/sources/hal/rp2040.cppis excluded by!resources/**resources/sources/hal/rp2040pico.cppis excluded by!resources/**resources/sources/hal/sequent_esp32.cppis excluded by!resources/**resources/sources/hal/stm32_f103cb.cppis excluded by!resources/**resources/sources/hal/stm32_f411ce.cppis excluded by!resources/**resources/sources/hal/stm32_f446zet_nucleo.cppis excluded by!resources/**resources/sources/hal/uno_leonardo_nano_micro_zero.cppis excluded by!resources/**resources/sources/hal/uno_q.cppis excluded by!resources/**resources/sources/hal/uno_r4.cppis excluded by!resources/**
📒 Files selected for processing (6)
CLAUDE.mdsrc/__architecture__/validate.tssrc/backend/editor/compiler/compiler-module.tssrc/backend/shared/ethercat/esi-parser.tssrc/backend/shared/ethercat/generate-ethercat-config.tssrc/backend/shared/ethercat/index.ts
…, and text-mode variable commits Drop the "Project opened!", "Changes saved!", "File saved", "Variables updated" and "Global Variables updated" success notifications. They fire on every routine save / load / text-mode flip, adding noise without information the user can't already see from the editor state. Error and warn toasts (parse failure, save failure, file-not-found, no-file-open) are kept so the user is still told when something needs attention. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…mposite generics The ladder editor's local validateVariableType iterated genericTypeSchema.shape[X].options assuming every entry was either a string or a ZodLiteral that resolved to a flat list of base-type strings. Composite generics (ANY_NUM, ANY_INTEGRAL, ANY_MAGNITUDE, ANY_CHARS, ANY_ELEMENTARY) violate that assumption — their unions are made of ZodLiteral instances pointing back into the schema, so resolving them requires walking the graph. Adding an EQ block (input type ANY_ELEMENTARY) hit a ZodLiteral inside the recursion and threw "subValue.toLowerCase is not a function". The FBD utils wrapper already delegated to frontend/utils/PLC/validate-variable-type which carries a correct recursive flattener. Replace the ladder duplicate with calls into the same shared helper, eliminating the duplicate code path entirely. Regression covered by validate-variable-type.test.ts (ANY_NUM, ANY_INTEGRAL, ANY_MAGNITUDE, ANY_ELEMENTARY). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
applyDynamicBlockHandleOffsets has two branches: an expansion path (block grew because of branch overlaps) and a reset path (no expansion needed — restore default). The reset path was hardcoded to clobber every block's height with blockStyle.height (= DEFAULT_BLOCK_HEIGHT = 128), regardless of how many input/output handles the block has. That works for typical 3-handle blocks but clips large function blocks: OSCAT SEQUENCE_4 declares 15 inputs and 8 outputs, so its natural frame is 620px tall. The reset pass overwrote it to 128px on every layout pass, leaving the bounding box sized for 3 connectors while IN3, START, RST, WAIT0..3, DELAY0..3, STOP_ON_ERROR, Q3, QX, RUN, STEP, STATUS rendered floating below the box. Compute naturalHeight from maxHandles using the same formula getBlockSize uses (FIRST_HANDLE_Y + (maxHandles - 1) * DEFAULT_OFFSET + 24) and apply it in the reset branch instead of the constant. Small blocks still get their default-equivalent height; large blocks finally fit their handles. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.
Summary
This release reconciles two long-running feature branches into a single line targeting v4.2.0:
.vpppackages; the editor ships with only OpenPLC Simulator + Runtime v3 + Runtime v4 by default.116 non-merge commits, 383 files. The +139k lines line stat is dominated by the bundled
avr-libstdcpp(199 freestanding-libstdc++ headers needed so STruC++ programs compile against the AVR Mega2560 simulator toolchain).Headline features
STruC++ compiler integration
The whole compile pipeline rebuilt around STruC++:
compiler-module.tsinvokes the STruC++ TypeScript API instead of the MatIEC binary chain. Generated C++ is split per-POU (one TU per FB / Program / Function plus a sharedconfiguration.cpp) so on-target builds canmake -j$(nproc)and ccache reuses unchanged.ofiles..stlibarchives ship underresources/strucpp/andnode_modules/strucpp/.avr-libstdcppshim underresources/sources/avr-libstdcpp/include/provides<cstdint>,<type_traits>,<array>,<string>,<initializer_list>, etc. for AVR builds — Arduino's avr-gcc ships no libstdc++. Compiler-module wires the include path whencorestarts witharduino:avr.tasks: [{ name, scan_count, scan_time_*, cycle_time_*, cycle_latency_*, overruns }]. Editor renders one row per task in the table.plc-task-Nplaceholders.Vendor Plugin Packages (VPP)
src/frontend/components/_features/[workspace]/editor/device/...andsrc/frontend/screens/). Browse, install, uninstall.vpppackages.PackagePortmiddleware port with editor adapter implementation. IPC bridge handlers (packageManager:*).hals.json) trimmed to OpenPLC-only defaults — VPP boards are dynamically registered after install.src/backend/shared/utils/iec-address/.STARTafter a successful build (replaces runtime's auto-start), withCOMMAND:BUSYretry handling.get_statsshows up as its own card grid below the IEC + EtherCAT stats. Rendered identically on the device-board screen (Electron) and the orchestrators screen (web) via a sharedPluginStatsPanelmolecule.EtherCAT (already on dev, refined here)
EtherCATStatstable component, rendered on both board + orchestrators screens.UI polish
Breaking changes
OpcUaNodeConfig.initialValueremoved;PLCTask.isSystemTaskandPLCTask.associatedDeviceremoved (the synthetic EtherCAT system-task they backed never shipped). Project files mid-flight on the strucpp branch may need re-saving.File scope highlights
src/backend/editor/compiler/compiler-module.ts— full rewrite around STruC++src/backend/editor/package-manager/— new modulesrc/backend/shared/utils/iec-address/— unified IEC-address collectorsrc/backend/shared/utils/vpp/— VPP plugin config generatorsrc/frontend/components/_features/[workspace]/editor/device/{ethercat,vendor-screen,package-manager}/— new UIsrc/frontend/components/_molecules/{scan-cycle-stats,ethercat-stats,plugin-stats-panel}/— table-shape stats componentssrc/frontend/store/slices/{vpp,version-control,navigation,...}/— store extensionssrc/middleware/shared/ports/{package-port,version-control-port,navigation-port,types}.ts— port additionsresources/sources/avr-libstdcpp/include/— bundled freestanding libstdc++ for AVR (~199 files)resources/strucpp/— STruC++ runtime headers + bundled stlibsTest plan
.vpppackage built fromopenplc-packages, see the new board in the selector.npm test(3290 pass),npm run validate:arch(clean), TS strict (clean).After merge
Delete
feat/vpp-package-support-v2andstrucpp-implementation— their work is fully captured here. Future feature branches start fromdevelopmentper usual flow.🤖 Generated with Claude Code
Summary by CodeRabbit