Demo images / Build & push demo images (push) Successful in 3m24s
Writing the first tests for this section found the bug they were written to look for. `rotate()` started the poll, then the `load()` immediately behind it took a status snapshot that did not yet carry `running: true` and cancelled the interval — so the screen froze on the one action that most needs to show progress. The operator has just invalidated every credential their wallet holds, the rotation is genuinely running on the node, and the page tells them nothing is happening until they reload it by hand. It survived manual review because the backend flips `running` inside the same critical section that accepts the request, so the happy path usually wins the race. "Usually wins a race" is not a property to ship on a credential rotation. Polling now continues for a bounded window after a request the node accepted, and stops early as soon as `running` is observed. Bounded, so a request that was accepted but never acted on stops polling rather than hammering the node. 12 component tests cover the states that carry consequences: the channel census shown before the button is offered, the stale-BTCPay warning, the difference between "BTCPay has no internal node" (silence — an absence, not a fault) and "BTCPay's credential is dead" (a warning), the block on rotating while LND is unreachable, both poll races above, and that an idle tab does not wake the node. Verified: 12/12 new, 880/880 frontend tests, vue-tsc clean, and the rebuilt bundle contains the new strings. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>