Skip to content

Wave 9 #125 — Phase 1 catalog UI proper: render 28 connection cards via format_connection_card() - #129

Merged
bernardc6 merged 1 commit into
mainfrom
subagent/phase1-catalog-ui-cards
Aug 16, 2026
Merged

Wave 9 #125 — Phase 1 catalog UI proper: render 28 connection cards via format_connection_card()#129
bernardc6 merged 1 commit into
mainfrom
subagent/phase1-catalog-ui-cards

Conversation

@bernardc6

Copy link
Copy Markdown
Collaborator

Context

Phase 1 catalog UI proper — render 28 connection cards via format_connection_card() so every device the catalog references surfaces as a glanceable card with a coloured state chip + tier pill + Connect button. Bernard's chat #7588-7591 asked for "an app-store-style interface, very clearly laid out, easy to use for novices"; the cards are the foundation of that.

Slice #125 wires the Wave 9 #118 connection_card primitive into scripts/build_catalog.py so the render layer is no longer hand-maintained / drift-prone — every catalog page is regenerated from connections/<slug>/connection.yml via the inventory.

This closes Directive Priority #1 (the customer-facing app-store interface).

User-facing

When I open RoamCore's catalog, every device shows up as a card with a coloured chip telling me whether it's ready to use, needs a small action from me, or has hit a problem — and who built it honestly (RoamCore, community, or experimental).

GOLDEN.md alignment

  • P1 (novice-first UX) — chips + pills + Connect → are glanceable; the novice path no longer needs to read prose to understand who built a connection or whether it's ready.
  • P6 (OpenClaw first-class) — each row carries data-connection-name="<title>" so an OpenClaw reader can locate the card by class + name without parsing the page text.
  • E1 (customer-facing repo)docs/catalog/index.md + per-category landing pages are the user-facing MkDocs surface; the new format reads like an app-store card grid.
  • E2 (documentation-driven) — the catalog IS the documentation; the render layer is now data-driven (catalog regeneration = single CLI invocation).
  • E4 (git fast mode) — direct-to-main on subagent/<slug>; the slice ships without a review gate.
  • E7 (naming) — chips use rc-state-chip--* + rc-tier--* selectors and rc-connect-button, all kebab-case per the rc-entity-naming doc.

Anti-patterns avoided

  • ❌ Wide PRs that mix networking + UI + infra — pure docs/Python/tests scope (46 files but 38 are auto-generated catalog pages).
  • ❌ Adding advanced features before the novice path is solid — this IS the novice path foundation.
  • ❌ Hand-editing generated files — docs/catalog/index.md and the per-category indexes are outputs of scripts/build_catalog.py, never inputs.
  • ❌ Committing secrets — no tokens / passwords / API keys in the diff; the catalog-ui-cards-smoke.sh assertion 4 enforces this on the rendered output (regex-matched against JWT-shaped and api-key-shaped literals in the catalog md files).

Changes

  • scripts/build_catalog.py: +60/-25 — import connection_card; rewrite render_catalog_index() to emit one card per Connection with tier legend + per-category sections + the IKEA intro; rewrite render_category_index() to emit cards per category item; add state+reason to inventory_items_for() so the inventory file now carries the data layer's state for downstream consumers.
  • scripts/build_catalog_lib.py: +17/-0 — add state: str = "Available" and reason: str = "" to the Connection dataclass; populate both from raw.get("state") / raw.get("reason") in curate_connection() with the "Available" default so a missing YAML field still renders.
  • scripts/check.sh: +8/-0 — wire catalog-ui-cards-smoke.sh into the core-only chain right after catalog-state-chip-smoke.sh (the Wave 9 Wave 9 #122.d.iv — Phase 6 Tailscale wizard: mDNS roamcore.local fallback (Gate E 'local survives Tailscale failure') #118 smoke this slice builds on).
  • scripts/checks/catalog-ui-cards-smoke.sh: +384/-0 (NEW) — 6 assertion blocks: ≥20 cards in main index; every card has state chip + tier chip + Connect button; per-category card count == YAML count; no secret-like literal in generated output; IKEA shape (tier legend + intro + per-category sections); every CSS class the render layer emits exists in docs/styles/rc.css.
  • homeassistant/packages/tests/test_catalog_ui_cards.py: +582/-0 (NEW) — 12 pytest contract tests covering all 4 chip CSS classes, per-category count, inventory-driven rendering, format_connection_card argument wiring, "Available" fallback.
  • docs/catalog/index.md: +309/-[N] (regenerated) — main catalog index with tier legend + intro + 28 cards in 9 categories (power/automation/comfort/connectivity/location/maintenance/misc/safety/security/water).
  • 8 of the 10 spec-listed per-category <category>/index.md pages regenerated (power has 0 YAMLs so it was not regenerated; the existing hand-crafted power/ pages are preserved unchanged).
  • connections/_all_connections_inventory.yml: +82/-0 — inventory now carries state + reason per item.
  • docs/catalog/_inventory.yml: +74/-[N] — same.
  • docs/catalog/.build_catalog_manifest.txt: +1 — now lists the new automation/index.md + automation/mode.md files.
  • docs/catalog/automation/ + docs/catalog/misc/hub-backup.md: newly generated by build_catalog.py.

Verification

  • bash scripts/checks/catalog-ui-cards-smoke.sh: 20 PASS / 0 FAIL (exits 0; the 6 assertion blocks all green).
  • python3 -m pytest homeassistant/packages/tests/test_catalog_ui_cards.py -v: 12 passed in 4.20s (exits 0).
  • python3 -m pytest homeassistant/packages/tests/ -v (regression sweep): 150 passed in 5.31s.
  • bash scripts/check.sh --core-only: GREEN exit 0 — the new smoke chains in cleanly after catalog-state-chip-smoke.sh.
  • Spec secret check: git diff --cached | grep -iE 'token|secret|password|api_key' — only matches are script comments + assertion labels (no actual secret values).

Rollback

git revert <this-commit-sha> — the only on-disk state is the catalog index + per-category index pages (auto-regenerated by scripts/build_catalog.py) + the new Connection.state/reason defaults. Reverting restores the previous bullet-list catalog shape and removes the new files. No upstream entanglements: no Proxmox / HA / OpenWrt / vmbr0 / network changes; no secrets touched.

Source

…ormat_connection_card() so every device the catalog references surfaces as a glanceable card with a coloured state chip + tier pill + Connect button. Bernard's chat #7588-7591 asked for "an app-store-style interface, very clearly laid out, easy to use for novices"; the cards are the foundation of that. Slice #125 wires the Wave 9 #118 connection_card primitive into build_catalog.py so the render layer is no longer hand-maintained / drift-prone — every catalog page is regenerated from connections/<slug>/connection.yml via the inventory.

User-facing: When I open RoamCore's catalog, every device shows up as a card with a coloured chip telling me whether it's ready to use, needs a small action from me, or has hit a problem — and who built it honestly (RoamCore, community, or experimental).

GOLDEN.md alignment:
  P1 (novice-first UX) — chips + pills + Connect → are glanceable; the
     novice path no longer needs to read prose to understand who built
     a connection or whether it's ready.
  P6 (OpenClaw first-class) — each row carries
     data-connection-name="<title>" so an OpenClaw reader can locate
     the card by class + name without parsing the page text.
  E1 (customer-facing repo) — docs/catalog/index.md + per-category
     landing pages are the user-facing MkDocs surface; the new format
     reads like an app-store card grid.
  E2 (documentation-driven) — the catalog IS the documentation; the
     render layer is now data-driven (catalog regeneration = single
     CLI invocation).
  E4 (git fast mode) — direct-to-main on subagent/<slug>; the slice
     ships without a review gate.
  E7 (naming) — chips use rc-state-chip--* + rc-tier--* selectors and
     rc-connect-button, all kebab-case per the rc-entity-naming doc.

Anti-patterns avoided:
  ❌ Wide PRs that mix networking + UI + infra — pure docs/Python/
     tests scope (46 files but 38 are auto-generated catalog pages).
  ❌ Adding advanced features before the novice path is solid — this
     IS the novice path foundation.
  ❌ Hand-editing generated files — docs/catalog/index.md and the
     per-category indexes are outputs of scripts/build_catalog.py,
     never inputs.
  ❌ Committing secrets — no tokens / passwords / API keys in the
     diff; the catalog-ui-cards-smoke.sh assertion 4 enforces this
     on the rendered output (regex-matched against JWT-shaped and
     api-key-shaped literals in the catalog md files).

Changes:
  - scripts/build_catalog.py: +60/-25 — import connection_card;
    rewrite render_catalog_index() to emit one card per Connection
    with tier legend + per-category sections + the IKEA intro;
    rewrite render_category_index() to emit cards per category item;
    add state+reason to inventory_items_for() so the inventory file
    now carries the data layer's state for downstream consumers.
  - scripts/build_catalog_lib.py: +17/-0 — add `state: str =
    "Available"` and `reason: str = ""` to the Connection dataclass;
    populate both from raw.get("state") / raw.get("reason") in
    curate_connection() with the "Available" default so a missing
    YAML field still renders.
  - scripts/check.sh: +8/-0 — wire catalog-ui-cards-smoke.sh into the
    core-only chain right after catalog-state-chip-smoke.sh (the
    Wave 9 #118 smoke this slice builds on).
  - scripts/checks/catalog-ui-cards-smoke.sh: +384/-0 (NEW) — 6
    assertion blocks: ≥20 cards in main index; every card has state
    chip + tier chip + Connect button; per-category card count ==
    YAML count; no secret-like literal in generated output; IKEA
    shape (tier legend + intro + per-category sections); every CSS
    class the render layer emits exists in docs/styles/rc.css.
  - homeassistant/packages/tests/test_catalog_ui_cards.py:
    +582/-0 (NEW) — 12 pytest contract tests covering: (1) main
    catalog renders one card per non-excluded item; (2) ≥20 card
    sanity guard; (3) every card carries the 4 chip CSS classes
    (state chip, tier chip, Connect button, row wrapper); (4)
    render_category_index emits the right per-category count; (5)
    render_category_index emits the expected primitives in each
    card; (6) format_connection_card is called with the right args
    per item (monkeypatched spy); (7) the same calls happen per-
    category; (8) render layer is inventory-driven (synthetic
    Connection objects render the same chip classes as live data);
    (9) state field falls back to "Available" when missing.
  - docs/catalog/index.md: +309/-[N] (regenerated by
    build_catalog.py) — main catalog index with tier legend +
    intro + 28 cards in 9 categories (power/automation/comfort/
    connectivity/location/maintenance/misc/safety/security/water).
  - docs/catalog/<category>/index.md + <slug>.md: regenerated for
    9 of the 10 spec-listed categories (power has 0 YAMLs so it
    was not regenerated; the existing hand-crafted power/ pages
    are preserved unchanged).
  - connections/_all_connections_inventory.yml: +82/-0 — inventory
    now carries state: + reason: per item (auto-emitted by
    build_catalog.py).
  - docs/catalog/_inventory.yml: +74/-[N] — same.
  - docs/catalog/.build_catalog_manifest.txt: +1 — now lists the
    new automation/index.md + automation/mode.md files.
  - docs/catalog/automation/ + docs/catalog/misc/hub-backup.md:
    newly generated by build_catalog.py from connection.yml files
    that were previously not getting full catalog coverage.

Verification:
  - bash scripts/checks/catalog-ui-cards-smoke.sh: 20 PASS / 0 FAIL
    (exits 0; the 6 assertion blocks all green)
  - python3 -m pytest homeassistant/packages/tests/test_catalog_ui_cards.py -v:
    12 passed in 4.20s (exits 0)
  - python3 -m pytest homeassistant/packages/tests/ -v (regression
    sweep across the existing test rigs): 150 passed in 5.31s
  - bash scripts/check.sh --core-only: GREEN exit 0 (the new smoke
    chains in cleanly after catalog-state-chip-smoke.sh)
  - bash scripts/check.sh --core-only full output ends with:
    "✓ all requested smoke checks passed."
  - Spec secret check: git diff --cached | grep -iE 'token|secret|
    password|api_key' — only matches are script comments + assertion
    labels (no actual secret values); the smoke's stricter regex
    (api_key/hacs_token/secret_key/etc. with literal value patterns)
    returns empty.

Rollback: git revert <this-commit-sha> — the only on-disk state is
the catalog index + per-category index pages (auto-regenerated by
scripts/build_catalog.py) + the new Connection.state/reason
defaults. Reverting restores the previous bullet-list catalog shape
and removes the new files. No upstream entanglements: no Proxmox /
HA / OpenWrt / vmbr0 / network changes; no secrets touched.
from pathlib import Path
from unittest import mock

import pytest
@bernardc6
bernardc6 merged commit 1627c73 into main Aug 16, 2026
2 checks passed
@bernardc6
bernardc6 deleted the subagent/phase1-catalog-ui-cards branch August 16, 2026 07:08
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.

1 participant