The network monitor has grown organically and accumulated complexity (particularly around prover status/test merging logic that spans the backend-frontend boundary, root cause of the recent "duplicate prover cards" bug). The goal is to simplify the implementation.
In particular the tasks-related implementation in the backend and the JS logic grow too much and can be reduced with a little refactor.
Server-side prover merge
Problem: The backend sends prover status and test results as separate ServiceStatus entries. The frontend has a complex 60-line mergeProverStatusAndTests() to recombine them.
Solution: Merge on the server before serialization.
Dynamic ServerState with Vec<Receiver>
Problem: ServerState has a separate field per service type. Adding a new service requires modifying ServerState, get_status(), and start.rs. The get_status() handler has ~40 lines of if-let chains.
Solution: Replace per-service fields with a flat Vec<watch::Receiver<ServiceStatus>>.
- Replace ServerState fields with services: Vec<watch::Receiver>
- Remove the ProverReceivers type alias (though this wouldn't be needed if we address the previous point first).
- Simplify get_status() to a single
.iter().map().collect().
- In
spawn_prover_tasks(), spawn a small combiner task per prover that watches both the status and test receivers and publishes a single merged output.
- In start.rs, collect all receivers into a flat Vec
ServiceStatus builder methods
Problem: 31 occurrences of ServiceStatus { name: ..., status: ..., last_checked: ..., error: ..., details: ... } across the codebase. Each is 6-7 lines of boilerplate.
Solution: Add constructor methods on ServiceStatus.
- Move
current_unix_timestamp_secs() from monitor/tasks.rs to status.rs (it's a simple utility used everywhere)
- Add
ServiceStatus::healthy(name, details), ::unhealthy(name, error, details), ::unknown(name, details) and ::error(name, error) constructors
- Replace all 31 struct-literal constructions across status.rs, explorer.rs, note_transport.rs, faucet.rs, remote_prover.rs, and counter.rs
- Delete the private fn unhealthy() helpers in explorer.rs and note_transport.rs (replaced by ServiceStatus::error())
Componentize updateDisplay() in JavaScript
Problem: updateDisplay() is ~400 lines of nested template literals handling 8+ service types in one function.
Solution: Extract per-service-type render functions with a dispatch map.
- Create focused render functions: renderRpcDetails(), renderCombinedProverDetails(), renderFaucetDetails(), renderNtxIncrementDetails(), renderNtxTrackingDetails(), renderNoteTransportDetails(), renderExplorerCard()
- Create renderServiceCard(service, innerHtml, isHealthy) for the outer card shell
- Add a SERVICE_RENDERERS dispatch map keyed by detail type name
- updateDisplay() shrinks to ~30 lines: iterate services, dispatch to renderer, wrap in card
Unify health computation
Problem: The backend computes health, but the frontend re-computes it in isServiceHealthy() for provers (combining status + test + gRPC-Web probe). Two different health computations is fragile.
Solution: After the first point, the backend already merges prover health. Simplify the frontend to only override for gRPC-Web probes (the one browser-only signal).
- Simplify isServiceHealthy() to: trust service.status, only override if gRPC-Web probe for that service's URL has failed
CSS custom properties and consolidation
Problem: Color values are hardcoded 40+ times across CSS and JS.
Solution: CSS custom properties + merge media queries.
- Add :root block with --color-accent, --color-healthy, --color-unhealthy, --color-text-primary, --color-border, etc.
- Replace all hardcoded hex values with var(--color-*)
- Merge the four @media (max-width: 768px) blocks into one
- Replace the ~4 hardcoded color strings in JS with constants or CSS variable reads
The network monitor has grown organically and accumulated complexity (particularly around prover status/test merging logic that spans the backend-frontend boundary, root cause of the recent "duplicate prover cards" bug). The goal is to simplify the implementation.
In particular the tasks-related implementation in the backend and the JS logic grow too much and can be reduced with a little refactor.
Server-side prover merge
Problem: The backend sends prover status and test results as separate
ServiceStatusentries. The frontend has a complex 60-linemergeProverStatusAndTests()to recombine them.Solution: Merge on the server before serialization.
Dynamic
ServerStatewithVec<Receiver>Problem:
ServerStatehas a separate field per service type. Adding a new service requires modifyingServerState,get_status(), and start.rs. Theget_status()handler has ~40 lines of if-let chains.Solution: Replace per-service fields with a flat
Vec<watch::Receiver<ServiceStatus>>..iter().map().collect().spawn_prover_tasks(), spawn a small combiner task per prover that watches both the status and test receivers and publishes a single merged output.ServiceStatusbuilder methodsProblem: 31 occurrences of ServiceStatus { name: ..., status: ..., last_checked: ..., error: ..., details: ... } across the codebase. Each is 6-7 lines of boilerplate.
Solution: Add constructor methods on
ServiceStatus.current_unix_timestamp_secs()from monitor/tasks.rs to status.rs (it's a simple utility used everywhere)ServiceStatus::healthy(name, details), ::unhealthy(name, error, details), ::unknown(name, details) and ::error(name, error)constructorsComponentize updateDisplay() in JavaScript
Problem:
updateDisplay()is ~400 lines of nested template literals handling 8+ service types in one function.Solution: Extract per-service-type render functions with a dispatch map.
Unify health computation
Problem: The backend computes health, but the frontend re-computes it in isServiceHealthy() for provers (combining status + test + gRPC-Web probe). Two different health computations is fragile.
Solution: After the first point, the backend already merges prover health. Simplify the frontend to only override for gRPC-Web probes (the one browser-only signal).
CSS custom properties and consolidation
Problem: Color values are hardcoded 40+ times across CSS and JS.
Solution: CSS custom properties + merge media queries.