EDGE-639: rename Orchestrator to Edge Device and its children to vPLC - #1096
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Advanced Run ID: 📒 Files selected for processing (2)
Included review availability: Your plan provides up to 2 included reviews per hour; 1 remains after this review. WalkthroughThe PR replaces user-facing “orchestrator” terminology with “Edge Devices” and “vPLC” terminology across the device editor, runtime status, navigation, documentation, and tests. ChangesEdge Device terminology
Priority: ⬇️ Low Estimated code review effort: 2 (Simple) | ~15 minutes Change: Other Suggested reviewers: Merge Risk: ⚪ Minimal · up to This change updates user-facing terminology while preserving the existing routes, identifiers, and device-selection behavior. 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. A rabbit hops past Edge Devices bright Comment |
a929a29 to
ea4728b
Compare
ea4728b to
2a8b9a2
Compare
…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
2a8b9a2 to
746ffcb
Compare
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
aria-label{n} device{s}{n}vPLC{s}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 onorchestrators.length === 0, and the load error is set in thecatcharoundlistOrchestrators(). Both describe the parent list, not the children, so both are Edge Device rather than vPLC. The per-row count readsorchestrator.devices.lengthand is therefore vPLCs; the switch modal is reached only fromhandleDeviceSelecton 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.
orchestrators-list.tsxis the vPLC. ItsshowSwitchConfirmModalandpendingDeviceSwitchare localuseState, set only byhandleDeviceSelectfrom a child row. It becomes Switch vPLC._organisms/modals/confirm-device-switch-modal.tsxkeeps "Switch Device", and that is deliberate. It is opened only fromdevice/configuration/board.tsx:299with anewBoard/formattedNewBoardpayload, so its Device is the PLC target being programmed. Renaming it would have been the exact mistakeBR06exists to prevent.What deliberately did not change
devices/directory anddevices/configuration.json,pin-mapping.json,servers/*.json,remote/*.jsonall 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 keepsNFR01safe.RSK01:compiler-platform-port.ts(10),library-build-port.ts(11),library-build-orchestrator.ts(3), plus 45 more acrossbackend/sharedand the language service. A global find-and-replace corrupts the compile pipeline.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.tsxhad no test in either repository, soAC03was 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:
{n} vPLC->{n} deviceUnable to find an element with the text: 2 vPLCsNo Edge Devices found.->No orchestrators found./orchestrator/isweepEach 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 ofOrchestratorsListagainst 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 touchingdevices/, 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.
TC01still needs doing by hand against a built editor, and so doTC05andTC06. 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; webtsc --noEmit -p tsconfig.app.jsonclean. Lint is scoped to non-test files becauseeslint.config.*globally ignores**/__tests__/**and**/*.test.{ts,tsx}, so an all-ignored file set exits 2.openplc-web reports 11
@typescript-eslint/require-awaitwarnings on the changed non-test files. They are pre-existing, proven rather than asserted: linting the same eight files in a checkout oforigin/development, with the samenode_modules, gives the identical 11. A twelfth entry is eslint reporting no matching configuration forconfigs/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/andsrc/__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.pyon cleangit archiveextracts of both HEADs reports 1114 files, 0 diffs across all five surfaces. Note five:CON01lists four, but the gate also coversbare-metal-runtime, mapping editorresources/sourcesto websrc/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 ondevelopment, none is touched by this branch, andcompare-surfaces.pyexcludes 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 Synconce, 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 currentdevelopment. 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 olddevelopment. 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
developmentbranches 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: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.pyrun 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/developmentin 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
developmentmoved 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.tsand.env.example, and a later fix touchedconfigs/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
3f1a937a9in openplc-editor unintentionally reverted theDevice Agentlabel: an edit toruntime-status/index.tsxwas in flight in the working tree while itsgit add -Aran, so older content was staged. It shows three files where its web counterpart shows two.546525cderestores 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 isfalseinEDITOR_CAPABILITIES(platform-capabilities.ts:146; web istrueat:177), andproject.tsx:429gates on it. Everything else in this diff is invisible there, and is carried so the sync gate stays green.That one field is reachable:
InfoFieldrenders 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, becausehostInfoFromOrchestratorreturns null whengetOrchestratorHostInfois undefined and the editor's adapter implements onlylistOrchestrators. 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
BR14the 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
developmentand 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
Tests