feat: auto-create Lovelace view on first setup - #37
Conversation
WalkthroughThe integration now automatically creates a "Zone Mapper" Lovelace view on the default dashboard during first setup (v1.1.0), with a user-configurable opt-out option. A new options flow, frontend seeding logic, and dependency declarations were added to support this feature. Changes
Sequence DiagramsequenceDiagram
participant HA as Home Assistant<br/>(Startup)
participant Setup as Integration<br/>Setup
participant Event as Event<br/>Listener
participant Frontend as Frontend<br/>Seeding
participant Lovelace as Lovelace<br/>Dashboard
participant Config as Dashboard<br/>Config
HA->>Setup: async_setup_entry()
Setup->>Setup: Check CONF_AUTO_CREATE_VIEW<br/>from options
Setup->>Event: Schedule seeding on<br/>EVENT_HOMEASSISTANT_STARTED
Setup->>Setup: Set flag check in<br/>entry.data
HA->>Event: Fire EVENT_HOMEASSISTANT_STARTED
Event->>Frontend: async_seed_default_view()
Frontend->>Lovelace: Resolve default dashboard<br/>from hass.data
Lovelace-->>Frontend: Dashboard reference
Frontend->>Frontend: Check if YAML-mode
alt YAML-mode detected
Frontend->>Frontend: Log warning & skip
else Storage-mode
Frontend->>Config: Load existing config
Config-->>Frontend: Dashboard config
Frontend->>Frontend: Check if card<br/>already present
alt Card exists
Frontend->>Frontend: Skip seeding
else Card not found
Frontend->>Frontend: Create placeholder<br/>view with Zone Mapper card
Frontend->>Config: Update & save config
Config-->>Frontend: Save confirmed
end
end
Frontend-->>Setup: Return True (seeding complete)
Setup->>Setup: Update SEEDED_DEFAULT_VIEW_FLAG<br/>in entry.data
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
On first config entry, append a "Zone Mapper" view to the default storage-mode Lovelace dashboard containing a placeholder card. Users no longer have to add the card by hand after installing the integration. - Options flow adds an `auto_create_view` toggle (default true) - YAML-mode dashboards are never rewritten; they log a paste-ready snippet instead - Seeding is gated by a per-entry flag so deleting the view doesn't cause it to be recreated on reload - Handles dashboards with no stored config (ConfigNotFound) by seeding a minimal config that preserves the default auto-generated first view via an "original-states" strategy - Resolves the default dashboard via the "lovelace" key (modern HA) with a fallback to the legacy None key Bumps manifest to 1.1.0.
058f87d to
21dbb7e
Compare
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
README.md (1)
66-66:⚠️ Potential issue | 🟡 MinorOutdated troubleshooting guidance.
Since the integration is UI-configurable (config flow creates the entry without YAML, as reflected in step 2 where YAML is now marked "Optional"), the troubleshooting tip telling users to confirm
zone_mapper:is present inconfiguration.yamlwill mislead users who set up via the UI. Consider rephrasing to "confirm the integration is added via Settings → Devices & Services (or via YAML if you chose that route)".🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@README.md` at line 66, Update the outdated troubleshooting line that currently reads "Service not found: confirm the integration is installed and `zone_mapper:` is present in configuration.yaml" to reflect the UI-configurable nature of the integration: replace it with guidance that tells users to confirm the integration is added via Settings → Devices & Services (or via YAML if they chose that route), and make sure the phrase "Service not found" remains so the troubleshooting section still matches that error message.
🧹 Nitpick comments (1)
custom_components/zone_mapper/__init__.py (1)
500-522: Seeding flag persisted inentry.data— considerentry.optionsorhass.datainstead.
entry.datais conventionally the immutable config created during the initial flow; using it for runtime state (seeding status) is a slight abuse and every call toasync_update_entry(entry, data=...)will fire update listeners (none are registered today, but adding one later could produce surprising reload loops). Two alternatives worth considering:
- Store the flag in
entry.optionsalongsideCONF_AUTO_CREATE_VIEW(still persistent, semantically closer to user-mutable state).- Store in
hass.data[DOMAIN](non-persistent — view won't re-seed within the same HA run, which is usually enough since_config_contains_cardalready dedupes across restarts).Not blocking — current approach works — but worth a thought before 1.1.0 ships.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@custom_components/zone_mapper/__init__.py` around lines 500 - 522, The code currently persists the SEEDED_DEFAULT_VIEW_FLAG into entry.data inside _schedule_view_seeding and calls hass.config_entries.async_update_entry(entry, data=...), which misuses immutable config data and may trigger update listeners; change this to either persist the flag in entry.options by updating via hass.config_entries.async_update_entry(entry, options={**entry.options, SEEDED_DEFAULT_VIEW_FLAG: True}) (keep the flag alongside CONF_AUTO_CREATE_VIEW), or store it non-persistently in hass.data[DOMAIN] keyed by entry.entry_id (e.g. hass.data.setdefault(DOMAIN, {})[entry.entry_id] = {SEEDED_DEFAULT_VIEW_FLAG: True}) and check that instead of entry.data; update all checks in _schedule_view_seeding and the inner _run to read the chosen location instead of entry.data and stop writing to entry.data.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.github/workflows/validate.yml:
- Around line 8-14: Confirm the repository's actual default branch name and
update .github/workflows/validate.yml so only that exact-cased branch appears in
both the "branches:" lists under the push and pull_request jobs; remove the
incorrect-cased duplicate (either drop "Main" or drop "main") so the workflow
only references the real default branch name.
In `@custom_components/zone_mapper/frontend.py`:
- Around line 148-155: The except block in async_seed_default_view currently
catches all Exceptions and returns True, which causes SEEDED_DEFAULT_VIEW_FLAG
to be set and prevents retries; change the handler to distinguish error types:
catch AttributeError/KeyError (API drift/structural issues) and keep returning
True after logging, but for transient I/O or storage errors (e.g., exceptions
raised by async_save or underlying IO errors) return False so setup can retry;
implement this by inspecting the caught exception instance (or re-raise for
unexpected types) in the except clause and returning False for
IOError/OSError/any storage-related exceptions while preserving the existing
_LOGGER.warning with exc_info for the other cases.
- Around line 57-73: The priority for selecting the default Lovelace dashboard
in _resolve_default_dashboard is reversed and its comment is incorrect; change
the lookup to prefer dashboards.get(None) first and fall back to
dashboards.get("lovelace"), and update the comment to state that the default
Overview is registered under the None key with "lovelace" as a secondary/legacy
fallback so the function returns dashboards.get(None) or
dashboards.get("lovelace").
---
Outside diff comments:
In `@README.md`:
- Line 66: Update the outdated troubleshooting line that currently reads
"Service not found: confirm the integration is installed and `zone_mapper:` is
present in configuration.yaml" to reflect the UI-configurable nature of the
integration: replace it with guidance that tells users to confirm the
integration is added via Settings → Devices & Services (or via YAML if they
chose that route), and make sure the phrase "Service not found" remains so the
troubleshooting section still matches that error message.
---
Nitpick comments:
In `@custom_components/zone_mapper/__init__.py`:
- Around line 500-522: The code currently persists the SEEDED_DEFAULT_VIEW_FLAG
into entry.data inside _schedule_view_seeding and calls
hass.config_entries.async_update_entry(entry, data=...), which misuses immutable
config data and may trigger update listeners; change this to either persist the
flag in entry.options by updating via
hass.config_entries.async_update_entry(entry, options={**entry.options,
SEEDED_DEFAULT_VIEW_FLAG: True}) (keep the flag alongside
CONF_AUTO_CREATE_VIEW), or store it non-persistently in hass.data[DOMAIN] keyed
by entry.entry_id (e.g. hass.data.setdefault(DOMAIN, {})[entry.entry_id] =
{SEEDED_DEFAULT_VIEW_FLAG: True}) and check that instead of entry.data; update
all checks in _schedule_view_seeding and the inner _run to read the chosen
location instead of entry.data and stop writing to entry.data.
🪄 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: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: a5f0a890-22dc-4fa9-85ea-541a0861a0ba
📒 Files selected for processing (8)
.github/workflows/validate.ymlREADME.mdcustom_components/zone_mapper/__init__.pycustom_components/zone_mapper/config_flow.pycustom_components/zone_mapper/const.pycustom_components/zone_mapper/frontend.pycustom_components/zone_mapper/manifest.jsoncustom_components/zone_mapper/translations/en.json
| def _resolve_default_dashboard(hass: HomeAssistant) -> object | None: | ||
| """Return the default Lovelace dashboard object, or None if unavailable.""" | ||
| lovelace_data = hass.data.get("lovelace") | ||
| if lovelace_data is None: | ||
| _LOGGER.debug("Zone Mapper: lovelace data not available yet.") | ||
| return None | ||
| dashboards = getattr(lovelace_data, "dashboards", None) | ||
| if not isinstance(dashboards, dict): | ||
| _LOGGER.warning( | ||
| "Zone Mapper: unexpected lovelace dashboards shape; skipping" | ||
| " auto-view seeding." | ||
| ) | ||
| return None | ||
| # In modern HA the default Overview is registered under the "lovelace" key. | ||
| # The legacy `None` key can hold a stale/phantom LovelaceStorage — prefer | ||
| # "lovelace" so we read and write the dashboard the user actually sees. | ||
| return dashboards.get("lovelace") or dashboards.get(None) |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
In Home Assistant, what key is used in hass.data["lovelace"].dashboards for the default Overview storage dashboard — None or "lovelace"?
💡 Result:
In Home Assistant, the key used in hass.data["lovelace"].dashboards for the default Overview storage dashboard is None.
Citations:
- 1: https://github.com/home-assistant/home-assistant/blob/dev/homeassistant/components/lovelace/__init__.py
- 2: https://github.com/home-assistant/core/blob/5dcbc1d5/homeassistant/components/lovelace/dashboard.py
- 3: https://github.com/home-assistant/core/blob/5dcbc1d5/tests/components/lovelace/test_dashboard.py
Update the default-dashboard key logic and comment — the code has the priority backwards.
The web search confirms that in Home Assistant, the default Overview storage is registered under the None key in dashboards, not "lovelace". The code currently does dashboards.get("lovelace") or dashboards.get(None), which prioritizes a key that isn't the standard default. The fallback to None makes the code work, but the logic is backwards and the comment stating "In modern HA the default Overview is registered under the 'lovelace' key" is incorrect.
Swap the order to dashboards.get(None) or dashboards.get("lovelace") and correct the comment to reflect that None is the primary key and "lovelace" is a secondary fallback (if applicable).
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@custom_components/zone_mapper/frontend.py` around lines 57 - 73, The priority
for selecting the default Lovelace dashboard in _resolve_default_dashboard is
reversed and its comment is incorrect; change the lookup to prefer
dashboards.get(None) first and fall back to dashboards.get("lovelace"), and
update the comment to state that the default Overview is registered under the
None key with "lovelace" as a secondary/legacy fallback so the function returns
dashboards.get(None) or dashboards.get("lovelace").
| except Exception: # noqa: BLE001 | ||
| _LOGGER.warning( | ||
| "Zone Mapper: could not seed default Lovelace view; continuing" | ||
| " without auto-view. Add `type: %s` manually to any dashboard.", | ||
| CARD_TYPE, | ||
| exc_info=True, | ||
| ) | ||
| return True |
There was a problem hiding this comment.
Blanket except Exception returns True — surfaces as a silent no-op.
The broad catch is defensible given the private Lovelace API, but returning True (marking as seeded) after a failure means transient errors (e.g., temporary store read failure) permanently disable the feature for this entry. Consider distinguishing recoverable from non-recoverable failures: e.g., return False for I/O errors from async_save so the next setup retries, and return True only for structural mismatches (AttributeError, KeyError indicating API drift).
At minimum, the current WARNING is a good breadcrumb — just aware the flag path in __init__.py only flips when seeded is True, so True here is persistent via SEEDED_DEFAULT_VIEW_FLAG.
Wait — re-reading: seeded = await async_seed_default_view(hass) then if seeded: ... set flag. Since this returns True on error, the flag does get set → no retry. Intentional per the docstring, but consider returning False on unexpected exceptions so a reload/restart gets another chance; today a one-off glitch permanently disables auto-seeding.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@custom_components/zone_mapper/frontend.py` around lines 148 - 155, The except
block in async_seed_default_view currently catches all Exceptions and returns
True, which causes SEEDED_DEFAULT_VIEW_FLAG to be set and prevents retries;
change the handler to distinguish error types: catch AttributeError/KeyError
(API drift/structural issues) and keep returning True after logging, but for
transient I/O or storage errors (e.g., exceptions raised by async_save or
underlying IO errors) return False so setup can retry; implement this by
inspecting the caught exception instance (or re-raise for unexpected types) in
the except clause and returning False for IOError/OSError/any storage-related
exceptions while preserving the existing _LOGGER.warning with exc_info for the
other cases.
Summary
auto_create_viewoptions flow toggle (defaulttrue) so power users can opt outoriginal-statesstrategy"lovelace"key with a fallback to the legacyNonekey (prevents writing to a phantom dashboard that the user never sees)1.1.0Mainto the branch triggers invalidate.ymlso PRs against the default branch run CIThe companion Zone Mapper card still installs separately via HACS; this PR no longer bundles it (earlier bundling approach was reverted in favor of a simpler, lower-risk change).
Test plan
auto_create_viewoff in the options flow before first setup: no view is createdSummary by CodeRabbit
New Features
Documentation