Skip to content

feat(licensing): watch for the purchase after buy() and write the licence automatically - #1008

Merged
marconetsf merged 3 commits into
developmentfrom
feat/post-purchase-license-poll
Aug 12, 2026
Merged

feat(licensing): watch for the purchase after buy() and write the licence automatically#1008
marconetsf merged 3 commits into
developmentfrom
feat/post-purchase-license-poll

Conversation

@marconetsf

@marconetsf marconetsf commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Problem

Buying happens in an external browser tab, and nothing brought the result back: after clicking Buy licence 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.

What changed

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 + one HTTP round-trip. 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).
  • 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.

Badge 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".

Files

  • store/slices/device/{slice,types}.tsawaitingPurchase + setAwaitingPurchase; clearDeviceLicense also ends the watch
  • hooks/use-device-license.ts — the watch effect, buy() starting it, cancelPurchaseWatch
  • device-license-status.tsx + board.tsx — badge states and wiring

Companion PR in autonomy-edge (#358, EDGE-593): the /buy page polls the same truth and shows "License issued" only when the webhook actually landed it.

Verification

  • 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)

🤖 Generated with Claude Code

https://claude.ai/code/session_015uUH3ZL5ehreMUf2dtanWD

Summary by CodeRabbit

  • New Features

    • Added purchase monitoring after opening a device licence purchase page.
    • Automatically checks for licence activation and stops when activation is detected.
    • Added a clear “Waiting for purchase…” status with an option to stop waiting.
    • Purchase actions are hidden while monitoring is active.
  • Bug Fixes

    • Prevented duplicate purchase checks and ensured licensing status displays consistently during the purchase process.
    • Monitoring now ends automatically after a defined period if activation is not detected.

…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
@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

The device license flow now stores a purchase deadline, polls for licensing completion, supports cancellation, and updates the board UI with waiting status and automatic license-writing messaging.

Changes

Device license purchase watch

Layer / File(s) Summary
License state contract and lifecycle
src/frontend/store/slices/device/types.ts, src/frontend/store/slices/device/slice.ts, src/frontend/store/__tests__/device-slice.test.ts
The store records a 10-minute purchase-watch deadline. License resets and board or device-definition changes clear the deadline.
Purchase watch polling and cancellation
src/frontend/hooks/use-device-license.ts, src/frontend/hooks/__tests__/use-device-license.test.ts
useDeviceLicense starts polling after a successful purchase-link open. It prevents overlapping checks and stops on licensing, cancellation, or deadline expiry.
Purchase-watch status UI
src/frontend/components/_features/[workspace]/editor/device/configuration/components/device-license-status.tsx, src/frontend/components/_features/[workspace]/editor/device/configuration/board.tsx, src/frontend/components/_features/[workspace]/editor/device/configuration/__tests__/device-license-status.test.tsx
DeviceLicenseStatus shows waiting status, hides purchase actions, explains automatic license writing, and provides a “Stop waiting” action.

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

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant DeviceLicenseStatus
  participant useDeviceLicense
  participant DeviceStore
  participant LicensingAPI
  User->>DeviceLicenseStatus: Select purchase
  DeviceLicenseStatus->>useDeviceLicense: Open purchase URL
  useDeviceLicense->>DeviceStore: Set purchase-watch deadline
  loop Until licensed, cancelled, or deadline expiry
    useDeviceLicense->>LicensingAPI: Refresh license
    LicensingAPI-->>useDeviceLicense: License report
  end
  useDeviceLicense->>DeviceStore: Clear watch when licensed
  User->>DeviceLicenseStatus: Stop waiting
  DeviceLicenseStatus->>useDeviceLicense: Cancel purchase watch
Loading

Possibly related PRs

Suggested reviewers: thiagoralves

Poem

A rabbit starts the purchase watch,
And checks the license on schedule.
“Waiting,” says the badge bright,
Then writes the license when it is right.
A stop command ends the watch.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main licensing change: automatically watching for purchase completion and writing the licence.
Description check ✅ Passed The description clearly covers the problem, changes, affected files, companion work, and verification, but it omits the template headings and DOD checklist.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
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 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/post-purchase-license-poll

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: 5

🤖 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/hooks/__tests__/use-device-license.test.ts`:
- Around line 10-19: Remove the fixture’s type assertions by defining explicit
MockState and license-state types, then type mockUseOpenPLCStore as a Jest mock
rather than casting through unknown. Initialize BOARD with all required
BoardInfo fields—compiler, core, preview, and specs—and update setLicenseState
to use the typed value directly instead of an as object assertion.

In `@src/frontend/hooks/use-device-license.ts`:
- Around line 206-210: Update the purchase-opening flow in the hook containing
setAwaitingPurchase: synchronously guard with a purchaseOpeningRef before
awaiting system.openExternalLink, return early when that ref is set or the store
already has an active purchase watch, and clear the ref in a finally block while
preserving the existing awaitingPurchase update.
- Around line 161-162: Update refreshRef in the useDeviceLicense hook by moving
refreshRef.current = refresh into a committed useEffect, ensuring the interval
only observes refresh from committed renders while preserving the existing ref
usage.
- Line 175: Update the polling invocation in the effect around
refreshRef.current() to attach a rejection handler for promises rejected outside
run(). Preserve the existing fire-and-forget behavior while ensuring rejected
refresh promises are handled.

In `@src/frontend/store/slices/device/slice.ts`:
- Around line 613-621: Reset deviceLicense.awaitingPurchase to false in
clearDeviceDefinitions and the changed-board reset branch of setDeviceBoard,
matching clearDeviceLicense. Ensure every direct license-reset path clears the
purchase watch while preserving the existing phase and report resets.
🪄 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: 0ff0be0b-76b1-455a-b04b-d876d07e9853

📥 Commits

Reviewing files that changed from the base of the PR and between 32e46b8 and 40e01cc.

📒 Files selected for processing (8)
  • src/frontend/components/_features/[workspace]/editor/device/configuration/__tests__/device-license-status.test.tsx
  • src/frontend/components/_features/[workspace]/editor/device/configuration/board.tsx
  • src/frontend/components/_features/[workspace]/editor/device/configuration/components/device-license-status.tsx
  • src/frontend/hooks/__tests__/use-device-license.test.ts
  • src/frontend/hooks/use-device-license.ts
  • src/frontend/store/__tests__/device-slice.test.ts
  • src/frontend/store/slices/device/slice.ts
  • src/frontend/store/slices/device/types.ts

Comment thread src/frontend/hooks/__tests__/use-device-license.test.ts Outdated
Comment thread src/frontend/hooks/use-device-license.ts
Comment thread src/frontend/hooks/use-device-license.ts
Comment thread src/frontend/hooks/use-device-license.ts Outdated
Comment thread src/frontend/store/slices/device/slice.ts Outdated
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

@JulioSergioFS JulioSergioFS 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.

PR Review — openplc-editor #1008

Verdict: Request changes

The dead end this closes is real, and the design decisions are mostly the right ones for the right reasons — ending the watch on the report rather than on the poll's own return value, calling refresh through a ref so the tick budget survives a port change, letting the store action stay dumb while the hook owns the lifecycle. Those are the non-obvious calls and they were all made correctly.

What blocks is finding 1: the watch does not end when the board is switched or the project is closed, contrary to what the description says, and in that state it keeps running licensing sequences — including potential device writes — against a board the user never asked to buy for, with no UI left to stop it. The tests that would have caught it were updated in a way that guarantees they can't.

What I verified against the code

  • clearDeviceLicense has exactly one call site, use-device-connect.ts:182, on deliberate Disconnect. A link that merely drops does not go through it, by design (clearDeviceConnection's comment).
  • Check again is already disabled={isChecking}, so the manual-vs-poll race against main's deviceLicenseSequenceInFlight guard (which returns check-failed: "A license check is already running on this device") is closed at the button. Good — that would otherwise have clobbered the last good report with a spurious failure.
  • The overlap guard reads live state, not a stale closure. useOpenPLCStore.getState().deviceLicense.phase inside the interval is correct; a captured phase would have been permanently stale given the effect's deps.
  • startCheck() is synchronous before run()'s first await, so the guard actually latches within the same timer batch. This matters for finding 5.
  • The ref rationale holds. refreshrun depends on device.readLicense/refreshLicense and target, both of which follow the port and the board, so an interval keyed on refresh really would rebuild and reset ticks on every port change.
  • The per-tick cost is larger than the description says — see finding 9.
  • The tick-budget arithmetic matches the test. ticks += 1 then if (ticks > 30) means ticks 1–30 refresh and tick 31 closes the watch; the test's toHaveBeenCalledTimes(MAX_TICKS) is consistent.

Strengths

  • Ending the watch on the report, not on the poll's return value. This is the seam that lets a manual "Check again" or the connect flow conclude the purchase, and it is the difference between a poll and a state machine. The comment says exactly this, in the right place.
  • The badge-label precedence is argued and tested, not asserted. "Every tick flips isChecking, so alternating labels read as flapping when it is one continuous wait" is a real observation about a real artefact, and there is a test named after it.
  • Withdrawing "Buy licence" mid-wait. The double-purchase risk is the expensive failure in this flow, and it is handled — with a test.
  • The slice action is deliberately dumb, and there is a test pinning that landing an unlicensed report does not end the watch. That test is the one that stops a future refactor from making the first poll tick kill the watch it serves.
  • The store comment on clearDeviceLicense states the intent ("a watch for a device that is no longer there would keep hitting it over a dead link") — which is exactly right, and exactly why finding 1 matters.

Findings

1. (High) The watch survives a board switch and a project close, and keeps running licensing sequences against the new board

clearDeviceLicense was updated to clear awaitingPurchase (slice.ts:621). But it is not the path a board switch or a project close takes. Both reset the licence inline and were not updated:

  • setDeviceBoardslice.ts:417-418 sets deviceLicense.phase = 'idle' and report = null when the board actually changes. awaitingPurchase is untouched.
  • clearDeviceDefinitionsslice.ts:152-153, same two lines, same omission.

So: start a watch, switch the board (or close the project). The resulting state is { phase: 'idle', report: null, awaitingPurchase: true }. From there:

  • DeviceLicenseStatus returns null because report is null — the badge disappears, and with it the "Stop waiting" button. The user has no way to cancel a watch they can no longer see.
  • The interval in useDeviceLicense keys only on awaitingPurchase (use-device-license.ts:164, 178), so it keeps ticking, unaffected.
  • refreshRef.current has meanwhile followed boardInfotarget → the new board's packageId (use-device-license.ts:161-162). Every 20 seconds for the next 10 minutes, the editor runs handleDeviceRefreshLicense for the new VPP against the connected device: read the board-id anchor, ask the backend for entitlement, and — if one exists — write a licence blob to the device and read it back.

That last part is the serious half. The user switched boards; nothing they did asked for a licensing write on the new one, and there is no visible affordance telling them it is happening or letting them stop it. It ends only at tick 31.

The description states: "The watch ends … when the board is switched / disconnected (clearDeviceLicense)." The parenthetical is the bug — board switch does not go through clearDeviceLicense.

Fix: clear awaitingPurchase in setDeviceBoard's change branch and in clearDeviceDefinitions. Better, since this is the third copy of the same three-line reset: extract a resetDeviceLicense(deviceLicense) helper in the slice so a fourth reset path can't drift again — that drift is precisely what happened here.

2. (High) The two slice tests that cover those paths cannot fail

Both were touched by this PR, and both were made to assert awaitingPurchase: false without ever setting it to true:

it('clearDeviceBoard change drops the licence …')
  setDeviceLicenseReport(LICENSED)      // never setAwaitingPurchase(true)
  setDeviceBoard('Raspberry Pi 4')
  expect(deviceLicense).toEqual({ phase:'idle', report:null, awaitingPurchase:false })

The false they assert is the slice's initial value. Same for the clearDeviceDefinitions case. Meanwhile the one path that was fixed got a purpose-built test that does set the flag first ("clearDeviceLicense ends the watch").

This is worth calling out separately from finding 1, because the test edits look like coverage — a reviewer scanning the diff sees awaitingPurchase: false asserted on all three reset paths and concludes all three are handled. Whatever the fix, both tests need setAwaitingPurchase(true) before the act, so the assertion is about the reset rather than about the default.

3. (Medium) The badge hides a failing check for the full ten minutes

const badgeLabel = awaitingPurchase ? 'Waiting for purchase…' : isChecking ? 'Checking licence…' : label

awaitingPurchase outranks everything, including check-failed. And negative && !awaitingPurchase strips the dashed underline, which is the only affordance saying "there is something to read in here."

So if the link goes flaky mid-watch — a Modbus timeout, requireControl failing without a deliberate disconnect, a collision with main's deviceLicenseSequenceInFlight — every tick lands a check-failed report and the user reads a calm "Waiting for purchase…" for ten minutes, with the visual cue that would invite them to open the panel actively removed.

The comment above the interval claims the opposite:

refresh reports its own failures as check-failed reports, so a flaky tick shows in the badge instead of silently killing the watch.

The first half is true (the report records it). The second is not: the badge is exactly where it does not show.

This is the same failure mode #358 exists to eliminate on the web side — telling someone who just paid that things are fine when they may not be. The editor should hold the same line. Either let check-failed outrank the waiting label, or compose them ("Waiting for purchase — last check failed"), or at minimum keep the dashed underline so the panel still reads as worth opening. Whichever is chosen, the comment needs to match.

4. (Medium) buy() starts the watch even when the browser never opened

await system.openExternalLink(url)
setAwaitingPurchase(true)

SystemPort.openExternalLink returns Promise<{ success: boolean }> (system-port.ts:46). The result is awaited and discarded.

On a failed launch — no handler registered, a sandboxed environment, the accelerator path failing — the user gets no purchase page and a ten-minute "Waiting for purchase…", with "Buy licence" withdrawn (finding: correctly, per the double-buy rule) in the one state where they most need it back. The only escape is to find and click "Stop waiting" in a panel that is telling them to wait.

Guard on success, and on failure leave the badge alone so the Buy button stays offered. The hook's own test mocks { success: true }, so a { success: false } case is a two-line addition to the existing suite.

5. (Medium-low) Two live instances of the watch per board screen

board.tsx mounts useDeviceLicense(currentBoardInfo) directly at line 69, and useDeviceConnect(currentBoardInfo) at line 65, which mounts its own useDeviceLicense(boardInfo) (use-device-connect.ts:45). Both instances now run the interval effect against the same shared awaitingPurchase flag.

In practice the second one is saved by the overlap guard: both timers were created in the same render and fire in the same timer batch, and run() calls startCheck() synchronously before its first await, so instance A flips phase to 'checking' before instance B's callback executes. B skips.

But that is an accident of timer batching plus where startCheck() happens to sit — not a designed invariant, and nothing documents or tests it. Move startCheck() after an await, or introduce any asynchrony before it, and the editor starts firing two overlapping licensing sequences per tick on the same Modbus link; main's deviceLicenseSequenceInFlight would then catch it and land a check-failed (masked by finding 3).

Meanwhile instance B silently burns its entire 30-tick budget doing nothing.

Cleanest fix is a single owner: hoist the watch effect out of useDeviceLicense into whichever component owns the screen, or make the hook take a flag for whether this instance drives the watch. Failing that, at minimum a test that asserts one tick produces exactly one refresh when the hook is mounted twice.

6. (Low) The overlap skip spends tick budget

ticks += 1 runs before the phase === 'checking' check (use-device-license.ts:167 vs :174), so a skipped tick still costs a tick. A device slow enough to straddle intervals — the exact case where a longer window is most warranted — gets a window shorter than the advertised ten minutes, and with the duplicate mount in finding 5, instance B's budget is consumed entirely by skips.

Move the increment below the guard, or count only ticks that actually issued a refresh.

7. (Low) The ten-minute budget is not a bound — it resets on remount

ticks is effect-local. Navigating off the device screen tears the interval down but leaves awaitingPurchase: true in the store; navigating back mounts a fresh effect with ticks = 0 and a full new budget. A user cycling between tabs keeps the watch alive indefinitely, and there is no state in which "the watch has been running for 10 minutes" is actually known.

An absolute deadline in the store — awaitingPurchaseUntil: number set by buy(), checked by the effect — bounds it for real, survives unmount, and makes the intent inspectable in devtools. It also gives finding 1 a natural backstop.

8. (Low) The first check is 20 seconds away, always

Nothing fires immediately on watch start or on remount; the interval's first callback is at t+20s. Paddle's completion webhook frequently lands before the user has switched back to the editor, so the common happy path pays a full interval for a result that was already available.

One immediate tick on effect start (subject to the overlap guard) makes the best case feel instant and costs one extra request per watch.

9. (Nit) The per-tick cost in the description understates what runs

While it runs, the existing refresh() executes every 20s — one Modbus read frame + one HTTP round-trip.

handleDeviceRefreshLicense reads the board-id anchor (readLicenseAnchor, one frame) and then runs resolveDeviceLicense, which is read → HTTP → write → read-back per its own docblock at main.ts:2438. All of it rides the control channel behind the frame mutex, competing with run/stop and the liveness status poll.

Not a defect — the mutex serialises it correctly and noteTraffic() keeps the liveness poll from declaring the link dead mid-sequence, both of which are handled. But the cost is worth stating accurately in the description, because it is the number someone will reach for when they next tune the interval.

Summary of what I'd fix before merge

# Severity Fix
1 High Clear awaitingPurchase in setDeviceBoard and clearDeviceDefinitions (extract one reset helper)
2 High Make the two slice tests set the flag before asserting it was cleared
3 Medium Don't let the waiting label mask check-failed; fix the contradicting comment
4 Medium Only start the watch when openExternalLink reports success
5 Medium-low One owner for the watch effect, or a test pinning one-tick-one-refresh
6–9 Low / nit Tick accounting, real deadline, immediate first tick, description accuracy

Note that findings 1, 3 and 4 share a shape: each is a state where the UI keeps saying "waiting for purchase" while something other than a pending webhook is going on. Given the companion PR's entire thesis is that a post-payment screen must report what is actually true, they're worth fixing together.

Mirror: every finding here ships byte-identically to openplc-web#669 (all five product files verified sha256-identical). Fixes must land here and be re-mirrored; #669 cannot diverge.

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

@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/store/slices/device/slice.ts`:
- Line 435: In the previousBoard !== deviceBoard branch around
resetDeviceLicense, call projectActions.syncVariableAliases() after
recalculating IEC addresses and before completing the board-change handling.
Ensure aliases are synchronized whenever the target board changes.
🪄 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: 4a1188cf-6aa3-4606-9426-da94c79189c9

📥 Commits

Reviewing files that changed from the base of the PR and between c00d186 and eccb277.

📒 Files selected for processing (8)
  • src/frontend/components/_features/[workspace]/editor/device/configuration/__tests__/device-license-status.test.tsx
  • src/frontend/components/_features/[workspace]/editor/device/configuration/board.tsx
  • src/frontend/components/_features/[workspace]/editor/device/configuration/components/device-license-status.tsx
  • src/frontend/hooks/__tests__/use-device-license.test.ts
  • src/frontend/hooks/use-device-license.ts
  • src/frontend/store/__tests__/device-slice.test.ts
  • src/frontend/store/slices/device/slice.ts
  • src/frontend/store/slices/device/types.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/frontend/components/_features/[workspace]/editor/device/configuration/board.tsx
  • src/frontend/store/tests/device-slice.test.ts
  • src/frontend/components/_features/[workspace]/editor/device/configuration/components/device-license-status.tsx
  • src/frontend/components/_features/[workspace]/editor/device/configuration/tests/device-license-status.test.tsx

Comment thread src/frontend/store/slices/device/slice.ts

@JulioSergioFS JulioSergioFS 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.

LGTM

@marconetsf
marconetsf merged commit cb54d9c into development Aug 12, 2026
20 of 21 checks passed
@marconetsf
marconetsf deleted the feat/post-purchase-license-poll branch August 12, 2026 18:36
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