Skip to content

EDGE-639: rename Orchestrator to Edge Device and its children to vPLC - #1096

Merged
thiagoralves merged 7 commits into
developmentfrom
feat/edge-639/rename-device-vplc
Sep 18, 2026
Merged

thiagoralves merged 7 commits into
developmentfrom
feat/edge-639/rename-device-vplc

Conversation

@Thiago-Pio-Autonomy

@Thiago-Pio-Autonomy Thiago-Pio-Autonomy commented Sep 9, 2026 •

Copy link
Copy Markdown
Contributor

Implements EDGE-639, the openplc-editor half of demand EDGE-633. Mirror pair with openplc-web PR on the same branch name.

Why

"Orchestrator" is not a word the industrial automation audience owns. A new user opens the platform, sees it, and cannot tell what the feature does without reading the documentation, and that costs adoption in the first session. The ecosystem vocabulary therefore changes: the platform's Orchestrator becomes Device, its child becomes vPLC, and the agent becomes the Device Agent.

Inside these two repositories the word Device is already taken. The project tree's Device node is the PLC target being programmed: board selection, pin mapping, communication settings. That is a different concept from the platform's Device, so the platform entity reads Edge Device here (BR05, BR06), and the tree's Device node keeps its name (FR09).

Both assessments are short form and conclude no attack surface impact: display text over an unchanged data path, same API, same session, same authorization.

What changed

today becomes
tree leaf and its tab Orchestrators Edge Devices
panel heading Device Orchestrators Edge Devices
subtitle Select a device from your orchestrators to connect to. Select a vPLC from your Edge Devices to connect to.
refresh aria-label Refresh orchestrators Refresh Edge Devices
loading Loading orchestrators... Loading Edge Devices...
empty state No orchestrators found. No Edge Devices found.
empty-state hint Register an orchestrator ... Register an Edge Device ...
load error Failed to load orchestrators. Please try again. Failed to load Edge Devices. Please try again.
per-row count {n} device{s} {n} vPLC{s}
switch modal Switch Device / current device Switch vPLC / current vPLC
runtime status field Orchestrator agent Device Agent

Which noun each string takes was decided from what the code does, not from how the string reads. Two of them were specified the other way round and the code won: No Edge Devices found. renders on orchestrators.length === 0, and the load error is set in the catch around listOrchestrators(). Both describe the parent list, not the children, so both are Edge Device rather than vPLC. The per-row count reads orchestrator.devices.length and is therefore vPLCs; the switch modal is reached only from handleDeviceSelect on a child row and is therefore vPLC too.

The two "Switch Device" modals

There are two, with the same phrase and opposite meanings, which is the clearest illustration of why the tree's Device node keeps its name.

  • The one inside orchestrators-list.tsx is the vPLC. Its showSwitchConfirmModal and pendingDeviceSwitch are local useState, set only by handleDeviceSelect from a child row. It becomes Switch vPLC.
  • _organisms/modals/confirm-device-switch-modal.tsx keeps "Switch Device", and that is deliberate. It is opened only from device/configuration/board.tsx:299 with a newBoard / formattedNewBoard payload, so its Device is the PLC target being programmed. Renaming it would have been the exact mistake BR06 exists to prevent.

What deliberately did not change

  • The on-disk project format. The devices/ directory and devices/configuration.json, pin-mapping.json, servers/*.json, remote/*.json all stay (FR13). This is why no compatibility layer is needed and why no project saved on a customer machine is at risk.
  • derivation: 'orchestrators', the tab path, leafLang='devOrchestrators', the four DOM ids, OrchestratorPort, OrchestratorInfo, orchestratorId, hasOrchestratorDevices, kind: 'web-orchestrator', maxOrchestrators, OrchestratorIcon. Internal identifiers, and keeping the derivation key is precisely what keeps NFR01 safe.
  • "Orchestrator" in the ordinary software-engineering sense, which is RSK01: compiler-platform-port.ts (10), library-build-port.ts (11), library-build-orchestrator.ts (3), plus 45 more across backend/shared and the language service. A global find-and-replace corrupts the compile pipeline.
  • Remote devices, meaning Modbus master and EtherCAT bus master.
  • The wire contract per BR09: Edge API routes, WebSocket topic names, the agent's written paths.

FR14 ledger

Every remaining occurrence is accounted for with a reason: 402 in openplc-editor across 75 files, 672 in openplc-web across 100 files, grouped as RSK01 trio (24 each), pipeline and language-service prose (45), identifiers (230/216), test identifiers (82/79), wire contract (20/251), repo docs and config (1/57).

The number rose rather than fell, and that is expected. It was 365/635 after the string work; the new test file contributes 37 of its own identifiers (OrchestratorsList, listOrchestrators, useOrchestrator, orchestratorId), plus 3 re-added by the copy changes. Anyone comparing the two figures would otherwise reasonably suspect something was undone.

How it was verified

A vocabulary test for the screen, proven by negative control

orchestrators-list.tsx had no test in either repository, so AC03 was something a person had to remember to check. 13 cases now pin it: the six parent strings, the three child strings, the subtitle that names both, and a sweep asserting no visible /orchestrator/i.

It is not offered as evidence merely because it is green. Two mutations were run against it:

mutation result
{n} vPLC -> {n} device 4 cases red, failing on Unable to find an element with the text: 2 vPLCs
No Edge Devices found. -> No orchestrators found. 2 cases red: the empty-state case on its own text, and independently the /orchestrator/i sweep

Each failed on its own claim rather than incidentally, and both were restored to the same blob afterwards. The aria-label loop carries expect(labelled.length).toBeGreaterThan(0) so it cannot pass vacuously on a render that produced no labels.

The scope of that sweep, stated plainly so nobody reads it as repo-wide: visibleText()).not.toMatch(/orchestrator/i) is screen-scoped and fixture-scoped. It renders three states of OrchestratorsList against a mocked port with fixtures the test defines itself. It cannot see the runtime-status screen, the project tree, or anything arriving as server data. That limit is not theoretical: it is exactly why a leftover in the local-runtime dev mock survived it, found in review and fixed in the last commit here.

The saved-project proof, and its limit

Shown: all 44 project-format source files byte-identical to development; zero changed lines touching devices/, a path literal, or any I/O call; no persist middleware in the store, tabs never serialised, and nothing under the project-format modules mentioning tabs, so renaming a tab label cannot reach disk.

Not shown: this is a static invariant, not a round trip. Neither repo commits an on-disk project fixture, so no project was saved before the change and reopened after it. The argument is that the read/write code is byte-identical and therefore cannot behave differently, which is sound, but it is an argument about the code rather than an observation of a project. TC01 still needs doing by hand against a built editor, and so do TC05 and TC06. Neither is discharged here.

Gates

prettier and eslint clean in both repos, judged by exit code and the absence of an [error] line rather than by the last line of stdout; web tsc --noEmit -p tsconfig.app.json clean. Lint is scoped to non-test files because eslint.config.* globally ignores **/__tests__/** and **/*.test.{ts,tsx}, so an all-ignored file set exits 2.

openplc-web reports 11 @typescript-eslint/require-await warnings on the changed non-test files. They are pre-existing, proven rather than asserted: linting the same eight files in a checkout of origin/development, with the same node_modules, gives the identical 11. A twelfth entry is eslint reporting no matching configuration for configs/vite/local-runtime.ts, which appeared only because the last commit added that file to the changed set.

Shared surface

src/frontend/, src/middleware/shared/, src/backend/shared/ and src/__architecture__/ are byte-identical between the two repositories, so this ships as a coordinated pair on the same branch name (CON01, NFR05).

Identity was proven per commit and over the whole surface, not only over the files each commit touched, and compare-surfaces.py on clean git archive extracts of both HEADs reports 1114 files, 0 diffs across all five surfaces. Note five: CON01 lists four, but the gate also covers bare-metal-runtime, mapping editor resources/sources to web src/assets/firmware, 46 files, untouched here.

Four shared-surface test files differ between the repositories (new-uuid, save-actions, runtime-versions, native-pou-list). All four already differed on development, none is touched by this branch, and compare-surfaces.py excludes test files by design. The enforced surface is 1114 non-test files, not the 1403 a directory walk finds.

A property of the sync gate, worth knowing before you doubt your own diff

This pair tripped sync / Shared Surface Sync once, and the failure mode is structural rather than specific to this change, so it is written down here.

The job compares asymmetrically. It checks out refs/remotes/pull/<n>/merge, so its own side carries the current development. When the surfaces then differ, it falls back to iterating the sibling repo's open PRs and compares against each candidate's head — which may still sit on the old development. A feature branch that has fallen behind therefore can never match, however correct its own diff is.

And it fails looking exactly like a real divergence. The report is a list of files, and the first thing you see is the files you changed, because those genuinely differ from the sibling's development. The natural reading is "my mirror is broken". It is not.

Concretely, here: both development branches had advanced four commits while this was in flight, each taking the same DOPE-551 LSP fix. That fix touches 19 shared files, of which 12 are non-test. The log:

Found 6 difference(s) against web's development branch.
Found 10 open web PR(s) targeting 'development'.
Still 12 difference(s) with this PR:      <- the sibling PR, first in the list

The 6 are this PR's own changed non-test shared files. The 12 is precisely the non-test count of the LSP fix — the commits present on the merge-ref side and absent from the sibling's head. Nothing in the shared surface of this change was ever divergent: compare-surfaces.py run locally across both branch HEADs reported 0 diffs throughout.

The fix is a rebase on both sides, not an edit to any file. After git rebase origin/development in both repos the count is 1117 files, 0 diffs — 1117 rather than 1114 because the LSP fix added three non-test files to the surface.

So: if this gate fails and the list is your own files plus a suspiciously round extra count, check whether development moved under you before reading the diff as a defect. One rebase clears a stale base; a second would mean the diagnosis was wrong.

The commit counts differ, and that is correct

openplc-web has one more commit than openplc-editor. Phase 4 touched two web-only files, src/middleware/adapters/web/project-adapter.ts and .env.example, and a later fix touched configs/vite/local-runtime.ts; none of the three exists in openplc-editor. The asymmetry is entirely outside the shared surface, verified by set-differencing the two branches' changed-file lists. It is not a lost cherry-pick.

One commit in history is wrong, deliberately left there

3f1a937a9 in openplc-editor unintentionally reverted the Device Agent label: an edit to runtime-status/index.tsx was in flight in the working tree while its git add -A ran, so older content was staged. It shows three files where its web counterpart shows two. 546525cde restores it and says so in its message.

History was not rewritten, because the misleading commit had already been read and amending it would have hidden the event. The per-commit whole-surface check reports exactly one broken commit, that one, and shows the next resolving it. That check exists because the narrower touched-files check passed straight over it.

Note for openplc-editor reviewers

In the desktop editor the only user-visible change is the runtime-status label. The Edge Devices tab, its leaf and its whole screen are gated on hasOrchestratorDevices, which is false in EDITOR_CAPABILITIES (platform-capabilities.ts:146; web is true at :177), and project.tsx:429 gates on it. Everything else in this diff is invisible there, and is carried so the sync gate stays green.

That one field is reachable: InfoField renders its label unconditionally, and the field renders whenever no bootloader answers, which is every native install and every vPLC container. But its value is always the em-dash placeholder in that build, because hostInfoFromOrchestrator returns null when getOrchestratorHostInfo is undefined and the editor's adapter implements only listOrchestrators. So this is a label-only change in openplc-editor. Said explicitly because the first person to open the desktop editor after this ships would otherwise file a bug about an empty field that was equally empty before.

Out of scope

Back end routes, the database, permission keys, audit event keys, WebSocket topic names, the partner API, the paths the agent has already written on customer machines, and the Editor's on-disk project format. Per BR14 the six repositories ship together.

Before merge

Both risk assessments are at Pass 2, with section 9 recording what was actually built against what was planned. What remains is signatures: the technical reviewer (João Pereira) and the Project Manager. By those pages' own wording the signatures gate the merge to development and nothing else, which is why this PR is open.

Section 19 of the Requirements Gathering is deliberately still open: it is one document covering all six repositories, and it is completed once, after the rest land.


Jira: https://autonomylogic.atlassian.net/browse/EDGE-639
Mirror PR: https://github.com/Autonomy-Logic/openplc-web/pull/745 (EDGE-640, openplc-web)

🤖 Generated with Claude Code

https://claude.ai/code/session_01SEHN6E4oZKbi6ekw1da6A4

Summary by CodeRabbit

  • User Interface

    • Renamed device-related labels from “Orchestrators” to “Edge Devices” throughout navigation, breadcrumbs, headings, counts, actions, and empty/loading/error states.
    • Updated runtime status text to refer to the “Device Agent.”
    • Updated modal and instructional copy to use “vPLC” terminology.
  • Tests

    • Added comprehensive coverage verifying the updated Edge Devices and vPLC terminology across states, controls, accessibility labels, and navigation.

@coderabbitai

coderabbitai Bot commented Sep 9, 2026 •

Copy link
Copy Markdown
Contributor

Review Change StackReview Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Advanced

Run ID: 40e1f6ae-7d74-41b4-a646-cf16e6365ffb

📥 Commits

Reviewing files that changed from the base of the PR and between 2a8b9a2 and 746ffcb.

📒 Files selected for processing (2)
  • src/frontend/components/_organisms/explorer/project.tsx
  • src/middleware/shared/ports/types.ts

Included review availability: Your plan provides up to 2 included reviews per hour; 1 remains after this review.


Walkthrough

The PR replaces user-facing “orchestrator” terminology with “Edge Devices” and “vPLC” terminology across the device editor, runtime status, navigation, documentation, and tests.

Changes

Edge Device terminology

Layer / File(s) Summary
Editor labels and terminology tests
src/frontend/components/_features/[workspace]/editor/device/orchestrators/*
The device list uses “Edge Devices” and “vPLC” in headings, states, controls, counts, errors, and switch dialogs. Tests cover visible text and accessibility labels.
Runtime status label
src/frontend/components/_features/[workspace]/editor/device/runtime-status/*
The fallback runtime label now reads “Device Agent”.
Navigation and documentation terminology
src/frontend/components/_molecules/breadcrumbs/*, src/frontend/components/_molecules/plugin-stats-panel/index.tsx, src/frontend/components/_organisms/explorer/project.tsx, src/middleware/shared/ports/types.ts
Breadcrumbs, project-tree labels, documentation, and capability comments now refer to “Edge Devices”.

Priority: ⬇️ Low

Estimated code review effort: 2 (Simple) | ~15 minutes

Change: Other

Suggested reviewers: thiagoralves

Merge Risk: ⚪ Minimal · up to 746ff

This change updates user-facing terminology while preserving the existing routes, identifiers, and device-selection behavior.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the primary change: renaming Orchestrator terminology to Edge Device and vPLC terminology.
Description check ✅ Passed The description is detailed and covers the Jira reference, rationale, scope, terminology changes, verification results, out-of-scope items, pending manual checks, and merge prerequisites. It does not …
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 9…
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/edge-639/rename-device-vplc

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

A rabbit hops past Edge Devices bright
vPLCs count in morning light
Old words fade from every screen
Tests keep each label crisp and clean
Device Agent guards the scene

Comment @coderabbitai help to get the list of available commands.

@JoaoGSP JoaoGSP left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@Thiago-Pio-Autonomy
Thiago-Pio-Autonomy force-pushed the feat/edge-639/rename-device-vplc branch from ea4728b to 2a8b9a2 Compare September 14, 2026 17:21
Thiago-Pio-Autonomy and others added 6 commits September 16, 2026 12:15
…tree

"Orchestrator" is software infrastructure jargon that an automation
professional has to learn before they can use the feature, so the product
vocabulary moves to Device and vPLC across the ecosystem.

Inside the editor the word Device is already taken: the project tree's Device
node is the PLC target being programmed. The platform entity therefore reads
"Edge Devices" here, which is why this renames the leaf and its tab rather
than the branch above them.

Renames only what the user reads. The `devices/` directory on disk, the
`derivation: 'orchestrators'` key, the tab path and `leafLang` all stay, so
no project already saved on a customer machine opens differently and no
compatibility layer is needed.

The breadcrumbs fixture and the two comments that quote the old label follow,
so the tree, the trail and the code that describes them agree.

Shared surface: byte-identical with the sibling repository, verified with
git hash-object per file and compare-surfaces.py (1114 files, 0 diffs).

Refs EDGE-640.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SEHN6E4oZKbi6ekw1da6A4
Second half of the vocabulary change on this screen: the parent entity the
list holds is an Edge Device, and each entry under it is a vPLC.

Which word each string takes was decided from what the code does, not from
the string's wording. "No Edge Devices found." renders on
`orchestrators.length === 0` and the load error is set in the catch around
`listOrchestrators()`, so both describe the parent list and neither is about
the children. The count beside each row reads `orchestrator.devices.length`,
so it becomes vPLCs. The switch-confirmation modal is reached only from
`handleDeviceSelect` on a child row, so it becomes vPLC too.

"Failed to connect to runtime" keeps the word runtime: the OpenPLC runtime
running inside a vPLC is its own concept in the demand's glossary, not a
synonym for the container.

Renames only what the user reads. Every `orchestrator*` identifier, the port
call, the component and file names, the four DOM ids and the `[Orchestrators]`
console tag stay, because nothing persisted or transmitted may move.

Shared surface: byte-identical with the sibling repository, verified with
git hash-object and compare-surfaces.py (1114 files, 0 diffs).

Refs EDGE-640.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SEHN6E4oZKbi6ekw1da6A4
The daemon that runs on the customer machine and manages the vPLC containers
is the Device Agent, so the header field that names it says so.

This is the only user-visible change a desktop editor user gets. The Edge
Devices tab and its screen are gated on `hasOrchestratorDevices`, which is
false in EDITOR_CAPABILITIES, so the earlier phases are invisible there. This
field is not gated: `InfoField` always renders its label, and the field itself
renders whenever no bootloader answers, which is every native install and
every vPLC container.

The test assertion moved with the string rather than being relaxed. It is
still an exact `getByText`, which throws when the text is absent, and it was
checked against the old label to confirm it fails.

Shared surface: byte-identical with the sibling repository, verified with
git hash-object and compare-surfaces.py (1114 files, 0 diffs).

Refs EDGE-640.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SEHN6E4oZKbi6ekw1da6A4
Both described the screen the earlier phases renamed, so they sent a reader
looking for something that no longer exists. Updated as stale references, not
as quoted UI strings: that distinction is why they were held back until the
comment edits could land in one commit rather than scattered across phases.

Everything else keeping the word stays and is recorded in the FR14 ledger. In
particular the comments describing the orchestrator agent and the compile
pipeline are still accurate: the container, the written paths and the topic
names are all unchanged, so the prose matches the system as built.

Shared surface: byte-identical with the sibling repository.

Refs EDGE-640.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SEHN6E4oZKbi6ekw1da6A4
…s commit

The previous commit, "point two comments at the screen's current name",
unintentionally reverted the Phase 3 change: it shows three files where its
openplc-web counterpart shows two, and the third put
`label='Orchestrator agent'` back on the runtime status screen.

Cause: an edit to this file was in flight in the working tree while that
commit's `git add -A` ran, so the older content was staged and swept in. The
comment change the commit describes was correct and is untouched here.

Left in history rather than amended away, because the misleading commit had
already been read. This restores `label='Device Agent'`, which is what
openplc-web has carried since Phase 3, so the shared surface is byte-identical
again and `runtime-status.test.tsx` passes in both repositories.

Refs EDGE-640.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SEHN6E4oZKbi6ekw1da6A4
This screen carries most of the copy the rename changes and had no test at
all, so AC03 was something a person had to remember to check. Thirteen cases
pin it instead.

What is asserted is which noun each string uses, because that was a product
decision taken from what the code does and it is what a later refactor
silently undoes. The parent strings are the ones driven by
`orchestrators.length` and by the catch around `listOrchestrators()`; the
child strings are driven by `orchestrator.devices` and by the row click that
opens the switch modal. Swapping the two nouns is the specific mistake this
file exists to catch.

A final sweep asserts no visible /orchestrator/i in any of the three states
the screen can be in, and none in any aria-label, so a leftover string cannot
hide in a branch the other cases do not render.

Proven by negative control rather than by being green: reverting the vPLC
count failed four cases on "Unable to find 2 vPLCs", and reverting
"No Edge Devices found." failed the empty-state case on its own text plus the
sweep. Both restored to the same blob afterwards.

No snapshot and no change to the component. Shared surface, so byte-identical
in both repositories, and it runs under jest and vitest from the one file.

Refs EDGE-640.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SEHN6E4oZKbi6ekw1da6A4
@Thiago-Pio-Autonomy
Thiago-Pio-Autonomy force-pushed the feat/edge-639/rename-device-vplc branch from 2a8b9a2 to 746ffcb Compare September 16, 2026 15:15
@thiagoralves
thiagoralves merged commit 6ac081b into development Sep 18, 2026
11 checks passed
@thiagoralves
thiagoralves deleted the feat/edge-639/rename-device-vplc branch September 18, 2026 18:03
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.

3 participants