Skip to content

0.10.0 — the dashboard cards ship with the integration - #53

Merged
amiel-35 merged 3 commits into
mainfrom
cards-bundled
Oct 20, 2026
Merged

amiel-35 merged 3 commits into
mainfrom
cards-bundled

Conversation

@amiel-35

@amiel-35 amiel-35 commented Oct 20, 2026 •

Copy link
Copy Markdown
Owner

Response to the HACS default-store review (hacs/default#10724, hacs/default#10725): the dashboard cards ship inside the integration. Decision record docs/ADR/0024-cards-ship-with-the-integration.md, contract v0.10 addendum in docs/contract.md. Three commits: the change (29c0da1), the fixes from the independent judge (a0ed463), one closed test gap (d408a8a). Not merged, not tagged, not released.

Summary

  1. One repository, one version. The card sources move to cards/ (from notify-switchboard-cards d7bc8e9, unchanged except paths, the documentation URL and a guard on the card-picker entries). The build goes to custom_components/notify_switchboard/www/ and is committed, because the release zip ships what is committed. The cards take the integration's version (0.10.0); a test checks that cards/package.json matches manifest.json.
  2. Served by the integration. async_setup serves www/ at /notify_switchboard. aiohttp silently accepts a second static resource on a prefix that is already taken, so the router is checked first: if the path is taken, nothing is served, a warning is logged and no resource is written.
  3. Registered as a dashboard resource. On each entry setup, idempotently, there is exactly one module resource /notify_switchboard/notify-switchboard-cards.js?v=<version>. It is created if missing, moved to the current version, put back to module if needed, and duplicates are removed. An absolute URL to the same path counts as ours. With YAML resources the YAML is left untouched and the bundle is loaded with add_extra_js_url. Removing the entry removes the resource. A failure never fails the entry: it is logged and the router keeps working.
  4. Migration. A new repairs issue, legacy_cards_resource, appears while any other resource still loads notify-switchboard-cards.js (/hacsfiles/…, /local/community/…, /local/…). That resource belongs to HACS, so the integration never deletes it.
  5. Docs. docs/cards.md (the former cards README), the README, ARCHITECTURE, CONTRIBUTING, known issues (a stale resource after uninstalling through HACS or disabling the entry), CHANGELOG 0.10.0, and the cards' changelog in docs/history/cards-changelog.md.

Tests

  • ruff, ruff format --check, mypy (strict) and pytest are green: 486 unit tests and 392 acceptance tests. cards.py is at 99 %. Cards: lint, format, typecheck, 179 vitest tests and the size budget (22 kB gzip) pass. The build is byte-identical when rebuilt. hassfest run locally passes (0 invalid).
  • tests/unit/test_cards.py covers storage and YAML modes, reload, restart, version move, type fix, deduplication, removal, every legacy path, an absolute URL, a resource registration failure (a message still reaches a phone), a failing static path and a path already served by something else.
  • Independent judge (claude -p, Opus, separate context, hidden tests the author never saw, success criteria written from the HACS comments only):
    • First pass: no-go. A static-path clash was silent (my test mocked an exception that aiohttp never raises), and one starting state of the resources was left wrong.
    • Second pass: go. Every mutation was caught except one, closed in d408a8a.
  • End to end on the dev instance (HA 2026.9.1, Docker):
    • The bundle is served with HTTP 200 and the same hash. Exactly one module resource is written.
    • legacy_cards_resource is raised for the old /local/ copy, and its menu path exists.
    • With both copies loaded the dashboard still renders, and the existing card configs work unchanged.
    • The silence tile's buttons work.
    • After the old resource was removed: a single resource and a single bundle are left, and the repair is gone.
    • Not exercised: acknowledging an alert, since none was active.

Deviations and things to look at

  • A new workflow, .github/workflows/cards.yml. The checklist forbids workflow edits outside a CI sprint. This one only adds a job (lint, test, build, size, and a check that the committed build matches the sources). ADR-0024 explains why.
  • http, frontend and lovelace are after_dependencies, not dependencies. The code handles their absence, and the tests do not need the frontend package.
  • StaticPathConfig is imported from homeassistant.components.http.server, because the package re-exports it without __all__ and mypy strict complains.
  • While the old 0.2.0 bundle is also loaded, the card picker lists each card twice. The old bundle does not guard its entries; the new one does.
  • release.yml publishes as soon as the tag is pushed, without waiting for Validate or Cards, so tag only after green CI.

Core APIs cited (2026.9.1)

  • homeassistant/components/http/server.py: StaticPathConfig, async_register_static_paths → _async_register_static_paths (router.register_resource)
  • homeassistant/components/lovelace/__init__.py: LovelaceData (resources, resource_mode)
  • homeassistant/components/lovelace/resources.py: ResourceStorageCollection (async_get_info, async_create_item with res_type, async_update_item, async_delete_item), ResourceYAMLCollection.async_items
  • homeassistant/components/lovelace/const.py: LOVELACE_DATA, RESOURCE_CREATE_FIELDS
  • homeassistant/components/frontend/__init__.py: add_extra_js_url, remove_extra_js_url
  • homeassistant/helpers/issue_registry.py: async_create_issue, async_delete_issue
  • script/hassfest/dependencies.py: calc_allowed_references (after_dependencies allow the imports)

🤖 Generated with Claude Code

amiel-35 and others added 3 commits October 20, 2026 13:40
The HACS default-store review asked that the cards, a separate repository
up to cards 0.2.0, ship inside the integration (ADR-0024):

- card sources move to cards/ (from notify-switchboard-cards d7bc8e9); the
  build lands in custom_components/notify_switchboard/www/ and is committed;
  a Cards workflow checks the sources and that the build is up to date
- the integration serves www/ at /notify_switchboard and keeps one module
  resource at ?v=<version> (extra frontend module in YAML resource mode);
  removing the entry removes it; failures are logged, never fatal
- a repair, legacy_cards_resource, while an old copy of the bundle loads
- cards take the integration's version; contract v0.10 addendum; docs/cards.md

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…odule

From the independent judge's no-go:

- aiohttp keeps the first static resource on a prefix and accepts a second
  silently, so a clash on /notify_switchboard left the bundle at 404 with no
  warning; the router is now searched first, and on a clash nothing is
  served, a warning is logged and no dashboard resource is written
- an existing resource to the bundle of type `js` is put back to `module`
  (the bundle is an ES module); an absolute URL to the bundle counts as ours,
  not as a legacy copy
- the failure test now checks that a message still reaches a phone

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Closes the mutant the judge's second pass found surviving.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@amiel-35
amiel-35 merged commit 06e6e4e into main Oct 20, 2026
7 checks passed
@amiel-35
amiel-35 deleted the cards-bundled branch October 20, 2026 14:41
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