Skip to content

dashboard: quick-wins batch (#69, #64, #74)#87

Merged
BrettKinny merged 4 commits into
mainfrom
dashboard-quick-wins
May 22, 2026
Merged

dashboard: quick-wins batch (#69, #64, #74)#87
BrettKinny merged 4 commits into
mainfrom
dashboard-quick-wins

Conversation

@BrettKinny
Copy link
Copy Markdown
Owner

Dashboard quick-wins

Three small, self-contained dashboard improvements — one commit each.

Not included — #72 (content-filter rule-detail ring)

Pulled from the batch — spec/code mismatch. The issue assumes a
dotty_content_filter_hits_total increment at bridge.py:1286 with the
rule + tier in scope. In the current tree content_filter() is imported
from a textUtils module and returns only the replacement string — it
exposes neither rule nor tier. The (rule, redacted-prefix, tier) ring
therefore needs a content_filter() API change in a shared module, not a
dashboard-only edit. Re-triage comment posted on #72.

Verification

py_compile and Jinja tag-balance are clean. The bridge test suite and a
live dashboard render need the RPi (fastapi etc. not installed in the
dev env).

Closes #69. Closes #64. Closes #74.

🤖 Generated with Claude Code

BrettKinny and others added 3 commits May 22, 2026 20:06
sound_event already carries `balance`; the dashboard rendered only a bare
event tag. Capture it into a 60 s rolling buffer and render an inline SVG
sparkline on the State tile — directly visualises the stuck-left localizer
bias tracked in #27.

- bridge.py: _sound_balance_history deque + _sound_balance_series getter,
  fed from the sound_event perception handler.
- dashboard.py: _sound_balance_sparkline builds the SVG polyline; /ui/state
  passes it to the template.
- state.html: sparkline row beneath the state buttons.

Closes #69.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
zeroclaw-discord runs on the same RPi as the bridge, so its health is a
local `systemctl` query — no SSH needed. New /ui/discord card shows the
unit's systemd state (active / failed / inactive) with a status dot and
the active-since timestamp.

- dashboard.py: DISCORD_UNIT env (ZEROCLAW_DISCORD_UNIT), _discord_unit_status
  via `systemctl show`, GET /ui/discord.
- discord.html: status card body.
- dashboard.html: new card after the perception card.

Model / channel / last-message would need the daemon to expose a status
endpoint — out of scope for this card, which surfaces daemon health.

Closes #64.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The vision endpoint already classifies failures by `err_kind` for the
Prometheus metric; add a lightweight in-memory aggregate so the dashboard
surfaces capture trouble without a metrics scrape.

- bridge.py: _vision_failures deque + _vision_failure_record (called from
  the vision_explain finally block, where err_kind is already in scope) +
  _vision_failures_last_hour getter.
- dashboard.py: _vision_failures_count helper, wired into the perception
  card context.
- perception.html: "N capture failures in the last hour" line in the
  SIGHT scene-view box, shown only when non-zero.

Closes #74.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings May 22, 2026 10:13
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR batches three small dashboard enhancements to improve operational visibility on the bridge UI: a sound-localizer balance sparkline, a local systemd health card for the Discord daemon, and an aggregated “vision capture failures (last hour)” indicator on the Perception card.

Changes:

  • Add a 60s rolling sound-localizer balance sparkline on the State tile.
  • Add a new /ui/discord partial and dashboard card that queries local systemctl status for zeroclaw-discord.
  • Track and display aggregated vision capture failures over the last hour.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
bridge/templates/state.html Renders the sound-balance sparkline when data is available.
bridge/templates/perception.html Displays a “failures in last hour” warning line when non-zero.
bridge/templates/discord.html New partial template for the Discord daemon status card.
bridge/templates/dashboard.html Adds the Discord card container with HTMX polling.
bridge/dashboard.py Wires new getters, builds sparkline context, adds /ui/discord endpoint.
bridge.py Adds rolling buffers + getters for sound balance and vision failures; wires them into the dashboard.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread bridge.py
Comment on lines +3637 to +3638
if err_kind:
_vision_failure_record(err_kind)
Comment thread bridge.py
Comment on lines +1718 to +1731
# tuples; the getter windows to the last hour and counts by error kind.
_vision_failures: "collections.deque[tuple[float, str]]" = collections.deque(
maxlen=200
)


def _vision_failure_record(kind: str) -> None:
"""Record a vision-capture failure for the dashboard aggregator (#74)."""
_vision_failures.append((time.time(), str(kind or "unknown")))


def _vision_failures_last_hour() -> dict[str, int]:
"""Vision-capture failures in the last hour, counted by error kind."""
cutoff = time.time() - 3600.0
Comment thread bridge.py
Comment on lines +1710 to +1714
"""Sound-localizer `balance` samples from the last
`_SOUND_BALANCE_WINDOW_SEC`, oldest→newest — the dashboard sparkline
source (#69). Empty when no sound_event has landed recently."""
cutoff = time.time() - _SOUND_BALANCE_WINDOW_SEC
return [bal for ts, bal in _sound_balance_history if ts >= cutoff]
`_call_vision_api` swallows its own request/timeout exceptions and returns an `ERROR:` sentinel string rather than raising. A dead or unconfigured VLM endpoint therefore reached `vision_explain` as an ordinary return, leaving `err_kind` unset — so the failure aggregator added in this branch never counted the single most common failure mode.

Promote the two sentinel strings to named constants and add `_classify_vision_result()`, which `vision_explain` now applies to the final description. Failures are recorded as `camera_offline` (API key unset), `vision_api` (request failed / non-2xx / bad body), or `exception` (anything raised) — so the per-kind aggregation and the `dotty_request_errors_total` metric carry real signal.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@BrettKinny BrettKinny merged commit a9363cd into main May 22, 2026
9 checks passed
@BrettKinny BrettKinny deleted the dashboard-quick-wins branch May 22, 2026 10:30
BrettKinny added a commit that referenced this pull request May 22, 2026
Brings in 5 commits that landed on main while the ZeroClaw-retirement
doc sweep was in progress (#53 per-person memory, #74, #81 hardware-doc
verification, #88 CI fix, the #87 dashboard-quick-wins merge).

Conflicts resolved (2 files):
- CLAUDE.md — kept the post-#36 architecture diagram and pi_voice
  Config-Files bullet over main's older ZeroClaw text.
- docs/faq.md — combined main's verified servo spec (feedback servos,
  SCS0009 pitch) with the ZeroClaw-retirement fix to the brain-host line.

Co-Authored-By: Claude Opus 4.7 (1M context) <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.

Dashboard: Vision-failure aggregator Dashboard: Sound-localizer balance sparkline Dashboard: Discord daemon status card

2 participants