Repository navigation
feat: live CPU/mem/temperature + per-container docker stats via subscriptions (0.1.40-beta2) - #201
Merged
Merged
Conversation
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>
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>
…etrics Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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>
…ntainerLiveStats Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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>
…ame 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>
…eam, 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>
… 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>
…ows (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>
…er 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>
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.
Rolls the ADR-0042 WebSocket subscription substrate out to four more domains, building directly on the device-verified
0.1.40-beta1network-throughput pilot (ADR-0043).Live domains
Container.Fallback + nullable handling
subscriptionOrPollcombinator implements the long-standing ADR-0026 fallback mandate: CPU/Mem/Temp degrade to the existing 2 sGetMetricspoll on sustained live failure (graceSUB_FALLBACK_GRACE_MS = 6 s, 3× the metrics poll) and return to the live stream on recovery, so the cards never go blank. Docker stats have no query path and so have no fallback (same as network).systemMetricsTemperaturehas a nullable root — a null frame is treated as "no reading yet" via an UNKNOWN sentinel, never an error.Version bookkeeping
Ships as 0.1.40-beta2, versionCode 108. versionCode stays 108: 0.1.39 stable = 107; the broken
v0.1.40-beta1tag was built at a commit reading 107 (the #200 merge omitted the release commit) and never validly shipped as 108, so beta2 reuses 108. The broken tag is left untouched — this PR also corrects the stranded 0.1.40 version bookkeeping.Beta-first (ADR-0006); on-device acceptance (ADR-0027 Tier 3) is the promotion-to-stable gate, which stays maintainer-only.
References
🤖 Generated with Claude Code