Wave 9 #125 — Phase 1 catalog UI proper: render 28 connection cards via format_connection_card() - #129
Merged
Merged
Conversation
…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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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_cardprimitive intoscripts/build_catalog.pyso the render layer is no longer hand-maintained / drift-prone — every catalog page is regenerated fromconnections/<slug>/connection.ymlvia 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
data-connection-name="<title>"so an OpenClaw reader can locate the card by class + name without parsing the page text.docs/catalog/index.md+ per-category landing pages are the user-facing MkDocs surface; the new format reads like an app-store card grid.subagent/<slug>; the slice ships without a review gate.rc-state-chip--*+rc-tier--*selectors andrc-connect-button, all kebab-case per the rc-entity-naming doc.Anti-patterns avoided
docs/catalog/index.mdand the per-category indexes are outputs ofscripts/build_catalog.py, never inputs.Changes
scripts/build_catalog.py: +60/-25 — importconnection_card; rewriterender_catalog_index()to emit one card per Connection with tier legend + per-category sections + the IKEA intro; rewriterender_category_index()to emit cards per category item; add state+reason toinventory_items_for()so the inventory file now carries the data layer's state for downstream consumers.scripts/build_catalog_lib.py: +17/-0 — addstate: str = "Available"andreason: str = ""to the Connection dataclass; populate both fromraw.get("state")/raw.get("reason")incurate_connection()with the "Available" default so a missing YAML field still renders.scripts/check.sh: +8/-0 — wirecatalog-ui-cards-smoke.shinto the core-only chain right aftercatalog-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 indocs/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).<category>/index.mdpages 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 newautomation/index.md+automation/mode.mdfiles.docs/catalog/automation/+docs/catalog/misc/hub-backup.md: newly generated bybuild_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 aftercatalog-state-chip-smoke.sh.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 byscripts/build_catalog.py) + the newConnection.state/reasondefaults. 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
/home/bernard/.openclaw/workspace/memory/cross-project/unified-backlog.mdrow Wave 9 #123.d.iv — Phase 7 Gate D (agent integration) acceptance test #125