Skip to content

fix(runtime-v4): switch-in-STOP warning, mid-transition gating, and Connect button regressions - #996

Merged
JoaoGSP merged 7 commits into
developmentfrom
feat/runtime-v4-switch-stop-ux
Aug 7, 2026
Merged

fix(runtime-v4): switch-in-STOP warning, mid-transition gating, and Connect button regressions#996
JoaoGSP merged 7 commits into
developmentfrom
feat/runtime-v4-switch-stop-ux

Conversation

@thiagoralves

@thiagoralves thiagoralves commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Run/stop UX fixes for Runtime v4 targets, plus two UI regressions found while testing, rebased cleanly onto development.

Why this replaces feature/runtime-run-stop-state

That branch had feat/always-on-debugger merged into it, so merging it would have dragged 74 commits / 126 files / +13,576 lines of unreviewed licensing work into development as a side effect. That branch has no PR of its own.

Everything else on it turned out to be already in development via #980: refusedBySwitch, switchPosition, FC 0x4b, warnSwitchInStop, debuggerTransports, acquireDebugChannel, the Baremetal run/stop machine and the MBAP floor fix. Every remaining firmware difference belongs to always-on-debugger (license_blob.h, license_gate.h, license_store.h).

So this is the genuine remainder, cherry-picked onto development.

1. A mode switch in STOP is a warning, not a failed upload

Uploading with the switch in STOP printed a successful compile followed by Failed to start PLC / Failed to upload to runtime / Stopping compilation process. The program reached the device and compiled there; only the start was declined, which is what the switch is for.

startPlcAfterBuild now treats ERROR_SWITCH_STOP as its own outcome and reports it as a warning"Program uploaded. The PLC was not started because the mode switch is in STOP — move it to RUN to start." — without retrying, since nothing changes until someone moves the switch. deployRuntimeProgram maps it to UPLOADED_NOT_STARTED, which counts as a successful upload.

"Did the program reach the device?" is now one shared predicate, deployReachedDevice(), instead of outcome === 'STARTED' re-derived in two places here and a third in web.

2. Run/stop blocked while the runtime is mid-transition

TRANSITIONING means the runtime answers COMMAND:BUSY to everything but PING and STATUS, and the icon is drawn from a state about to change. The button goes inert with the tooltip "PLC is changing state...", folded into development's existing plcControlBlocked pair beside the not-connected and unsupported-target cases. handlePlcControl carries the same guard, since status is polled and a render can be one interval stale.

3. Connect button font, and duplicate status labels

Both regressions arrived with 231e11d9b in #980, not from this work.

The font. cn() runs twMerge, which does not know this project's custom cp-* font scale — it read text-cp-sm as conflicting with text-white and dropped the size, leaving the button at the browser default. main escapes this only because it passes a plain string rather than calling cn(). Fixed in cn() itself via extendTailwindMerge, declaring cp-xs/cp-sm/cp-base as font sizes, so the button's original classes render correctly again. This silently repairs every other call site with the same pattern — a failure mode with no warning and no build error, just the wrong size.

The duplicates. The button label is the status: "Disconnect" only renders while connected. The green ● Connected inside the button and the grey Connected from DeviceConnectedIndicator (used at two call sites) both said it again, so all three are gone. PLC: RUNNING stays — that is the program's state, which the button says nothing about — as does ● Connection failed, since the button reads "Connect" whether the last attempt failed or never happened.

Verification

  • Editor: 15/15 jest in the shared-library suites, 8/8 in the connect-button suite, tsc --noEmit clean apart from 4 pre-existing ReactFlow errors in the graphical editor.
  • Runtime behaviour on an SLM-RP4 (openplc-runtime#162): 135 switch edges → 19 transitions, 19 landings, 5 reconciliations, 0 wedges.
  • Button appearance confirmed against main on hardware.

Paired with openplc-web#655

These 8 files are byte-identical across both branches:

start-plc-after-build.ts · deploy-runtime-program.ts · start-plc-after-build.test.ts · board.tsx · device-connect-button/index.tsx · device-connect-button.test.tsx · workspace-activity-bar/default.tsx · cn.ts

Only the platform adapters differ, which is expected.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Uploads now correctly report when a program reaches the device but cannot start because the hardware mode switch is in STOP.
    • The application provides a warning and avoids unnecessary retries in this situation.
    • PLC controls are disabled while a transition is in progress, preventing duplicate commands and commands during state changes.
  • UI Improvements

    • Simplified connection status displays and removed redundant “Connected” indicators.
    • Improved handling of custom font-size styling across the interface.

…pload

Uploading with the hardware mode switch in STOP ended with:

  Compilation completed successfully (exit code: 0).
  Failed to start PLC: START:ERROR_SWITCH_STOP
  Failed to upload to runtime.
  Stopping compilation process.

All three lines after the first are wrong. The program did reach the device and
compiled there; the runtime simply declined to start it, which is what a mode
switch in STOP is for. Leaving the switch there while uploading is a normal thing
to do, so the message should not send anyone looking for a problem.

startPlcAfterBuild now recognises ERROR_SWITCH_STOP as its own outcome and
explains it as a warning: "Program uploaded. The PLC was not started because the
mode switch is in STOP -- move it to RUN to start." It is not retried either;
nothing changes until a human moves the switch, so the 5 s BUSY loop had no
business spinning on it.

deployRuntimeProgram maps that to UPLOADED_NOT_STARTED, and both platform
adapters treat it as a successful upload. Whether an outcome means "the program
reached the device" now lives in one shared predicate, deployReachedDevice(),
rather than being re-derived as `outcome === 'STARTED'` in each adapter -- the
editor had it in two places and web in a third, which is how they would drift.

Not addressed here, but noticed: START_TIMEOUT still maps to a failed upload, so
a runtime that stays BUSY past the deadline reports "Failed to upload to runtime"
after logging a warning that says otherwise. Same shape of bug, different trigger.
TRANSITIONING means a start or stop is already underway: the runtime answers
COMMAND:BUSY to everything except PING and STATUS, and the state it will settle
on is not decided yet — so the icon is drawn from a state that is about to
change and a click cannot do what it appears to.

Folded into the existing plcControlBlocked / plcControlBlockedReason pair rather
than added as a second mechanism, so the tooltip explains this the same way it
explains a missing connection: 'PLC is changing state...'.

handlePlcControl carries the same guard. Status arrives by poll, so a render can
be up to one interval stale; the guard closes the window where the button still
looks live and covers callers that are not the click.
@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: c020c181-b32f-4422-b297-e58069cf84bc

📥 Commits

Reviewing files that changed from the base of the PR and between 50219cf and ecd880b.

📒 Files selected for processing (1)
  • src/frontend/components/_organisms/workspace-activity-bar/default.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/frontend/components/_organisms/workspace-activity-bar/default.tsx

Walkthrough

The deployment flow distinguishes uploaded programs that did not start because the PLC switch is in STOP. Compiler upload checks accept both started and uploaded outcomes. PLC controls are disabled during transitions. Connection status and custom font class merging are updated.

Changes

PLC deployment and control flow

Layer / File(s) Summary
STOP-switch start outcome
src/backend/shared/library/start-plc-after-build.ts, src/backend/shared/library/__tests__/start-plc-after-build.test.ts
ERROR_SWITCH_STOP returns SWITCH_IN_STOP, logs a warning, and does not retry.
Uploaded-but-not-started deployment outcome
src/backend/shared/library/deploy-runtime-program.ts, src/backend/shared/library/__tests__/deploy-runtime-program.test.ts
Adds UPLOADED_NOT_STARTED and deployReachedDevice. A STOP-switch refusal after upload returns the new outcome.
Compiler upload success classification
src/backend/editor/compiler/editor-compiler-platform-port.ts
Runtime v3 and v4 uploads use deployReachedDevice.
PLC transition control guard
src/frontend/components/_organisms/workspace-activity-bar/default.tsx
PLC controls are disabled during TRANSITIONING and ignore duplicate commands.
Simplified connection status display
src/frontend/components/_features/[workspace]/editor/device/configuration/board.tsx, src/frontend/components/_molecules/device-connect-button/index.tsx, src/frontend/components/_molecules/device-connect-button/__tests__/device-connect-button.test.tsx
Connection controls no longer show a separate Connected indicator.
Custom font class merging
src/frontend/utils/cn.ts, src/frontend/utils/__tests__/cn.test.ts
The cn helper merges configured cp-* font-size classes with conflict handling covered by tests.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Compiler
  participant deployRuntimeProgram
  participant startPlcAfterBuild
  participant PLC
  Compiler->>deployRuntimeProgram: deploy runtime program
  deployRuntimeProgram->>startPlcAfterBuild: start uploaded program
  startPlcAfterBuild->>PLC: send start request
  PLC-->>startPlcAfterBuild: return ERROR_SWITCH_STOP
  startPlcAfterBuild-->>deployRuntimeProgram: return SWITCH_IN_STOP
  deployRuntimeProgram-->>Compiler: return UPLOADED_NOT_STARTED
  Compiler->>Compiler: accept with deployReachedDevice
Loading

Possibly related PRs

Suggested labels: bug

Poem

A rabbit uploads code with care,
The PLC waits in STOP mode there.
“Reached” is true though start must wait,
Controls pause during state change.
Short status text and fonts align. 🐇

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the Runtime v4 STOP warning, transition gating, and Connect button fixes.
Description check ✅ Passed The description explains the changes and verification results, but it omits the template checklist and issue or Jira references.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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/runtime-v4-switch-stop-ux

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

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 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.

Inline comments:
In `@src/frontend/components/_organisms/workspace-activity-bar/default.tsx`:
- Around line 115-121: Update the plcControlBlockedReason conditional in the
workspace activity bar so deviceConnectionStatus !== 'connected' is checked
before plcTransitioning, ensuring disconnected targets display 'Connect to the
target first' even during transitions while preserving the unsupported-target
reason.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 76c2d255-1aa8-4cef-a18c-5c85b371b749

📥 Commits

Reviewing files that changed from the base of the PR and between 05dc8d7 and 20ff67e.

📒 Files selected for processing (5)
  • src/backend/editor/compiler/editor-compiler-platform-port.ts
  • src/backend/shared/library/__tests__/start-plc-after-build.test.ts
  • src/backend/shared/library/deploy-runtime-program.ts
  • src/backend/shared/library/start-plc-after-build.ts
  • src/frontend/components/_organisms/workspace-activity-bar/default.tsx

Comment thread src/frontend/components/_organisms/workspace-activity-bar/default.tsx Outdated
…icate status

The button rendered noticeably larger and heavier than the buttons beside it. The
cause is cn(): it runs twMerge, which does not know this project's custom cp-*
font scale, so it read `text-cp-sm` as conflicting with `text-white` and dropped
the size class entirely. The button fell back to the browser default -- larger
than the 10px intended AND larger than its 14px neighbours. Sibling brand buttons
on this screen escape it only because they pass a plain string rather than cn().

Now h-8/text-sm, matching those siblings, and a size twMerge understands.

Also removes both redundant "Connected" labels. The button label already is the
status: "Disconnect" can only be shown while connected, "Connect" only while
disconnected, so the green "● Connected" beside it and the grey "Connected" after
it said the same thing twice more. "PLC: RUNNING" stays -- that is the program's
state, which the button says nothing about. "● Connection failed" stays too: the
button reads "Connect" whether the last attempt failed or never happened, so that
one carries information the label does not.
My previous attempt made the Connect button WORSE, not better. The diagnosis was
right -- twMerge silently drops `text-cp-sm` because it cannot tell a custom size
from a text colour, so `text-cp-sm text-white` keeps only the colour -- but the fix
was wrong: I moved the button to text-sm (14px) when main renders it at
text-cp-sm (10px). It stayed too big, just for a new reason.

main gets away with the same class string only because it passes a plain string
instead of calling cn(). So the bug is in cn(), not in any one button:
extendTailwindMerge now declares cp-xs / cp-sm / cp-base as font sizes, and
`text-cp-sm text-white` keeps both. Size-versus-size still dedupes correctly.

The buttons are back to main's exact classes, which now render as intended. This
also silently repairs every other call site that puts a cp-* size and a text colour
through cn() -- there is no warning when it happens, only a wrong size.
@thiagoralves thiagoralves changed the title fix(runtime-v4): switch-in-STOP is a warning, and block run/stop mid-transition fix(runtime-v4): switch-in-STOP warning, mid-transition gating, and Connect button regressions Aug 6, 2026
Comment thread src/backend/shared/library/deploy-runtime-program.ts
Comment thread src/frontend/utils/cn.ts
thiagoralves and others added 3 commits August 7, 2026 11:03
Both files this PR touches sit under an enforced 100% functions/lines
threshold, and both shipped a change no test executed.

`deployReachedDevice` had 0% function coverage — nothing called it. It now
has direct assertions for all three outcome classes, driven off a
`Record<DeployRuntimeProgramOutcome, boolean>` so a new outcome fails to
compile until someone decides which side of "did the program reach the
device?" it falls on. The `SWITCH_IN_STOP -> UPLOADED_NOT_STARTED` mapping
was statement-covered but never asserted; a `deployRuntimeProgram` scenario
now scripts `START:ERROR_SWITCH_STOP` and checks the outcome, that start is
asked exactly once, and that the refusal logs as a warning and not an error.

`cn()`'s font-scale fix shipped with `cn.test.ts` untouched. The suite now
pins the exact regression — `cn('text-cp-sm', 'text-white')` keeps both
classes, `cn('text-cp-xs', 'text-cp-base')` last-wins — and reads the scale
out of tailwind.config.ts rather than restating it, so the "keep this list
in step" comment on `cn()` is enforced instead of hoped for. Verified it
fails against plain twMerge.

Both files stay byte-identical with openplc-web#655.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The reason chain asked about TRANSITIONING before the connection, so a
disconnected target whose last polled `plcStatus` was TRANSITIONING got
"PLC is changing state..." — a state we last saw, not the reason the button
is inert. `plcStatus` is polled and survives the drop, so the stale value
outlives the session that produced it. Reordering to match
`plcControlBlocked`'s own order — unsupported, then no session, then a
transition in flight — makes the tail unreachable except for the one reason
that is left.

Also unwraps `plcControlBlocked` onto one line: at 118 chars it fits inside
Prettier's 120, and the manual wrap has been failing the format gate (which
`sync` and `complete-build` both hang off) since 20ff67e.

Found by CodeRabbit on #996. Mirrored byte-identically in openplc-web#655.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@JoaoGSP
JoaoGSP self-requested a review August 7, 2026 15:36

@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

@JoaoGSP
JoaoGSP merged commit 6aab007 into development Aug 7, 2026
13 checks passed
@JoaoGSP
JoaoGSP deleted the feat/runtime-v4-switch-stop-ux branch August 7, 2026 15:37
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.

2 participants