Repository navigation
Build circuit breaker emergency pause status banner 993 - #1090
Merged
Sadeequ merged 3 commits intoOct 3, 2026
Conversation
|
@owohdora-stack Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits. You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀 |
owohdora-stack
had a problem deploying
to
github-pages
September 29, 2026 08:26 — with
GitHub Actions
Failure
owohdora-stack
had a problem deploying
to
github-pages
October 2, 2026 18:57 — with
GitHub Actions
Failure
This branch had an error being deployed
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.
feat(#993): real-time circuit breaker emergency pause status banner
Summary
Adds a high-visibility emergency alert banner to the top of the root layout that activates the instant the indexer mirrors an on-chain
CircuitBreakerTriggeredevent. The banner names every halted protocol module and states the two numbers a user needs during an incident — how long the risk review is expected to take and when the module is estimated to recover.Alongside the banner, module views can now lock their primary action CTA behind the same pause state, with an informative tooltip explaining why the action died.
What Changed
New —
src/types/circuitBreaker.tsCircuitBreakerModuleunion (swaps,liquidations,bridges,lending,staking,remittance,governance,payments) plus display labels.CircuitBreakerIndexerEvent— the indexer wire envelope. Onlymoduleandtypeare guaranteed; everything else is optional and defaulted during normalisation.ActiveModulePause— the normalised, render-ready record shared by the banner and the CTA lock-down.New —
src/lib/circuitBreakerEvents.tsPure, React-free and transport-free parsing/formatting helpers:
parseCircuitBreakerFrame/toActiveModulePause— validate and normalise raw frames; returnsnullfor anything malformed so a bad frame can never take the banner down.normalizeCircuitBreakerModule— resolves contract naming variants (swaps,Pause Swaps,pause_liquidations,swap_router) onto a known module; unknown modules are ignored rather than locking down an unrelated view.hasPauseElapsed— auto-resume safety net for a missedCircuitBreakerClearedevent.buildPauseTooltip,formatDurationMinutes,formatRecoveryCountdown,formatRecoveryClock— user-facing copy.New —
src/components/circuit-breaker/CircuitBreakerContext.tsxCircuitBreakerProviderowns the single realtime subscription and the set of active pauses, keyed by module so a re-trigger replaces its entry instead of stacking duplicate banners.useCircuitBreakerState()for the banner,useModulePause(module)for module views.CircuitBreakerCleared, or when a self-resuming breaker passes its recovery ETA.New —
src/components/circuit-breaker/CircuitBreakerBanner.tsx(the deliverable)role="alert"+aria-live="assertive"+aria-atomicso the pause is announced the moment it lands.New —
src/components/circuit-breaker/PausedActionButton.tsxdisabledstrips a button from the tab order and stops it emitting pointer/focus events, which would leave a dead control with no explanation. This wrapper hard-disables the CTA viacloneElement, then wraps it in a focusable carrier carrying both a nativetitletooltip and a styled popover wired througharia-describedby.Modified —
src/utils/WebSocketManager.tssubscribeToIndexerEventsfan-out for non-price channels so circuit breaker frames ride the existing centralised socket rather than opening a second connection. The socket already drops unrecognised frame types; they now reach channel owners who own their own payload parsing.Modified —
src/app/layout.tsxCircuitBreakerProviderwraps the whole tree (a pause must be able to lock a CTA on any route, not just the one that happens to be open) andCircuitBreakerBanneris mounted above it in document order.Modified —
src/app/swap/page.tsxPausedActionButton module="swaps", relabels to "Swaps Paused", andhandleSwapbails early as defence in depth.Modified —
src/app/globals.css.circuit-breaker-flashkeyframes — a slow 1.8s low-contrast flash that reads as urgent without strobing. Added to the existingprefers-reduced-motion: reduceblock, alongside thestatus-ping/status-pulseguards.Why This Matters
Accessibility
role="alert"/aria-live="assertive"for immediate announcement.prefers-reduced-motion: reduce; the pulsing icon usesmotion-safe:animate-pulse.tabIndex={0}carrier — keyboard and touch users can reach it, not just mouse users.Files Changed
src/types/circuitBreaker.tssrc/lib/circuitBreakerEvents.tssrc/components/circuit-breaker/CircuitBreakerContext.tsxsrc/components/circuit-breaker/CircuitBreakerBanner.tsxsrc/components/circuit-breaker/PausedActionButton.tsxsrc/components/circuit-breaker/index.tssrc/utils/WebSocketManager.tssrc/app/layout.tsxsrc/app/swap/page.tsxsrc/app/globals.cssTesting
This repo has no wired test runner (
npm testrunsscripts/test-lang-cache.js; the*.test.tsxfiles are not attached to a runner), so verification was done with two throwaway harnesses that drove the real modules and were removed before commit.18 logic checks over
circuitBreakerEvents.ts— module alias resolution, malformed-frame rejection, protocol default substitution, auto-resume semantics, and every formatter/tooltip string. This surfaced a real defect:Math.ceilmade anminutes < 1fallback unreachable, so the final minute would have displayedin 0m. Fixed to round up (in 1m).27 integration checks in jsdom against a fake socket — provider → banner → CTA lock-down end to end:
CircuitBreakerTriggeredrenders the banner instantly withrole="alert"/aria-live="assertive", the module name, the on-chain reason, the review window, a live countdown and the UTC recovery timedisabled+aria-disabled, relabels, and gains the pause marker + a focusable carrier with a native tooltipCircuitBreakerClearedclears the banner and re-enables the CTAconnection,price_update, unknown module, invalid JSON) never break the bannerStatic checks
tsc --noEmitoutput is byte-identical to the pre-change baseline — 33 pre-existing errors, 0 new.eslinton all changed and new files reports only the 5 pre-existing problems, confirmed by diffing against agit stashed baseline.Notes / Out of Scope
module="liquidations"and any view can opt in with one line, but I did not invent a module to satisfy the issue's example.swapsis the one real module view wired up.server.jswas deliberately left untouched. The client is the deliverable and degrades to "no banner" rather than erroring. Wiring a producer is a follow-up once the indexer exists.tscsyntax errors inPoolTable.tsx,TransactionHistoryTable.tsx,settings/page.tsx,dashboard/transactions/page.tsxandapp/services/api.ts; theMobileBottomNav is not definedlint error inlayout.tsx; and the unused-var warnings inlayout.tsx/swap/page.tsx.Closes #993