Skip to content

fix(dashboard): repair busted render — stale CSS, dead cards, wrong status#128

Open
BrettKinny wants to merge 1 commit into
mainfrom
fix/dashboard-busted-render
Open

fix(dashboard): repair busted render — stale CSS, dead cards, wrong status#128
BrettKinny wants to merge 1 commit into
mainfrom
fix/dashboard-busted-render

Conversation

@BrettKinny
Copy link
Copy Markdown
Owner

Fixes the "busted" dashboard render. Five defects + a layout change. Already deployed to the dotty-bridge container on Unraid (healthcheck green, md5s verified) and confirmed live.

Fixes

# Issue Root cause Fix
1 State-card ballooning sparkline / giant gaps Vendored tailwind.min.css was stale (Apr 28) vs. later template edits, so .h-6/.border-t/.py-1.5/.break-all were absent. With .h-6 gone the sound-balance SVG (viewBox 0 0 100 24, flex-1, no height) stretched to ~140px via its intrinsic aspect ratio. Rebuilt CSS from current templates + updated SRI integrity hash
2 Robot status dot grey/"offline" when online Dot was driven by voice activity today; no voice turn → grey, even with the robot connected and streaming fresh perception events (sensor_stale: false) Treat a fresh perception device as online — perception liveness is the real connectivity signal
3 Version chip showed vunknown Container has no .git; deploy never stamped a version BRIDGE_VERSION Docker build-arg writes /app/.bridge-version; deploy-bridge-unraid.sh passes HEAD short SHA
4 Discord daemon card stuck "Status unavailable" Queried systemctl zeroclaw-discord locally — impossible in the Unraid container; ZeroClaw retired in #36 Removed route, template, helper, card section
5 Smart Mode advertised a defunct model swap Showed claude-sonnet-4-6 / mistral-small, but the swap only fires on the retired tier1slim path Relabeled to honest behaviour on the default PiVoiceLLM path; still shows the swap on tier1slim deployments

Also switched the page to natural document scroll (was a single-screen overflow:hidden layout that pushed cards/feed below an unreachable fold); the activity feed now caps at 70vh and scrolls internally.

Notes

  • Bridge-only change — dotty-behaviour untouched, single-container deploy.
  • After deploy, the version chip reads vd528643, the robot dot is green ("online — perception active, no voice yet today"), .h-6 is present in the served CSS, and /ui/discord returns 404.

Test plan

  • pytest tests/test_bridge_routes.py tests/test_dashboard_csrf.py — 28 passed
  • All /ui/* partials render 200; /ui/discord → 404
  • Deployed + verified live on Unraid

🤖 Generated with Claude Code

…tatus

Five dashboard defects fixed:

1. State-card ballooning sparkline / giant gaps. The vendored
   tailwind.min.css was stale (Apr 28) vs. later template edits, so
   `.h-6`, `.border-t`, `.py-1.5`, `.break-all` were absent. With `.h-6`
   missing the sound-balance SVG (viewBox 0 0 100 24, flex-1, no height
   bound) stretched to ~140px via its intrinsic aspect ratio. Rebuilt the
   CSS from current templates and updated the SRI integrity hash.

2. Robot status dot showed grey "offline" when the robot was online but
   simply hadn't had a voice turn today. Now treats a fresh (non-stale)
   perception device as online — perception liveness is the real
   connectivity signal; voice idleness is normal.

3. Version chip showed "vunknown" — the container has no .git checkout
   and the deploy never stamped a version. Added a BRIDGE_VERSION Docker
   build-arg (writes /app/.bridge-version); deploy-bridge-unraid.sh passes
   the HEAD short SHA.

4. Removed the Discord daemon card. It queried `systemctl zeroclaw-discord`
   locally — impossible inside the Unraid bridge container; ZeroClaw was
   retired in #36. Dropped the route, template, helper, and card section.

5. Smart Mode card advertised a model swap (claude-sonnet / mistral) that
   only fires on the retired tier1slim path. Relabeled to honest behaviour
   on the default PiVoiceLLM path ("Robot smart-mode pip … · model-swap
   pending (v2)"); still shows the swap on tier1slim deployments.

Also switched the page to natural document scroll (was a single-screen
overflow:hidden layout that pushed cards/feed below an unreachable fold);
the feed now caps at 70vh and scrolls internally.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings May 29, 2026 09:56
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 fixes several dashboard rendering and status issues in the dotty-bridge FastAPI dashboard, including stale CSS output, incorrect robot liveness display, missing deployed version metadata, and removal of a retired Discord daemon card.

Changes:

  • Rebuilds vendored Tailwind CSS and updates the SRI hash.
  • Removes the obsolete /ui/discord card/route/helper.
  • Updates dashboard layout, status-strip liveness logic, version stamping, and Smart Mode display behavior.

Reviewed changes

Copilot reviewed 6 out of 7 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
scripts/deploy-bridge-unraid.sh Passes the current short Git SHA into the Docker build.
bridge/Dockerfile Writes the build-provided bridge version to /app/.bridge-version.
bridge/dashboard.py Removes Discord status support and updates Smart Mode/status-strip context logic.
bridge/templates/dashboard.html Updates CSS integrity, natural page scrolling, and removes Discord card markup.
bridge/templates/smart_mode.html Shows different Smart Mode copy depending on whether model swap is active.
bridge/templates/discord.html Deletes the retired Discord daemon partial.
bridge/static/tailwind.min.css Rebuilt bundled Tailwind/DaisyUI stylesheet.

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

Comment thread bridge/dashboard.py
# the swap is v2 scope (docs/cutover-behaviour.md) — toggling smart_mode
# only lights the robot's smart-mode LED pip + persists the flag. Mirror
# bridge.py's env so the card describes what actually happens.
VOICE_PROVIDER = os.environ.get("DOTTY_VOICE_PROVIDER", "pivoice")
BrettKinny added a commit that referenced this pull request May 30, 2026
Adds DashboardRenderFixTests guarding the repairs from PR #128 against
regression: /ui/discord now 404s (dead zeroclaw-discord card removed),
the smart-mode partial still renders, and its card is honest about the
live PiVoiceLLM path performing no model swap ("model-swap pending").

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
BrettKinny added a commit that referenced this pull request May 31, 2026
* fix(dashboard): repair busted render — stale CSS, dead cards, wrong status

Five dashboard defects fixed:

1. State-card ballooning sparkline / giant gaps. The vendored
   tailwind.min.css was stale (Apr 28) vs. later template edits, so
   `.h-6`, `.border-t`, `.py-1.5`, `.break-all` were absent. With `.h-6`
   missing the sound-balance SVG (viewBox 0 0 100 24, flex-1, no height
   bound) stretched to ~140px via its intrinsic aspect ratio. Rebuilt the
   CSS from current templates and updated the SRI integrity hash.

2. Robot status dot showed grey "offline" when the robot was online but
   simply hadn't had a voice turn today. Now treats a fresh (non-stale)
   perception device as online — perception liveness is the real
   connectivity signal; voice idleness is normal.

3. Version chip showed "vunknown" — the container has no .git checkout
   and the deploy never stamped a version. Added a BRIDGE_VERSION Docker
   build-arg (writes /app/.bridge-version); deploy-bridge-unraid.sh passes
   the HEAD short SHA.

4. Removed the Discord daemon card. It queried `systemctl zeroclaw-discord`
   locally — impossible inside the Unraid bridge container; ZeroClaw was
   retired in #36. Dropped the route, template, helper, and card section.

5. Smart Mode card advertised a model swap (claude-sonnet / mistral) that
   only fires on the retired tier1slim path. Relabeled to honest behaviour
   on the default PiVoiceLLM path ("Robot smart-mode pip … · model-swap
   pending (v2)"); still shows the swap on tier1slim deployments.

Also switched the page to natural document scroll (was a single-screen
overflow:hidden layout that pushed cards/feed below an unreachable fold);
the feed now caps at 70vh and scrolls internally.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs: add whole-repo vision alignment review (2026-05-29) + locked decisions

Captures the multi-agent audit output: canonical vision ground-truth, 50
verified findings, cross-cutting drift patterns, and the six maintainer
decisions (§0) that gate the remediation pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Remove Tier1Slim provider entirely (vision review decision E)

Tier1Slim was a two-tier voice provider whose tool escalation POSTed to the
retired ZeroClaw bridge (/api/voice/escalate), non-functional since the #36
cutover. Per the 2026-05-29 vision review it is removed in full:

- delete custom-providers/tier1_slim/, tests/test_tier1_slim.py, docs/tier1slim.md
- drop the /xiaozhi/admin/set-tier1slim-model route + shared_llm plumbing
  (http_server.py, websocket_server.py, portal_bridge.py)
- gut bridge.py's Tier1Slim hot-swap machinery: smart_mode is now toggle-only
  on the live PiVoiceLLM path (LED pip + flag); model-swap is v2 scope (decision D).
  Removes _dispatch_set_tier1slim_model, _tier1slim_target_for_smart_mode,
  TIER1SLIM_* env, DOTTY_VOICE_PROVIDER, and the startup voice-model reconcile.
- dashboard: drop VOICE_PROVIDER/MODEL_SWAP_ACTIVE; smart_mode card + host-detail
  LLM row now always show 'PiVoiceLLM · model-swap pending (v2)'
- remove the Tier1Slim block from .config.yaml.template; fix compose comment
- refresh pi_voice docstrings/comments that referenced Tier1Slim as a live peer

Functional no-op in production (live provider is PiVoiceLLM; the swap pushed to
an unselected provider). 269 tests pass; ruff clean.
Makefile LLAMA_SWAP wizard prompts (now orphaned) deferred to the operator-script batch.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Remove dashboard self-update/restart/reboot actions (vision review decision F)

The /ui Update, Restart, and Reboot-All actions invoked 'systemctl restart
zeroclaw-bridge' (a unit that doesn't exist in the container) and git-pulled
into the dead /root/zeroclaw-bridge install dir — they no-op'd while reporting
success (findings M6/M7). restart-bridge and reboot-all were already UI-orphaned.

Per decision F (remove the entire update apparatus):
- delete _collect_update_preview, _fetch_latest_remote_sha, _fetch_remote_tags,
  _parse_tag_version, _build_chip_context, _pull_and_install_bridge,
  _ssh_restart_xiaozhi, BRIDGE_INSTALL_DIR, and the preview-update/update-bridge/
  restart-bridge/reboot-all routes (~474 lines)
- reduce the header version chip to a static 'Bridge vX.Y.Z' label linking to the
  built commit (new _static_chip_context); version_chip.html drops the update button
- remove the #update-modal dialog from dashboard.html
- delete update_preview/update_result/reboot_all_result/restart_result templates

Deploys go through scripts/deploy-bridge-unraid.sh; restart:unless-stopped handles
restarts. 46 tests pass; ruff clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Fix operator tooling to the post-#36 four-container arch (H1, L20)

make doctor/status and dotty_doctor.py extracted a host via the first
'url: http://' line and probed :8080 labelled 'Bridge health' — but post-#36
that line is the llama-swap endpoint, so the check silently probed the wrong
service (finding H1). make setup prompted for a nonexistent ZeroClaw host;
make audit SSHed dietpi@<ZEROCLAW_HOST> (a powered-off RPi).

- doctor/status/dotty_doctor.py now derive the Docker host from the ws:// URL
  and health-check the dashboard (:8081) + dotty-behaviour (:8090)
- make audit drops the retired 'Bridge host (ZeroClaw host)' SSH block (the
  bridge is now a container on XIAOZHI_HOST, covered by the server-host check)
- make setup drops the ZEROCLAW_HOST/USER and now-orphaned LLAMA_SWAP_* prompts,
  validation, .env writes, sed substitutions, and WIZARD_PLACEHOLDERS entries
  (those placeholders left the templates with Tier1Slim)
- dotty_doctor.py gains --behaviour-url; --bridge-url now means the dashboard
- deploy-bridge-unraid.sh header: the legacy deploy-bridge.sh/install-bridge.sh
  are gone; flag the still-stale dotty-deploy-bridge skill

All changed targets parse; doctor --help OK; ruff clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* H2: share kid/smart-mode state across the xiaozhi + bridge containers

receiveAudioHandle.py (in the xiaozhi container) read kid/smart-mode from
/root/zeroclaw-bridge/state/* with no env or volume set, while the bridge
dashboard wrote them under /var/lib/dotty-bridge/state — disjoint, unshared
paths, so the firmware kid/smart LED pips desynced from the dashboard on every
reconnect (finding H2).

- docker-compose.yml.template: set DOTTY_KID_MODE_STATE / DOTTY_SMART_MODE_STATE
  on the xiaozhi service and bind-mount the bridge's state dir read-only
  (${DOTTY_STATE_DIR:-/mnt/user/appdata/dotty-bridge/state} -> /var/lib/dotty-bridge/state);
  also drop the now-deleted tier1_slim provider mount
- receiveAudioHandle.py + bridge.py: default the state paths to
  /var/lib/dotty-bridge/state (the bridge's dir) instead of the retired
  /root/zeroclaw-bridge, so reader and writer agree even without the env vars
- compose.all-in-one.yml: fix the dashboard port comment 8080 -> 8081

Writer (bridge) and reader (xiaozhi) now resolve to the same files. Not
verifiable until both containers redeploy. 49 tests pass; ruff clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs: dashboard port :8080 -> :8081 sweep + .env ZeroClaw cleanup (M1, L19, L27)

The admin dashboard listens on :8081 (:8080 is the llama-swap LLM endpoint).
Fix every dashboard reference across SETUP, quickstart, observability,
COMPATIBILITY, and variant-port-guide (also drop its dead POST /api/message
example — retired in #36). Strip the stale zeroclaw-bridge/ACP block from
.env.example, set PORT=8081, repoint ~/.zeroclaw state paths. llama-swap :8080
left intact.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs: re-attribute perception to dotty-behaviour; 11 consumers; story/security pending (M2, L12, L13, M9, A, B)

modes.md (authoritative) and architecture.md cited retired bridge.py _perception_*
methods and wrong consumer counts. Re-attribute to dotty-behaviour class names +
perception/state.py; adopt 11 consumer classes (running set config-gated); mark
story_time AND security as Phase 7/8 PENDING (SecurityCycle is scaffolding, per
decision A). dotty-behaviour README no longer claims to host the dashboard — it
serves data consumed by bridge.py; drop the abandoned dashboard-port slice.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs: brain voice-tools 5 -> 7, drop nonexistent set_led, restore remember (H3, M8)

dotty-pi/README listed a nonexistent set_led tool (LEDs are firmware-owned) and
omitted the real remember. Canonicalize the 7 registered tools (memory_lookup,
remember, recall_person, remember_person, think_hard, take_photo, play_song);
refresh the dotty-pi-ext README table/status/layout; fix package.json description;
update turn_logger.ts + index.ts comments to post-#36 (PiVoiceLLM is the live
write path, not 'dormant pre-cutover').

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs: kid-safety/emoji/egress wording to live PiVoiceLLM reality (C, D, M3, M12, L18)

The emoji/kid-safety docs described retired ZeroClaw bridge.py symbols
(_ensure_emoji_prefix, _build_sandwich_prompt, _content_filter, _BLOCKED_WORDS_RE,
ACPClient) as the live mechanism. Repoint to the persona prompt + .config.yaml
prompt: + textUtils.build_turn_suffix. Per decision C: the kid-mode sandwich
SHIPS on the live path; the gap (#22) is specifically the absent blocked-words
content filter (now in no live code). voice-mode-entry: face-greeter is
dotty-behaviour/consumers/face_greeter.py (not bridge.py); drop the removed
Discord-daemon reference.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* cleanup: stale comments, dead code, mkdocs nav, broken link (L1,L2,L4,L6,L7,L9,L14,L15,L21,L22,M13)

- textUtils/textMessageHandlerRegistry/ota_handler: repoint docstrings off the
  retired zeroclaw-bridge to dotty-behaviour (:8090) / current importers
- remove dead code: SimpleHttpServer._get_websocket_url, openai_compat._ensure_emoji_prefix
- pi_voice/README: drop stale 'not yet done' sandwich claims + Tier1Slim rollback/
  see-also refs; fix the diagram flags (qwen3.5:4b, --thinking off, no --extensions)
- household/registry.py: default doc ~/.zeroclaw -> STATE_DIR; perception.py: add head_pet_ended
- style.md: fix ../protocols.md -> ./protocols.md; compose.local.override: drop undefined dotty network
- mkdocs.yml: add modes/quickstart/wake-word/proactive-greetings/llama-swap-cookbook to nav (tier1slim.md was deleted)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs: finish Tier1Slim removal + post-#36 framing across hub docs (M11)

Sweep the remaining Tier1Slim/ZeroClaw prose out of the canonical docs:
- CLAUDE.md / README.md: one alternate provider (OpenAICompat), 7 voice tools,
  dashboard :8081, drop set-tier1slim-model/shared_llm, #115 dashboard rewire done
- llm-backends/faq/voice-pipeline/quickstart/architecture/protocols: drop Tier1Slim
  as a current backend; PiVoiceLLM default + OpenAICompat alternate
- brain.md (M11): smart-mode does NOT swap the backend model on the live path
  (v2 scope); the swap existed only on the removed Tier1Slim
- ROADMAP/CONTRIBUTING/SECURITY/cookbook: drop Tier1Slim provider/dir refs;
  CONTRIBUTING dashboard port 8080 -> 8081; remove the deleted tier1slim.md links
Remaining Tier1Slim mentions are explicit past-tense 'removed' history.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* cleanup: repoint stale /root/.zeroclaw + /root/zeroclaw-bridge code defaults (L11)

bridge.py VOICE_MEMORY_DB / _ADMIN_WORKSPACE_DIR and dashboard.py LOG_DIR
still defaulted to the retired RPi paths (env vars already override them in
the container deploy). Repoint defaults to /var/lib/dotty-bridge/*; drop the
dangling 'See bridge/MEMORY-INDEX.md' pointer (that file doesn't exist).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* changelog: document the 2026-05-29 vision-alignment pass

Consolidate the [Unreleased] section into single Added/Changed/Removed blocks
and record the alignment pass (Tier1Slim removal, dashboard self-update removal,
:8081 port fixes, 11-consumer/7-tool corrections, shared state volume). Also
fix the stale 'port 8080' in the prior post-#36 doc-reconcile entry.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* #105: instrument ASR with per-utterance metrics (logging only)

Logs duration, RMS, peak, mean no_speech_prob / avg_logprob, language
probability, and segment count per recognised utterance, so a reject
gate separating close-talk speech from ambient TV/podcast audio can be
tuned on measured distributions. Also materialises the faster-whisper
segments generator to a list before it's consumed twice. No behaviour
change; strip the ASR-METRICS block once gate thresholds are chosen.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test(bridge): cover #128 dashboard render fixes

Adds DashboardRenderFixTests guarding the repairs from PR #128 against
regression: /ui/discord now 404s (dead zeroclaw-discord card removed),
the smart-mode partial still renders, and its card is honest about the
live PiVoiceLLM path performing no model swap ("model-swap pending").

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs: add YAML frontmatter to vision-alignment review (fix CI)

The Frontmatter Check job requires every non-symlinked docs/*.md to
start with a `---` YAML block. This file led with its `#` heading,
failing CI on #129. Add a title/description block matching the
convention in architecture.md / modes.md / brain.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (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.

2 participants