Skip to content

feat: auto-create Lovelace view on first setup - #37

Merged
TrevorSchirmer merged 1 commit into
Mainfrom
bundle-card-and-autoview
Apr 23, 2026
Merged

feat: auto-create Lovelace view on first setup#37
TrevorSchirmer merged 1 commit into
Mainfrom
bundle-card-and-autoview

Conversation

@bharvey88

@bharvey88 bharvey88 commented Apr 23, 2026

Copy link
Copy Markdown
Contributor

Summary

  • On first config entry, appends a "Zone Mapper" view to the default storage-mode Lovelace dashboard with a placeholder card, so users don't have to drop the card in by hand
  • Adds an auto_create_view options flow toggle (default true) so power users can opt out
  • 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 later won'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 modern "lovelace" key with a fallback to the legacy None key (prevents writing to a phantom dashboard that the user never sees)
  • Bumps integration version to 1.1.0
  • Adds Main to the branch triggers in validate.yml so PRs against the default branch run CI

The 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

  • Fresh HA, storage-mode Overview dashboard: add integration, new "Zone Mapper" view appears at the end of the dashboard
  • Reload the config entry after the view exists: no duplicate view is added
  • Delete the auto-created view manually, reload the config entry: view is NOT recreated (flag honored)
  • Toggle auto_create_view off in the options flow before first setup: no view is created
  • YAML-mode HA: no Lovelace write, INFO log with paste-ready YAML snippet
  • Default Overview with no stored config (ConfigNotFound path): integration seeds a config containing the original-states strategy view AND the Zone Mapper view, so the built-in auto-generated content is preserved

Summary by CodeRabbit

  • New Features

    • Automatic "Zone Mapper" view now seeded on first setup for storage-mode dashboards (v1.1.0).
    • New integration option to disable auto-view creation during setup.
    • Zone Mapper card is now optional; no longer required for functionality.
  • Documentation

    • Updated installation instructions and prerequisite messaging.

@coderabbitai

coderabbitai Bot commented Apr 23, 2026

Copy link
Copy Markdown

Walkthrough

The 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

Cohort / File(s) Summary
Workflow Configuration
.github/workflows/validate.yml
Added Main branch to push and pull_request workflow triggers alongside existing main branch.
Core Integration Setup
custom_components/zone_mapper/__init__.py
Added conditional logic to seed a default Lovelace view during config-entry setup. Includes helpers to read auto-create preference from options and schedule seeding after Home Assistant startup, with a flag preventing re-execution.
Configuration & Constants
custom_components/zone_mapper/config_flow.py, custom_components/zone_mapper/const.py
Added new options flow (ZoneMapperOptionsFlow) with boolean toggle for CONF_AUTO_CREATE_VIEW. Introduced constants for auto-view configuration (title, route, icon, placeholder location) and seeding flag.
Frontend View Seeding
custom_components/zone_mapper/frontend.py
New module implementing async_seed_default_view() function. Resolves default Lovelace dashboard, checks for YAML-mode (avoids modification), loads existing config, and appends a Zone Mapper placeholder view. Includes exception handling for safe failure.
Manifest & Localization
custom_components/zone_mapper/manifest.json, custom_components/zone_mapper/translations/en.json
Version bumped from 1.0.1 to 1.1.0. Added dependencies list (frontend, http, lovelace). Updated user description and added options-flow translations explaining automatic view creation with opt-out capability.
Documentation
README.md
Removed prior warning about required Zone Mapper card. Documented automatic "Zone Mapper" view seeding on first setup (v1.1.0) for storage-mode dashboards with opt-out in integration options. Updated HACS and manual installation instructions with revised repository and navigation guidance.

Sequence Diagram

sequenceDiagram
    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
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 A view springs forth from seeds so small,
Zone Mapper cards adorning all,
With dashboards blooming, fresh and bright,
Options to toggle, options so right!

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 76.92% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive The description is comprehensive with clear summary, test plan, and technical details, but does not follow the provided template structure (missing Related Issue, Type of Change checklist, Checklist items, and Screenshots sections). Restructure the description to follow the provided template: add a Related Issue section, explicitly check the Type of Change checkbox (New feature), and confirm completion of the Checklist items.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and accurately describes the main feature: auto-creating a Lovelace view on first setup, which is the primary change across all modified files.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch bundle-card-and-autoview

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

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.
@bharvey88
bharvey88 force-pushed the bundle-card-and-autoview branch from 058f87d to 21dbb7e Compare April 23, 2026 19:32
@bharvey88 bharvey88 changed the title feat: bundle card and auto-create Lovelace view on first setup feat: auto-create Lovelace view on first setup Apr 23, 2026
@bharvey88
bharvey88 marked this pull request as ready for review April 23, 2026 19:33

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 | 🟡 Minor

Outdated 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 in configuration.yaml will 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 in entry.data — consider entry.options or hass.data instead.

entry.data is conventionally the immutable config created during the initial flow; using it for runtime state (seeding status) is a slight abuse and every call to async_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.options alongside CONF_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_card already 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

📥 Commits

Reviewing files that changed from the base of the PR and between 893e91d and 21dbb7e.

📒 Files selected for processing (8)
  • .github/workflows/validate.yml
  • README.md
  • custom_components/zone_mapper/__init__.py
  • custom_components/zone_mapper/config_flow.py
  • custom_components/zone_mapper/const.py
  • custom_components/zone_mapper/frontend.py
  • custom_components/zone_mapper/manifest.json
  • custom_components/zone_mapper/translations/en.json

Comment thread .github/workflows/validate.yml
Comment on lines +57 to +73
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)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 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:


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").

Comment on lines +148 to +155
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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

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.

@TrevorSchirmer
TrevorSchirmer merged commit e5665f2 into Main Apr 23, 2026
3 checks passed
@TrevorSchirmer
TrevorSchirmer deleted the bundle-card-and-autoview branch April 23, 2026 21:04
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.

2 participants