Merge development into main - #1010
Merged
Merged
Conversation
…ence automatically Buying happens in an external browser tab, and until now nothing brought the result back: the editor kept showing "Not licensed" until the user guessed they should click "Check again". The purchase flow ended in a dead end at its most important moment. buy() now opens the purchase page AND starts a purchase watch (deviceLicense.awaitingPurchase). While it runs, the existing refresh() executes every 20s — one Modbus read frame plus one HTTP round-trip — and on the first tick after the completion webhook lands, that same refresh() activates the licence and WRITES the blob to the device, no click required. The watch ends on the first licensed report (whoever produced it: the poll, a manual re-check, the connect flow), after a 30-tick / 10-minute budget, on "Stop waiting", or when the board is switched / disconnected (clearDeviceLicense). Badge behaviour while waiting: - reads "Waiting for purchase…" steadily — it outranks the isChecking flicker each tick would otherwise cause; - withdraws "Buy licence" — offering it mid-wait invites a double buy; - offers "Stop waiting". Ticks that would overlap a call still in flight (slow device, 30s HTTP timeout) are skipped instead of stacking a second call on the same link. The interval calls refresh through a ref: its identity follows the device port, and an interval keyed on it would reset the tick budget on every change. Companion change (autonomy-edge, EDGE-593): the /buy page now polls the same truth and shows "License issued" only when the webhook actually landed it. Verified: 7 new hook tests (watch start, no-URL no-watch, per-tick refresh, overlap skip, licensed ends it, tick budget, cancel), 6 new badge tests, 4 new slice tests; device-slice 127/127, badge 26/26, tsc --noEmit clean, eslint 0 errors (the 4 unbound-method warnings in use-device-license.ts pre-date this change, same count). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015uUH3ZL5ehreMUf2dtanWD
The format check runs prettier --check over src/**; the multiline className in the waiting-state badge was hand-wrapped differently than prettier wants it. No behavioural change — the badge suite still passes 26/26. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015uUH3ZL5ehreMUf2dtanWD
…very stop path Two fixes, one ownership rule: the emulator and the session it serves start and end together. 1. Stop actually stops the emulator (shared surface) The Stop button ended the debug session, logged "Simulator stopped." and left the emulator running. `stopSession()` deliberately does not stop it (a debug session is a consumer of the emulator, not its owner — see its docstring), and this button, which owns that job, never made the call. The avr8js loop kept re-scheduling itself and burning a core for the rest of the session, unreachable from the UI because the button had already flipped back to "Start". `workspace-activity-bar/default.tsx` is byte-identical with openplc-web and carries the same change there; the two must land together for the Shared Surface Sync gate. 2. Every stop path closes the session (main process) Stops were scattered, and two of them closed nothing: - `handleWindowReload` stopped the emulator but left the session open, so main went on holding a simulator session the reloaded renderer knew nothing about. - `handleSimulatorLoadFirmware`'s catch did neither. `loadAndRun` marks the emulator running before it finishes wiring, so a throw after that point leaked a running emulator with no session — and no button to reach it, because the renderer never learned it had started. All six sites now route through one `stopSimulator()` choke point that closes the session first, then stops the emulator. The load-failure path is the parity fix for openplc-web, where the worker already cleans up and reports 'stopped' when `loadAndRun` throws. Adds `simulator-session.handler.test.ts`: 5 cases over ordering, the non-simulator link left alone, the reload path and the throw path. The two covering new behaviour fail without this change. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Review fixes on the post-purchase watch: - reset the watch on EVERY licence reset path: a real board change and project close previously left it polling (and potentially writing a licence) against the next board's package id. One resetDeviceLicense helper now serves all three reset paths so a fourth cannot drift. - replace the 30-tick budget with an absolute deadline persisted in the store (awaitingPurchaseUntil, PURCHASE_WATCH_WINDOW_MS): a remount resumes the SAME window, an overlap-skipped tick costs nothing, and the state is inspectable. - let a check-failed report outrank the "Waiting for purchase..." badge label so a dead link cannot hide behind a calm wait for ten minutes; the failure label holds steady across poll ticks. - start the watch only when openExternalLink actually opened the page, and fire the first check immediately instead of at t+20s. - give the watch a single owner (the board screen passes ownsWatch) so the useDeviceConnect instance no longer double-polls the same flag. - product-neutral panel copy: "OpenPLC checks periodically...". - slice tests arm the watch before asserting the resets, so the two reset assertions can actually fail (verified by mutation). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015uUH3ZL5ehreMUf2dtanWD
…nt claims Addresses the review on #1009 / #670. Stop the emulator from a `finally` (shared surface, mirrors openplc-web). Sequencing `simulator.stop()` after a plain `await debugSession.stopSession()` left the emulator running whenever the teardown rejected — control jumped to the catch, which only logs. Reproduced on web by injecting a rejecting `debugger.disconnect`: emulator still running, debug panel up on frozen values, `simulatorRunning` stuck true, every retry failing identically. Nothing settled, which is worse than the bug this branch fixes. The nearest trigger is a throwing `onDisconnected` subscriber, which this repo's debugger adapter re-invokes inside its own catch — the second throw escapes. Clear `debugSessionRidesDeviceRef` on the manual Stop path (shared surface). `stopSession()` hides the debugger first, so the drop handler's `isDebuggerVisible` gate returns before it reaches the reset. Fix the load-firmware test so it exercises the leak its comment describes. It threw on `fs.readFile`, which runs BEFORE `loadAndRun` — so the emulator was never marked running and the assertion passed only because `stopSimulator()` is unconditional. It would have stayed green if the real post-`loadAndRun` leak regressed. `fs/promises` is now stubbed so the read succeeds and `loadAndRun` throws, putting the throw where the leak was; the read-failure case is kept as a separate test, since the catch cannot tell the two apart. Both fail with the `stopSimulator()` call removed. 303 suites / 6404 tests pass. The modbus-rtu-client flake and the jest worker teardown warning both reproduce on a clean `development`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…se-poll feat(licensing): watch for the purchase after buy() and write the licence automatically
…d-session fix(simulator): stop the emulator on Stop, and close its session on every stop path
Contributor
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. 🗂️ Base branches to auto review (1)
Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
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. Comment |
Thiago-Pio-Autonomy
approved these changes
Aug 12, 2026
Gustavohsdp
approved these changes
Aug 12, 2026
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.
Promotes
developmenttomain, paired with openplc-web #671.What this ships
Simulator fix (#1009) — Stop never actually stopped the emulator:
stopSession()deliberately doesn't own it and the button never made the call, so the avr8js loop kept re-scheduling itself and burning a core, unreachable once the button flipped back to "Start". Also routes every emulator stop path through one choke point that closes the session first, fixing two leaks (window reload, and a start that threw afterloadAndRun).License watch (#1008) — rides along; already reviewed, merged and green on
development.Notes
mainhad no unique non-merge content, so this is a clean promotion.APP_VERSIONandpackage.jsonstay at4.2.11. This promotion therefore does not produce a desktop release — the "Build and Release" workflow fires on avX.Y.Ztag, andv4.2.11already exists. Deliberate — requested explicitly.workspace-activity-bar/default.tsxis byte-identical with openplc-web migrate(step-18): Atoms batch 1 — shared identical components #671.🤖 Generated with Claude Code