Repository navigation
feat: live network throughput card — subscription test balloon (0.1.40-beta1) - #200
Merged
Merged
Conversation
Brainstormed design for feeding live systemMetricsNetwork throughput into the existing Overview Network card via a GraphQL WS subscription. Scoped test balloon re-validating the subscription transport (ADR-0026 revisit; network throughput is newly available upstream). Ships beta-first. Also gitignore .superpowers/ brainstorm artifacts. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Task-by-task TDD plan (pure seams for unit-testable logic given no local toolchain; CI as the build/test authority). Co-Authored-By trailer below. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
nofuturekid
added a commit
that referenced
this pull request
May 30, 2026
…riptions (0.1.40-beta2) (#201) * docs(adr-0042): adopt WS subscription transport for network throughput pilot * release: 0.1.40-beta1 — live network throughput card (versionCode 108) * docs(spec): 0.1.40 live metrics & docker stats via subscriptions Approved brainstorm design for the multi-domain subscription rollout (CPU+Mem swap w/ poll-fallback, new Temperature card, Docker stats stream-join). Builds on the device-verified ADR-0042 network pilot. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs(plan): 0.1.40 live metrics & docker stats implementation plan 8 dependency-ordered TDD tasks (schema/ops -> subscriptionOrPoll substrate -> models -> mappers -> repository streams -> viewmodel -> UI -> release tail). Generated via survey/synthesize/critic/revise workflow; self-reviewed for spec coverage, type consistency, and the nullable systemMetricsTemperature trap. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(graphql): vendor cpu/mem/temp/docker subscriptions + extend GetMetrics Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(repo): subscriptionOrPoll fallback combinator + grace const New substrate capability over the ADR-0042 bare subscriptionStream: when a live sub Errors past SUB_FALLBACK_GRACE_MS (or never delivers a first frame), degrade to the HTTP poll; return to the sub on recovery. NoServer is forwarded immediately. Poll is collected only while in fallback (no double-collect). ADR-0026 mandate: no blank card. Tests pin the degrade/recover/cold-start/no-server transitions through the real production combinator on the virtual scheduler: transient error within grace keeps the last frame; sustained error or silent cold-start past grace uses the poll; recovery returns to the sub (the recovered frame emitted directly) and stops the poll (no lingering double-collect); NoServer is forwarded immediately; the grace const encodes its 3x-poll-interval intent. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(model): Temperature (+UNKNOWN sentinel, pure toTemperature) + ContainerLiveStats Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(model): null-safe toTemperature smart-cast (sample!!.let) The `sample?.average ?: return` Elvis guard does NOT smart-cast `sample` to non-null (the null check is on the property, not the receiver), so the subsequent `sample.unit` / `sample.hottestName` accesses would not compile. Assert non-null via `sample!!.let { it -> ... }` — control flow proves it, since a null `sample` triggers the Elvis return. Also correct the same defect in the plan spec so future tasks reference a compilable pattern. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(mapper): combineLiveMetrics (poll+live) + cpu/mem/temp/docker frame mappers Shared combineLiveMetrics holds the byte->GiB + null-buffcache math once; toLiveMetrics now delegates to it so the GetMetrics poll and the live cpu+mem subscription combine are provably byte-identical (Rule 9). Adds the thin Apollo frame extractors (cpuPercentTotal, toMemoryTriple), the shared TemperatureSummary->Temperature mapper for both the subscription and the extended GetMetrics poll, the G->domain TemperatureUnit mapping, and the dockerContainerStats frame->(id, ContainerLiveStats) mapper (netIO/blockIO dropped, out of scope). Tests pin the GiB divisor, null-buffcache->0, and the null-root->UNKNOWN / unit-carrying temperature behavior. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(repo): live cpu+mem combine (subscriptionOrPoll), temperatureStream, dockerStatsStream Wire the three live streams for Task 5: - liveMetricsStream() combines systemMetricsCpu + systemMetricsMemory via the companion combineMetricsStates fold (Content only when both legs Content with matching server URL); metricsStream() now wraps it in subscriptionOrPoll with the existing GetMetrics poll as fallback, keeping its exact signature. - temperatureStream() = subscriptionOrPoll(tempSub, GetMetrics temp poll). - dockerStatsStream() scans frames into a growing immutable map (no fallback). - Companion test seams metricsCombineForTest (drives the real combineMetricsStates over DomainState legs) and dockerStatsStreamForTest/accumulateStats. - MetricsCombineTest + DockerStatsAccumulationTest pin the precedence fold and accumulate/update-by-id semantics. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(vm): gated temperatureState + dockerStatsState + dockerLiveStats overlay + joinContainerStats Adds the Overview-gated temperatureState and the dockerGate-gated dockerStatsState (so the stats socket and the docker poll come and go together), plus the derived dockerLiveStats StateFlow that unwraps the overlay to a plain Map<String, ContainerLiveStats> for the Docker UI (empty unless Content). dockerState stays byte-identical — the overlay is a SEPARATE flow (spec §C / Rule 2-3 surgical). joinContainerStats is the single LEFT-join overlay seam (companion): every polled container is kept in its original order; a container with no stats frame yet gets null stats (no zero noise), never dropped. Pinned by ContainerStatsJoinTest (keeps-without-frame, preserves polled order, attaches by id). It is called by DockerContent over dockerLiveStats (Task 7), so it is load-bearing, not dead code. CPU/Mem need no ViewModel change — the transport swap is invisible. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(ui): Overview Temperature StatCard + live per-container Docker rows (joinContainerStats) Thread temperatureState and dockerLiveStats from MainViewModel through MainScreen into OverviewTab and DockerTab. OverviewTab gains a Temperature StatCard with M3 accent escalation (t.danger for critical, t.warn for warning, else t.accent), a rolling tempSeries sparkline fed only when a reading is available, and a unitSymbol helper carrying the server-reported unit. Unavailable reads show an em-dash and 'live unavailable'. DockerTab overlays live per-container cpu%/mem onto the polled list via the single MainViewModel.joinContainerStats seam, threading the stats through all four ContainerRow call sites (List + Grouped); the Grid layout stays stats-free. Live numbers render only for Running containers (guards stale entries after a stop). dockerState's poll transport stays byte-for-byte unchanged (separate-map design, spec C). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * release: 0.1.40-beta2 — live CPU/mem/temperature + per-container docker stats (versionCode 108) Rolls the ADR-0042 WebSocket subscription substrate out to four more domains — CPU, memory, temperature, and per-container docker stats (ADR-0043). CPU/Mem/Temp degrade to the existing 2 s GetMetrics poll via the new subscriptionOrPoll fallback combinator (ADR-0026 mandate, finally implemented); docker stats and the temperature card are new surfaces with no poll fallback. ADR-0026 status updated to note the supersede. versionCode stays 108: 0.1.39 stable = 107; the broken v0.1.40-beta1 tag was built at a commit reading 107 (the #200 merge omitted the release commit) and never validly shipped as 108; this branch carries 108 but it has never been merged/tagged/distributed. beta2 reuses 108 (>107, never-shipped). The broken v0.1.40-beta1 tag is left untouched in history. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
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.
What shipped
Live network throughput (rx/tx bytes per second) is now displayed in the Overview Network card via a GraphQL WebSocket subscription (
systemMetricsNetwork), replacing the hardcoded-zero scaffold that shipped in earlier versions. The card shows real ~1 Hz rates for the primary NIC with no UI changes — the sparkline and StatCard were already wired, only the data source was missing.Why this matters / ADR-0042
This is a scoped test balloon re-validating the WebSocket subscription transport per ADR-0042. The prior
graphql-transport-wspilot (ADR-0026) was provisionally reverted pending two triggers:systemMetricsNetworksubscription deliveringrxBytesPerSec/txBytesPerSecper interface at ~1 Hz. Live-verified 2026-05-29 against the real server.Both triggers fired. This beta ships the narrowest possible balloon: network throughput only. Metrics (CPU/Mem/Temp), docker stats, and array subscription remain on their existing poll paths.
Key implementation points:
ApolloClientFactory.buildWs(...)reuses the ADR-0041 self-signed TLS pin path — no new trust surface.subscriptionStream<T>substrate mirrorsdomainStream(ADR-0017);networkThroughputStreamis its only consumer in this beta.MainViewModel.networkThroughputStategated withoverviewOnly()— WS socket lives only while Overview is the foreground tab.WebSocketNetworkTransportis already on the classpath.Spec / plan
docs/superpowers/specs/2026-05-29-live-network-graph-design.mddocs/superpowers/plans/2026-05-29-live-network-graph.mdBeta-first
This PR targets 0.1.40-beta1 (versionCode 108). Promotion to stable requires the maintainer's on-device acceptance gate (ADR-0027 Tier 3): the Network card must show live rates on real hardware. If the card shows "unavailable" after accepting the beta, stop — do not promote. The WS transport or TLS wiring needs diagnosis first.
Network throughput data (
systemMetricsNetwork) is newly available upstream — this is not a reimplementation of a prior feature.🤖 Generated with Claude Code