Skip to content

Sync/autonomy development 20260703#4

Merged
JW-Control merged 64 commits into
developmentfrom
sync/autonomy-development-20260703
Jul 5, 2026
Merged

Sync/autonomy development 20260703#4
JW-Control merged 64 commits into
developmentfrom
sync/autonomy-development-20260703

Conversation

@JW-Control

Copy link
Copy Markdown
Owner

No description provided.

Manuele Conti and others added 30 commits March 24, 2026 22:21
Fix a race where the renderer could request system:get-system-info while the Electron BrowserWindow was already being destroyed.

The IPC handler previously called isMaximized() on a stale BrowserWindow reference, which raised "Object has been destroyed" and surfaced as an unhandled promise rejection in development logs.

This change guards the main-process handler with isDestroyed() before reading window state, and wraps the renderer initialization request in try/catch so startup or teardown races do not produce unhandled rejections.
Guard development-only menu and devtools initialization paths against BrowserWindow teardown races.

This avoids Object has been destroyed errors when the menu is rebuilt during window shutdown, adds an explicit catch for asynchronous menu rebuild failures, and downgrades optional React DevTools installation failures to a compact warning instead of a noisy stack trace during development startup.
Signature verification previously ran only in `importFromFile`; the
persisted package store was not re-checked afterwards. Add
`PackageManagerModule.verifyInstalledSignatures()`, run once at
`app.whenReady()` before the first window. It re-verifies every
registry-listed package against TRUSTED_PACKAGE_KEYS and removes any whose
signature does not validate (directory + registry entry), logging a
warning per removal. Out-of-tree registry paths are de-listed without
touching disk. No-op when REQUIRE_SIGNATURE is false.

Scope: VPP packages only (userData/packages). User-installed libraries
(userData/libraries, LibraryManagerModule) are unsigned by design and are
not affected.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…hority

Mirrors the web change: the editor now centralizes JWT management in the main
process via the shared RuntimeTokenManager, so every runtime call self-heals on
expiry — including project upload, which previously did a raw HTTPS POST with no
refresh (the reason a long session could keep polling status while uploads 401'd).

- main process owns the token + credentials via the shared manager; login sets
  the session, clearCredentials clears it, and a single onTokenChanged
  subscription broadcasts runtime:token-refreshed to the renderer.
- makeRuntimeApiRequest / makeRuntimeApiPostRequest now run through tokens.withAuth
  (replacing the bespoke attemptTokenRefresh + per-call broadcast).
- New makeRuntimeApiUpload on the bridge does the multipart upload through
  tokens.withAuth; the upload pipeline (editor-compiler-platform-port) routes to
  it and the old refresh-less CompilerModule.sendRuntimeUpload is removed.
- Shared, byte-identical pieces with web: RuntimeTokenManager (+tests),
  RuntimePort.getAccessToken, and use-runtime-polling adopting refreshed tokens
  into the store connection flag.
- IPC handlers still accept (but ignore) the legacy jwtToken arg; the param is
  removed from the signatures in the follow-up cleanup.

Validated the runtime auth contract against a live SLM-RP4: TTL 900s, expiry 401.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…PC surface

With the main process as the token authority, the renderer no longer holds or
passes a token. Removes jwtToken from the runtime + EtherCAT IPC handlers, the
renderer bridge signatures, and the editor adapter (which now tracks only a
loggedIn flag for isReadyForDebug); makeRuntimeApiRequest/Post drop the param
too. The debugger's separate connectionParams.jwtToken is untouched.

deviceContext.jwt is kept solely as the 'are we logged in' gate for the upload
phase; the compile pipeline no longer threads it into runtime calls.

Brings the rewritten editor runtime-adapter to 100% coverage (adds the
previously-untested EtherCAT delegators). tsc --build, eslint, prettier clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Keeps the editor layer validator aligned with web's classification of the
shared RuntimeTokenManager directory.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…-dev-menu-startup

# Conflicts:
#	src/main/menu.ts
…ndow-teardown

Guard system info requests during window teardown
…enu-startup

Harden dev menu rebuild and extension startup paths
The .sidebar-scroll rail reserved scrollbar space on its left edge only
(scrollbar-gutter: stable + direction: rtl), shifting every icon right of
center: +3px in Chromium, +9px in Safari, which reserves the full ~16px
native scrollbar width regardless of the ::-webkit-scrollbar customization.
Items outside the scroll container (exit button, logo) stayed centered,
making the offset visible.

Hide the scrollbar from layout entirely (scrollbar-width: none +
::-webkit-scrollbar display: none) so the icon column centers in the full
56px rail; scrolling still works via wheel/trackpad. Also remove the
nineties-theme rules that compensated for the old gutter.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0118h3QKR2X7kPt2G2LMWjmL
Run the installed-package signature sweep when a project opens instead of
once at app startup, and surface each removal as a WARNING in the console
panel. verifyInstalledSignatures() is exposed through the PackagePort
(editor adapter -> packages:verify-signatures IPC); workspace-screen calls
it on project load and logs removed ids. On removal the main process emits
packages:boards-updated so the device list refreshes via the existing
subscription. Removes the app.whenReady() startup sweep.

Platforms without a local package store (web) don't wire a `packages`
port, so the shared open-project effect is a no-op there.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ify-installed-signatures-on-startup

fix(package-manager): verify installed VPP signatures on project open
…lized-runtime-token-manager

feat(runtime-auth): centralized token manager (editor half — fixes upload after token expiry)
…sidebar-misalignment

fix(ui): center activity-bar icons by hiding the sidebar scrollbar
Fixes three UI/UX issues in the remote-device (Modbus master) and
OPC-UA server editors:

1. Editing a Modbus IO Group's size had no effect. `updateIOGroup` only
   reassigned the group metadata via `Object.assign`, so the underlying
   `ioPoints` list (and therefore the size shown in the table) never
   changed. It now regenerates the edited group's I/O points to match
   the new length / function code, reusing the group's own freed
   addresses (pool built from every other producer) and preserving each
   point's alias positionally. The change is localized to the edited
   group — no project-wide reallocation.

2. Replaced double-click-to-edit on the IO Group table with per-row
   Edit / Delete icon buttons (matching the OPC-UA Security Profiles
   pattern) and removed the redundant "-" toolbar button, keeping only
   "+" to add.

3. Fixed low-contrast table headers on the OPC-UA Security Profiles,
   Users, and Certificates tables (unreadable in dark mode) by adopting
   the Modbus IO table's header styling.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
thiagoralves and others added 29 commits July 2, 2026 21:43
…ry (Phase 3)

Wires the Modbus remote-device producer onto the central IEC address
registry, delivering project-wide gap reclamation (bug #4) via the shared
core instead of a bespoke allocator.

- New `recalculateRemoteDeviceAddresses` project action: derives consumers
  from live producer state, keeps pin-mapping / VPP / EtherCAT pinned as
  fixed constraints, re-packs the Modbus producers (capability-scoped so a
  target switch drops inactive kinds and reclaims their space), writes the
  compacted addresses back onto the `ioPoints`, and reconciles bound
  variables via `syncVariableAliases`.
- `addIOGroup` / `updateIOGroup` / `deleteIOGroup` / `deleteRemoteDevice`
  now call it, so removing a group or device slides the survivors down into
  the freed slots (project-wide, across devices) with no gaps.
- EtherCAT stays on its own bit-offset allocator for now (participates as a
  fixed constraint); it moves onto the registry in a follow-up once its bit
  packing is verified against the runtime.

Updated two tests that had asserted the old gap-preserving / localized
behavior to the new project-wide compaction. 266 project-slice tests pass;
new code fully covered.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…se 4)

`setDeviceBoard` now triggers the central `recalculateRemoteDeviceAddresses`
when the target actually changes. Because the recalculation is
capability-scoped, switching from a platform WITH pin mapping / VPP to one
WITHOUT frees those addresses and the Modbus producers recompact
project-wide into the reclaimed space, with bound variables following. No-op
when the board is unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds `RegistryChannel.memoryKey` (a stable semantic identity like
`moduleId:slot:channel` that outlives the channel's presence) and the pure
`restoreAliasesFromMemory(registry, memory)` helper. This backs the
requirement that removing a producer (e.g. a VPP module) and re-adding the
same one on the same slot restores its aliases within a session — the
memory is keyed so "same module, different slot" and "different module,
same slot" resolve differently. The memory Record lives in the store
(session-scoped, never serialized); this is just the pure restore step.

60 tests, 100% coverage. Additive.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
VPP now delegates address allocation to the central registry instead of its
own per-effect `nextFreeAddress` loop, so VPP + Modbus are packed by one
allocator (project-wide, capability-scoped), and adds the session
alias-memory that restores a module's aliases on remove→re-add.

- Generalized `recalculateRemoteDeviceAddresses` → `recalculateIecAddresses`:
  reallocates VPP + Modbus (`ALLOCATED_KINDS`), restores aliases from the
  session memory (`restoreAliasesFromMemory`), and writes addresses+aliases
  back to VPP `io-mapping` entries (via `setVendorScreenData`) and Modbus
  `ioPoints`.
- Session alias-memory: new `iecAliasMemory` project-slice field (never
  serialized) + `rememberChannelAlias` action. `migrateToRegistry` now stamps
  each channel with a stable `memoryKey`; `PoolVppIoInput` carries `moduleId`.
- Both VPP layouts: allocator effects build channel structure then call
  `recalculateIecAddresses`; alias editors record into the session memory.

Pins + EtherCAT remain fixed constraints for now (their own commits).
428 store/registry tests pass; tsc, eslint, arch clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
EtherCAT channel addresses are now allocated by the central registry
alongside VPP + Modbus (independent-prefix model, matching the runtime's
separate typed buffers — the esi-parser's cross-width overlap avoidance was
over-conservative and its output is now superseded here; the dead allocator
is removed in the legacy pass).

- `ALLOCATED_KINDS` += `ethercat`; new `applyEthercatAddresses` writes the
  registry's addresses + aliases back onto each slave's `channelMappings`.
- `updateEthercatConfig` triggers `recalculateIecAddresses`, so channel-mapping
  changes recompact project-wide and bound variables follow.

271 project-slice tests pass; tsc, eslint clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…nstraints (Phase 7)

- `clearProjects` now resets the session alias-memory so one project's
  remembered aliases can't leak into the next (the memory is session-scoped
  and never serialized; only current addresses/aliases persist to disk).
- Documents pins as fixed registry constraints (hardware addresses are never
  reallocated; aliases persist per-board through the same uniqueness gate) and
  records the implementation status + deliberately-deferred legacy removals in
  docs/iec-address-registry.md.

272 project-slice tests pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…lobals

Derive the composite-key→index map from the debug tree (the one
traverseVariable enumeration walk) instead of a parallel inline walk, and
fan a polled value out to every composite key that shares an address.

Fixes two shared-global bugs surfaced with located VAR_GLOBALs:
- forcing a located global from the LD/FBD editor silently no-op'd (the
  inline index-map walk missed the VAR_EXTERNAL case, so the composite key
  had no protocol index).
- a global referenced by two programs displayed on only one POU (the
  index→leaf map was 1:1; now 1:many).

Also names the single path rule (buildVariableDebugPath); OPC-UA keeps using
the shared path primitives + address map. HIL-validated on P1AM-100 + SLM-RP4.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…led-globals

fix(debugger): resolve + force shared/located globals (Autonomy-Logic#653)
The unit-tests workflow runs 'npm ci --ignore-scripts', which skips the
postinstall that fetches strucpp (a GitHub-release package, not an npm dep).
The TS sources import strucpp/libs/iec-types.json and its runtime headers, so
every suite failed to load with 'Cannot find module strucpp' — the job has
been red on every run since it was added.

Add a '--strucpp-only' mode to download-binaries (installs just the
platform-independent strucpp package, no xml2st binary / native rebuild) and a
'setup:strucpp' script, then run it after install in the workflow so the suites
load and the coverage gate actually executes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…-tests-strucpp

fix(ci): install strucpp in the unit-tests job
These suites predate the XML/xml2st → in-process JSON-transpiler migration and
never ran in CI (the unit-tests job was broken), so they drifted:
- compile/pipeline.test.ts: rename transpileXmlToSt→transpileToSt, assert the
  current {projectData} call contract, drop the dead XmlGenerator mock + XML
  stage test, add a simulator-no-binary edge test.
- library/build-pipeline.test.ts: drop the XmlGenerator mock and the removed
  XML-generation/empty-data paths; assert prepareXmlForLibraryBuild's current
  {projectData,knownPous,manifest}|{error} contract.
- library/library-build-orchestrator.test.ts: fake port uses transpileToSt
  ({projectData}, log); remove plc.xml intermediates/logs; restore coverage.

All three source files back to 100% funcs/lines/statements. 95 tests pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Distinguish manually-located variables from alias-bound ones by storing
the binding itself in `variable.location`: either an alias name OR a
literal `%addr`. Aliases resolve to addresses at compile time in the
editor (via projectActions.getCompileReadyProjectData); the compiler and
runtime never see aliases. A missing alias resolves to an empty location
(the variable becomes unlocated).

- Remove the separate `alias` field from PLCVariable + the auto-adoption
  and sync-variable-aliases engine; location is stored verbatim.
- Add registry/resolve helpers (buildAliasIndex, resolveLocation,
  isLiteralLocation) and a pre-compile snapshot transform.
- renameAlias cascades onto every bound variable's location; EtherCAT /
  Modbus / pin / VPP alias edits drive it.
- Legacy two-field projects are folded on load (foldLegacyVariableAliases).
- Location picker allows a manual %addr even when an alias occupies that
  address; the cell renders location verbatim (alias name or %addr).
- Warn (amber glyph + Radix tooltip) when an alias-bound location no
  longer resolves (orphaned) OR when a manual %addr collides with an
  alias in use by another project variable (duplicate-location risk).
- Relax variableLocationValidation to accept alias-name locations.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…-shared-stale-tests

test(backend/shared): de-XML-ify stale compile/library-build suites
Replace the removed syncVariableAliases auto-adoption integration test with
`alias-location-on-board-load.test.ts` asserting the single-field contract:
board-load (setAvailableOptions) never mutates a variable's location, a manual
%addr colliding with an alias stays literal, and an alias-bound location
resolves to the producer address (or '') only in the compile-ready snapshot.
Also note the manual-conflict warning in the CLAUDE.md alias-registry section.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…l-iec-address-registry

feat(iec-address): central IEC address registry + single-field variable locations
The debug-compile path (handleDebugger) passed raw project data with
unresolved alias-name locations to compileForDebug, so the generated
_config.st emitted `AT <alias>` (e.g. `start_pb AT in_0`) which STruC++
rejects with `Expected DirectAddress, found identifier`. The build/upload
paths already resolve aliases → addresses via getCompileReadyProjectData;
wire the same pre-compile snapshot into the debug-compile call.

Regression from the single-field variable-location refactor (Autonomy-Logic#915 / Autonomy-Logic#575):
resolution was added to build/upload but the debug path was missed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ompile-resolve-aliases

fix(debug): resolve alias-bound locations before debug compilation
… blank) bound vars when an alias is cleared

Two variable-location warning bugs:

1. The location cell rendered the warning glyph only in its display
   branch, so selecting the row (which swaps to the editable combobox)
   hid the glyph while the conflict/orphan still stood. Render the glyph
   in both the selected and display branches, in the program and global
   variables tables.

2. Clearing an alias at its producer cascaded the empty string onto
   bound variables via renameAlias, silently wiping their location. Now
   an empty rename is treated as a deletion: bound variables keep the
   (now-missing) alias name and surface as orphaned (amber warning),
   matching the behavior of deleting the whole producer/device. Also
   commit the VPP io-table / module-slots alias inputs on blur (local
   state) instead of per keystroke, so a single rename reaches the store
   rather than one per intermediate string while editing.

Bump editor to 4.2.8.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…n-warning-and-alias-clear

fix(alias): persist location warning on select; orphan bound vars on alias clear (4.2.8)
Byte-identical mirror of the shared frontend / middleware changes that
land in openplc-web (PR fix/ai-autocomplete-empty-completions), kept in
sync by the mirror gate:

- monaco editor: `editContext: false` (Safari Tab-accept fix) and
  `inlineSuggest.experimental.showOnSuggestConflict: 'always'` so AI
  ghost text renders alongside the STruC++ LSP suggest widget.
- ai-port: new `completion_empty` telemetry event name.

The empty-completion behavioural fixes themselves live in the web-only
AI adapter (context builder + inline completion provider) and the
autonomy-edge backend, so no desktop release is required — this only
keeps the shared surface identical.

Also documents the app-version bump procedure in CLAUDE.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…complete-empty-completions

fix(ai): mirror shared-surface AI autocomplete fixes from openplc-web
Byte-identical mirror of the openplc-web APP_VERSION bump so the shared
surface stays in sync. Desktop's displayed version is tag-driven via CD,
so this only aligns the shared constant — no tag, no desktop release.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ersion-4.2.8

chore(version): bump APP_VERSION to 4.2.8 (mirror)
Byte-identical mirror of the shared monaco/index.tsx change in openplc-web
(fix/ai-completion-idle-retrigger): after 2s idle with AI on and no ghost
visible, re-trigger inline suggest so completions don't "give up" after a
late/dropped result. The debounce + token-guard halves of that fix live in
the web-only AI adapter, so no desktop behavior beyond this shared editor
wiring — no version bump, no tag.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…letion-idle-retrigger

fix(ai): mirror inline-completion idle re-trigger from openplc-web
@JW-Control
JW-Control merged commit 8ee7b5a into development Jul 5, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants