feat(ui): redesign diagnostics page and fix mobile table cropping#1013
Conversation
The name= parameter became mandatory for DB-registered listeners but five example apps were never updated, so every demo app failed at startup with ListenerNameRequiredError.
Tables flipped to table-layout: auto at 768px, which let unbreakable mono content (app keys, log messages) push tables past the viewport and crop columns mid-word. Fixed-layout tables now stay fixed on mobile: the apps table colgroup moves to CSS module classes so media queries can reallocate all width to the visible columns, app names truncate with ellipsis, and the log table's existing mobile colgroup widths take effect again.
The page was hidden because it read as a wall of noise: 20 identical 'running' rows, a giant empty state for boot issues, and a mostly empty telemetry card. Now a stats strip summarizes health at a glance, services render as a compact multi-column grid with anomalies sorted first (status/phase text only appears for non-running services), and the boot-issues and telemetry panels render only when there is something to show. Diagnostics joins the sidebar nav and command palette.
The old rule recommended table-layout: auto on mobile, which is the exact mechanism that caused the column cropping fixed in the previous commit. Document the fixed-layout + colgroup-reallocation pattern instead.
Adds the web-ui/diagnostics.md page, updates layout.md nav references from four to five top-level pages, and regenerates all docs screenshots to reflect the new sidebar item and diagnostics redesign.
Encodes the UI QA harness learned during the polish session: demo stack lifecycle and gotchas, a pages x viewports x themes screenshot matrix tool (tools/ui_qa_capture.py), screenshot critique agents anchored to DESIGN_RULES.md, and three task-driven personas (Morgan, Riley, Devon) that drive Playwright against the live demo to surface navigation dead ends that per-page review cannot see.
# Conflicts: # docs/_static/web_ui_detail_column_picker.png # docs/pages/web-ui/layout.md # mkdocs.yml
📝 WalkthroughWalkthroughThis PR adds a complete UI QA skill and Playwright screenshot CLI, delivers a Diagnostics page (component, styling, docs, nav, screenshot manifest, and tests), enforces fixed-layout mobile table behavior with new column classes, and adds explicit event listener names in example automations. ChangesUI QA Skill System
Diagnostics Page Feature
Apps Table Responsive Layout & Supporting Cleanup
Event Listener Naming in Example Handlers
Navigation E2E Test Updates
🎯 3 (Moderate) | ⏱️ ~25 minutes 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Pull request overview
This PR improves Hassette’s Web UI responsiveness and observability by fixing mobile table layout overflow, redesigning and surfacing the Diagnostics page (including navigation + docs), repairing broken example apps, and adding tooling/docs to support repeatable UI QA against the demo stack.
Changes:
- Keep fixed-layout tables fixed on mobile and reallocate colgroup widths via CSS Modules to prevent viewport overflow.
- Redesign Diagnostics with a stats strip + compact services grid, conditional boot/telemetry panels, and add it to sidebar + command palette + docs.
- Fix example apps by providing required
name=values on listener registrations; add UI QA capture tooling and supporting references.
Reviewed changes
Copilot reviewed 24 out of 37 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| tools/frontend/ui_qa_capture.py | New Playwright-based screenshot matrix capture script for UI QA. |
| tests/e2e/test_responsive.py | Updates mobile apps-table assertions to require fixed layout and no horizontal scroll. |
| tests/e2e/test_navigation.py | Adds Diagnostics to navigation coverage tests. |
| mkdocs.yml | Adds Diagnostics page to MkDocs nav. |
| frontend/src/styles/tables.css | Stops flipping .ht-table--fixed to auto layout at mobile breakpoints. |
| frontend/src/pages/diagnostics.tsx | Diagnostics redesign: stats strip, anomaly-first sorting, services grid, conditional panels. |
| frontend/src/pages/diagnostics.test.tsx | Updates diagnostics tests to match new conditional rendering and service row behavior. |
| frontend/src/pages/diagnostics.module.css | Converts services list into compact responsive grid with detailed-row spanning. |
| frontend/src/pages/apps.tsx | Moves apps table col widths from inline col styles to CSS module classes. |
| frontend/src/pages/apps.module.css | Implements breakpoint-based colgroup width reallocation + mobile truncation/ellipsis. |
| frontend/src/components/layout/sidebar.tsx | Adds Diagnostics to sidebar nav. |
| frontend/src/components/layout/palette-items.ts | Adds Diagnostics to command palette static pages. |
| frontend/DESIGN_RULES.md | Updates responsive table guidance (fixed layout + colgroup reallocation pattern). |
| examples/presence_tracker.py | Adds required name= to bus listener registration. |
| examples/motion_lights.py | Adds required name= to bus listener registrations. |
| examples/demo_stimulator.py | Adds required name= to bus listener registration. |
| examples/cover_scheduler.py | Adds required name= to bus listener registrations. |
| examples/climate_controller.py | Adds required name= to bus listener registrations. |
| docs/screenshots.yml | Adds Diagnostics screenshot capture entry; adjusts screenshot set. |
| docs/pages/web-ui/index.md | Links Diagnostics in Web UI overview task list. |
| docs/pages/web-ui/diagnostics.md | New Diagnostics docs page describing the redesigned UI and semantics. |
| .claude/skills/ui-qa/SKILL.md | Adds a UI QA skill doc describing matrix/persona workflows. |
| .claude/skills/ui-qa/references/personas.md | Adds task-driven UI personas for agent walkthrough QA. |
| .claude/skills/ui-qa/references/harness.md | Adds demo-stack lifecycle and QA workflow reference for UI QA runs. |
There was a problem hiding this comment.
Actionable comments posted: 6
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.claude/skills/ui-qa/references/harness.md:
- Around line 18-25: The fenced code block that begins with
"DEMO_HA_URL=http://localhost:NNNNN" is missing a language specifier; change the
opening fence from ``` to ```text so the block becomes ```text ... ``` (i.e.,
add the "text" specifier) in .claude/skills/ui-qa/references/harness.md to
satisfy the Markdown lint rule.
In `@frontend/src/pages/apps.module.css`:
- Around line 203-218: Remove the redundant width: 0 declarations for the hidden
column classes—.colError, .colRuns, .colLast, and .colActions—since those
columns are already hidden later with display: none via nth-child selectors;
locate the block defining .colError, .colRuns, .colLast, .colActions and delete
the width: 0 rules to simplify the CSS while leaving the display: none rules
intact.
In `@frontend/src/styles/tables.css`:
- Around line 77-82: The mobile override sets .ht-table--compact { table-layout:
auto } which conflicts with DESIGN_RULES.md and the components
execution-table.tsx and config-tab.tsx that do not use a <colgroup>; remove the
`@media` rule that switches .ht-table--compact to table-layout: auto (leaving
table-layout: fixed at all breakpoints) or alternatively implement the
colgroup/width reallocation pattern in execution-table.tsx and config-tab.tsx
and update the comment to reflect the new behavior; prefer deleting the mobile
override in frontend/src/styles/tables.css and keep the comment aligned with
DESIGN_RULES.md.
In `@tools/frontend/ui_qa_capture.py`:
- Line 63: The page.goto call is missing an explicit timeout; update the call to
pass an explicit timeout (e.g., timeout=30000 or a shared constant) to
page.goto(base_url + PAGES[page_name], wait_until="networkidle", timeout=...) so
the wait behavior is visible and follows the "explicit timeouts on external
calls" guideline; adjust any test config or imported timeout constant if your
project centralizes timeouts.
- Around line 75-86: Add a Google-style docstring to the main function
describing its purpose, arguments, and return behavior: document that main
parses CLI args (--base-url, --output-dir, --pages, --viewports, --themes),
calls capture_matrix(base_url, output_dir, pages, viewports, themes) to capture
screenshots, prints the count and output directory, and exits with status 0 on
success or 1 on failure; place the docstring immediately below the def main()
line and follow Google-style sections (Args, Returns) referencing the parsed
arguments and the exit code.
- Around line 51-72: The function capture_matrix is missing a Google-style
docstring; add a docstring immediately under its def that describes the function
purpose, lists and types/meanings of parameters (base_url, output_dir: Path,
pages: list[str], viewports: list[str], themes: list[str]), describes the return
value (int count of screenshots), and notes any side-effects (creates
output_dir, launches browser/uses VIEWPORTS, PAGES, ANIMATION_KILL_CSS,
SETTLE_MS). Keep the format consistent with other project docstrings (short
summary line, blank line, Args: ..., Returns: ..., and any Raises: if relevant).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 48cc6314-06b3-423f-98b7-d4a50581e5cc
⛔ Files ignored due to path filters (13)
docs/_static/web_ui_app_detail_code.pngis excluded by!**/*.pngdocs/_static/web_ui_app_detail_config.pngis excluded by!**/*.pngdocs/_static/web_ui_app_detail_handlers.pngis excluded by!**/*.pngdocs/_static/web_ui_app_detail_overview.pngis excluded by!**/*.pngdocs/_static/web_ui_apps.pngis excluded by!**/*.pngdocs/_static/web_ui_config.pngis excluded by!**/*.pngdocs/_static/web_ui_detail_command_palette.pngis excluded by!**/*.pngdocs/_static/web_ui_detail_log_drawer.pngis excluded by!**/*.pngdocs/_static/web_ui_detail_sidebar.pngis excluded by!**/*.pngdocs/_static/web_ui_detail_status_bar.pngis excluded by!**/*.pngdocs/_static/web_ui_diagnostics.pngis excluded by!**/*.pngdocs/_static/web_ui_handlers.pngis excluded by!**/*.pngdocs/_static/web_ui_logs.pngis excluded by!**/*.png
📒 Files selected for processing (24)
.claude/skills/ui-qa/SKILL.md.claude/skills/ui-qa/references/harness.md.claude/skills/ui-qa/references/personas.mddocs/pages/web-ui/diagnostics.mddocs/pages/web-ui/index.mddocs/screenshots.ymlexamples/climate_controller.pyexamples/cover_scheduler.pyexamples/demo_stimulator.pyexamples/motion_lights.pyexamples/presence_tracker.pyfrontend/DESIGN_RULES.mdfrontend/src/components/layout/palette-items.tsfrontend/src/components/layout/sidebar.tsxfrontend/src/pages/apps.module.cssfrontend/src/pages/apps.tsxfrontend/src/pages/diagnostics.module.cssfrontend/src/pages/diagnostics.test.tsxfrontend/src/pages/diagnostics.tsxfrontend/src/styles/tables.cssmkdocs.ymltests/e2e/test_navigation.pytests/e2e/test_responsive.pytools/frontend/ui_qa_capture.py
…ce rows The title attribute is invisible on touch devices and unreliable for assistive tech. The phase already renders inline for non-running services, where it carries diagnostic value.
- fix usage path and add docstrings + explicit goto timeout in ui_qa_capture.py - add language specifier to fenced block in ui-qa harness reference - align tables.css comment and DESIGN_RULES.md with the actual compact-table mobile behavior: --fixed tables stay fixed at every breakpoint; --compact flips to auto only because every visible mobile column is bounded
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
tools/frontend/ui_qa_capture.py (1)
66-84: 🧹 Nitpick | 🔵 Trivial | 💤 Low valueConsider wrapping browser lifecycle in try-finally for explicit cleanup.
While the
sync_playwright()context manager likely ensures cleanup, explicitly closing the browser in afinallyblock would make resource management more visible and robust if an exception occurs betweenbrowser.launch()andbrowser.close().♻️ Suggested refactor
output_dir.mkdir(parents=True, exist_ok=True) count = 0 with sync_playwright() as pw: browser = pw.chromium.launch() - for theme in themes: - for vp_name in viewports: - width, height = VIEWPORTS[vp_name] - context = browser.new_context(viewport={"width": width, "height": height}) - context.add_init_script(f"localStorage.setItem('hassette:theme', '\"{theme}\"');") - page = context.new_page() - for page_name in pages: - page.goto(base_url + PAGES[page_name], wait_until="networkidle", timeout=30_000) - page.evaluate(ANIMATION_KILL_CSS) - page.wait_for_timeout(SETTLE_MS) - out = output_dir / f"{page_name}--{vp_name}--{theme}.png" - page.screenshot(path=str(out), full_page=True) - print(f"captured {out}") - count += 1 - context.close() - browser.close() + try: + for theme in themes: + for vp_name in viewports: + width, height = VIEWPORTS[vp_name] + context = browser.new_context(viewport={"width": width, "height": height}) + context.add_init_script(f"localStorage.setItem('hassette:theme', '\"{theme}\"');") + page = context.new_page() + for page_name in pages: + page.goto(base_url + PAGES[page_name], wait_until="networkidle", timeout=30_000) + page.evaluate(ANIMATION_KILL_CSS) + page.wait_for_timeout(SETTLE_MS) + out = output_dir / f"{page_name}--{vp_name}--{theme}.png" + page.screenshot(path=str(out), full_page=True) + print(f"captured {out}") + count += 1 + context.close() + finally: + browser.close() return count🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tools/frontend/ui_qa_capture.py` around lines 66 - 84, Wrap the browser lifecycle from the call to pw.chromium.launch() in a try/finally so browser.close() is always executed (use the existing sync_playwright() but add try: ... finally: browser.close()), and similarly ensure each created context is closed if an exception occurs by wrapping the per-viewport creation of context (browser.new_context(...)) and its page usage in a try/finally that calls context.close() in the finally. Reference the symbols sync_playwright, browser (from pw.chromium.launch()), context (from browser.new_context), page, context.close(), and browser.close() when making the changes.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@tools/frontend/ui_qa_capture.py`:
- Around line 66-84: Wrap the browser lifecycle from the call to
pw.chromium.launch() in a try/finally so browser.close() is always executed (use
the existing sync_playwright() but add try: ... finally: browser.close()), and
similarly ensure each created context is closed if an exception occurs by
wrapping the per-viewport creation of context (browser.new_context(...)) and its
page usage in a try/finally that calls context.close() in the finally. Reference
the symbols sync_playwright, browser (from pw.chromium.launch()), context (from
browser.new_context), page, context.close(), and browser.close() when making the
changes.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: d511ffc6-4201-4907-b81d-158fca700384
📒 Files selected for processing (5)
.claude/skills/ui-qa/references/harness.mdfrontend/DESIGN_RULES.mdfrontend/src/pages/diagnostics.tsxfrontend/src/styles/tables.csstools/frontend/ui_qa_capture.py
🤖 I have created a release *beep* *boop* --- ## [0.42.0](v0.41.0...v0.42.0) (2026-06-11) ### Features * **ui:** redesign diagnostics page and fix mobile table cropping ([#1013](#1013)) ([3fee929](3fee929)) ### Bug Fixes * coerce numeric strings in Comparison condition ([#1003](#1003)) ([ae751a6](ae751a6)) * Docker semver images not published since v0.39.0 ([#999](#999)) ([ee1d804](ee1d804)) ### Documentation * rewrite documentation site ([#970](#970)) ([32d945e](32d945e)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). --------- Co-authored-by: hassette-release-please[bot] <273458268+hassette-release-please[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Summary
table-layout: autoat ≤768px, letting unbreakable mono content (app keys, log messages) push tables past the viewport. Tables now stay fixed on mobile; the apps table's colgroup widths moved into CSS module classes so media queries reallocate all width to the visible columns, app names truncate with ellipsis, and the log table's existing per-column mobile widths take effect again.DESIGN_RULES.mdupdated to document the fixed-layout + colgroup-reallocation pattern.web-ui/diagnostics.md, fitted to the new task-oriented docs structure from docs: rewrite documentation site #970), and all docs screenshots are regenerated.name=on bus listener registrations and failed at startup withListenerNameRequiredError(CI guard for this filed as Add CI check that example apps initialize against the framework #1011)..claude/skills/ui-qa/+tools/frontend/ui_qa_capture.py): demo-stack harness reference, a pages × viewports × themes screenshot matrix capture tool, and three task-driven personas that drive Playwright against the live demo to surface navigation dead ends.Verification
tsc --noEmit, eslint, prettier-n autopass in isolation — filed as Fix flaky drawer backdrop e2e tests under parallel runs #1006)mkdocs build --strictafter merging the docs: rewrite documentation site #970 docs rewriteRelated issues
Issues found during this session and filed separately: #1005 (stale module on reload), #1006 (flaky e2e), #1007–#1010 (UI polish), #1011 (examples CI guard). Partially related existing work: #900, #903.
Summary by CodeRabbit
New Features
Bug Fixes
Documentation
Tests
Chores