Skip to content

fix: end-to-end service flow bug pass (add → poll → display) - #148

Merged
EraPartner merged 3 commits into
mainfrom
claude/project-thread-k1qb3g
Oct 28, 2026
Merged

EraPartner merged 3 commits into
mainfrom
claude/project-thread-k1qb3g

Conversation

@EraPartner

Copy link
Copy Markdown
Owner

Requested by EraPartner · project thread

Method

Four read-only agents traced the whole flow (adding a service → building/polling it → REST/WS → UI) for all 13 services and the shared path, and reported findings with file:line evidence. Separate agents fixed the confirmed ones, each with a regression test, and a further read-only pass reviewed the diff for CI breakers. Nothing touches SSH host-key checking or Synology TLS (owned by #147). Watchman stays read-only toward services: the only new network activity is a ZMQ rawtx subscription, which only counts messages.

Tests: nothing has run locally, because the cloud environment can't reach the npm registry. CI is the first run.

Backend fixes

  • Bitcoin: zmqRawtxEndpoint was stored but never used. It now subscribes to rawtx and reports zmqTxCount / zmqRawtxConnected / zmqRawtxError, sharing one socket when the endpoint matches hashblock. The rpcUser help text now says that health also needs credentials.
  • Tor ControlPort mode: now emits bandwidthCurrent (max(bwRead, bwWritten)) and relayType. Before this, the tile headline was "—".
  • Homebridge: missing server-info fields are null. They were "", which showed as blank rows.
  • Synology:
    • Missing SNMP values (DSM-only mode, varbind errors) are null. They were 0 (0 °C, 0 B).
    • DSM fallbacks for temperature, model and version.
    • New dsmError metric.
    • An unreadable status is Unknown. It was Warning, which made the tile critical.
  • DSM client: a rejected login is remembered for 5 minutes. Before, it retried on every poll (Auto Block risk, unverified).
  • Hue:
    • A light seed taken while the event stream was down is now invalidated when the stream recovers. Before, it stayed stale.
    • On 401/403 the health message says the application key was rejected, and getStats returns UnauthorizedError.
  • Raspberry Pi: over direct SSH, piModel and hwRevision now come from /proc/cpuinfo. Before, it showed "Unknown" without pigpiod.
  • Mac mini: runs top -l 2 -n 0 -s 1 and parses the last sample. The first sample isn't a current reading (macOS behaviour, unverified).
  • DuckDB: TIMESTAMP columns are read back as UTC via fromTs. Before, createdAt, updatedAt and audit ts were shifted by the host's timezone.
  • WebSocket: a reachable:false health result now raises one alert per transition, and recovery is sent on the next reachable result. Before, the Events tab and toasts never showed ordinary outages.
  • Circuit breaker: while a lane is open because of UNAUTHORIZED or VALIDATION, it returns that error instead of CIRCUIT_OPEN. The breaker still trips and still blocks calls.
  • qBittorrent:
    • Log entries are deduped by id across overlapping polls (poller and SWR).
    • A 403 only clears the cookie that request used, so one session expiry no longer causes a double login.
  • Config: null on update clears a saved secret, for example the router snmpCommunity. Blank, *** and an omitted field still keep it.

Frontend fixes

  • Shared lib/healthStatus.ts: the tile and detail sheet show warning when the service is reachable but one of its checks (host or service) is down, matching the top bar's count. This also covers Tor ControlPort with a dead daemon.
  • Bitcoin: sync progress used the wrong percent scale ("1.00%" for a synced node).
  • Alby Hub: shows warn when running === false.
  • Synology: charts and summary use byte rates instead of cumulative counters; warns on dsmError.
  • IPFS: the WebUI quick link is now built from apiUrl. It never showed before.
  • Dashboard:
    • "Nothing active in this profile" when services exist but none run in the active profile.
    • "Backend unreachable — showing data from …" banner.
    • The WebSocket dot is visible at every width.
  • Service editor:
    • The "saved — leave blank to keep" hint only shows for secrets that are actually saved.
    • A Clear control removes a saved secret that isn't required.

Docs

Integration pages, component pages, API config, error codes, real-time updates, the flow visualizer and CHANGELOG were synced with the changes above. The pages also had pre-existing drift, now corrected: the Homebridge methods and routes that don't exist, the "service tier skipped" claim on AdGuard and Bitcoin, the Hue probe path, and Synology "SNMP required".

Decisions (chosen per the project's pick-the-best-option rule)

  • zmqRawtxEndpoint: implemented as a read-only counter rather than removing the field. Reason: the rule is never to cut functionality, and it is subscribe-only.
  • Tor bandwidthCurrent: set to the busier direction, not the sum. Reason: that is comparable to Onionoo's observed bandwidth.
  • Breaker: it keeps tripping on auth errors and only reports the original error while open. Reason: this keeps the login-ban protection.
  • qBittorrent overlapping polls: deduped log entries by id instead of serializing getStats. Reason: a shared in-flight promise would let one caller's abort fail the other's result.
  • Clearing secrets: null on PUT, with no schema change (ServiceInstanceInput already allows extra properties). Required secrets get no Clear button.
  • Synology disk metrics: null without DSM, because 0 would mean an empty disk.

Not changed

  • qBittorrent's default port of 8069. It matches the user's own setup, per the 2026-10-21 per-service review.

Compatibility

  • API: a PUT that sends null for a secret field used to get a 400. It now clears the field. Everything else is unchanged.
  • WS clients receive more alert frames: one pair per outage.

Generated by Claude Code

- Bitcoin: subscribe to zmqRawtxEndpoint (read-only tx counter, shared socket
  when it matches hashblock); rpcUser help says health needs credentials too
- Tor ControlPort mode: emit bandwidthCurrent (busier direction) and relayType
- Homebridge: missing server-info fields are null, not empty strings
- Synology: null instead of 0 for missing SNMP values, DSM fallbacks for
  temperature/model/version, dsmError metric, unreadable status is Unknown
- DSM client: remember a rejected login for 5 minutes (no retry every poll)
- Hue: re-seed lights after the event stream recovers; 401/403 says the
  application key was rejected
- Raspberry Pi: model and revision from /proc/cpuinfo over direct SSH
- Mac mini: CPU usage from the second top sample
- DuckDB timestamps read back as UTC instead of host local time
- WebSocket: reachable:false health raises one alert per transition
- Circuit breaker: an open lane keeps returning the UNAUTHORIZED/VALIDATION
  error that opened it instead of CIRCUIT_OPEN
- qBittorrent: dedupe log entries across overlapping polls; a 403 no longer
  discards a cookie a parallel request just obtained
- Config: null on update clears a saved secret
- Shared healthStatus: tiles and the detail sheet show warning when a tier
  is down, matching the top bar count
- Bitcoin sync progress formatted as a fraction (was 100x too small)
- Alby Hub warns when the hub is not running; Synology warns on dsmError
  and charts byte rates instead of cumulative counters
- IPFS WebUI quick link derived from apiUrl
- Dashboard: 'Nothing active in this profile' state, backend-unreachable
  banner with stale data, connection dot at all widths
- Service editor: saved-secret placeholder only for saved secrets, and a
  Clear control that removes a saved secret
@EraPartner EraPartner self-assigned this Oct 28, 2026
@github-actions

Copy link
Copy Markdown

Coverage Report for backend

Status Category Percentage Covered / Total
🟢 Lines 93.37% (🎯 80%) 2849 / 3051
🟢 Statements 90.39% (🎯 80%) 3125 / 3457
🟢 Functions 86.08% (🎯 80%) 594 / 690
🟢 Branches 79.48% (🎯 72%) 2116 / 2662
File Coverage
File Stmts Branches Functions Lines Uncovered Lines
Changed Files
apps/backend/src/domain/services/bitcoin/BitcoinService.ts 84.89% 68.88% 83.87% 90.05% 143, 154, 207-208, 217, 225, 232-233, 242, 408, 419, 477, 484-487, 490, 497, 513-516, 519-522, 526-528, 535, 538-539, 548
apps/backend/src/domain/services/homebridge/HomebridgeService.ts 98.59% 92.47% 100% 98.46% 267
apps/backend/src/domain/services/macMini/MacMiniService.ts 96.5% 78.28% 100% 97.72% 204-206, 219, 255, 368, 418
apps/backend/src/domain/services/philipsBridge/PhilipsBridgeService.ts 94.96% 86.36% 88.46% 97.65% 158, 167, 320, 342, 346, 371, 375
apps/backend/src/domain/services/qbittorrent/QBittorrentService.ts 90.72% 67.79% 81.81% 94.02% 209-213, 217-220, 253, 289, 325, 335, 401, 425-428, 431
apps/backend/src/domain/services/raspberryPi/PiStatsCollector.ts 93.41% 79.38% 95% 97.81% 152, 234, 236, 237, 239, 240, 241, 243-244, 282, 410
apps/backend/src/domain/services/synology/SynologyService.ts 98.15% 89.07% 100% 99.3% 269, 287, 619
apps/backend/src/domain/services/tor/TorService.ts 89.49% 81.9% 73.33% 93.75% 166, 170-171, 193, 199, 214, 251, 264, 347, 376, 383, 386, 395, 400, 485, 489, 509, 519, 555-557, 609, 668, 672, 674
apps/backend/src/infra/circuitBreaker/guardedService.ts 88.23% 70.58% 80% 100% 88, 89
apps/backend/src/infra/db/DuckDbPool.ts 89.23% 82.14% 100% 91.37% 19, 32, 100-105, 126, 129
apps/backend/src/infra/synology/dsmClient.ts 98.24% 91.48% 88.88% 100% 46
Generated in workflow #382 for commit 1a80e51 by the Vitest Coverage Report Action

@github-actions

Copy link
Copy Markdown

Coverage Report

Status Category Percentage Covered / Total
🔵 Lines 87.29% 1772 / 2030
🔵 Statements 84.91% 1937 / 2281
🔵 Functions 79.29% 494 / 623
🔵 Branches 74.68% 1593 / 2133
File Coverage
File Stmts Branches Functions Lines Uncovered Lines
Changed Files
apps/frontend/src/components/dashboard/BentoDashboard.tsx 81.48% 79.06% 66.66% 84.61% 59, 204-214
apps/frontend/src/components/dashboard/TopNav.tsx 72.09% 50% 90% 75.75% 33, 36, 42, 99-106
apps/frontend/src/components/detail/ServiceDetailSheet.tsx 74.48% 54.26% 69.56% 82.35% 54, 55, 57, 103, 150, 154, 164-169, 176, 179, 180, 213, 227, 232-234, 240-243, 347, 525-532
apps/frontend/src/components/tile/ServiceTile.tsx 89.65% 74.07% 82.35% 93.42% 71, 131, 166, 174, 180, 183-194, 241, 246, 257
apps/frontend/src/hooks/useAggregatedHealth.ts 97.82% 93.33% 90% 97.22% 47
apps/frontend/src/lib/healthStatus.ts 100% 100% 100% 100%
apps/frontend/src/pages/Settings/ServiceEditor.tsx 85.54% 79.45% 80.48% 87.5% 44, 54, 56, 57, 185, 210, 211, 224, 239, 248-249, 267-268, 277, 289, 334, 394-421, 498-503, 511-523
apps/frontend/src/services/renderers/albyHub.ts 100% 100% 100% 100%
apps/frontend/src/services/renderers/bitcoin.ts 80.95% 63.63% 100% 100% 121, 123, 129, 131
apps/frontend/src/services/renderers/ipfs.ts 100% 100% 100% 100%
apps/frontend/src/services/renderers/synology.ts 92.59% 90.9% 100% 100% 138, 139
apps/frontend/src/types/generated.ts 0% 0% 0% 0%
Generated in workflow #382 for commit 1a80e51 by the Vitest Coverage Report Action

@EraPartner
EraPartner marked this pull request as ready for review October 28, 2026 10:23
@EraPartner
EraPartner merged commit 33deb1b into main Oct 28, 2026
18 checks passed
@EraPartner
EraPartner deleted the claude/project-thread-k1qb3g branch October 28, 2026 10:23
EraPartner pushed a commit that referenced this pull request Oct 28, 2026
Rebased onto #145/#147/#148: document SSH host-key pinning (Mac Mini,
Pi), the DSM certificate fingerprint and DSM-only health, the now-used
Bitcoin rawtx ZMQ endpoint, Roon pairing state in DATA_DIR, and the
new error messages and login backoffs.
EraPartner added a commit that referenced this pull request Oct 28, 2026
…144)

* docs: add per-service setup guides with least-privilege credentials

Add docs/guides/service-setup/ with an index and one guide for each of
the 13 service kinds: what to enable on the service, which read-only or
least-privilege credentials to create, what to enter in Watchman, and
troubleshooting keyed to the error messages in the code.

Link the guides from the integration pages and indexes, and fix doc
drift found while checking against the code: AdGuard no longer claims
toggle control, the Homebridge health probe path, Bitcoin's Tor claim,
and the unused zmqRawtxEndpoint.

* docs: drop Test connection caveat, fix useServiceHealth hook doc

#146 made Test connection report an unreachable service as Failed, so
the setup guides no longer need the known-issue callout.

docs/components/use-service-health.md described a protection-toggle
mutation hook and a cache-clear hook that don't exist; rewrite it from
apps/frontend/src/hooks/useServiceHealth.ts (read-only, ADR-032).

* docs: update setup guides for the service bug fixes on main

Rebased onto #145/#147/#148: document SSH host-key pinning (Mac Mini,
Pi), the DSM certificate fingerprint and DSM-only health, the now-used
Bitcoin rawtx ZMQ endpoint, Roon pairing state in DATA_DIR, and the
new error messages and login backoffs.

---------

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants