Skip to content

Conversation

@balloob
Copy link
Member

@balloob balloob commented Nov 28, 2025

Breaking change

Proposed change

Currently the ingress logic still lives in the panel hosted by the supervisor. This PR makes it available inside Home Assistant without needing the hassio JavaScript or iframe inside an iframe.

As we're having an architecture discussion to rename add-ons to apps, I have named the panel app.

Also added the option to configure the add-on in the panel config, so backend can choose to register panels directly (debug UIs of Z-Wave, Matter, MQTT come to mind).

CleanShot 2025-11-29 at 16 21 10@2x

There is also an option for Home Assistant aware apps to subscribe to the narrow property, to allow hiding the toolbar on mobile, and provide their own interface. To do so, they can call the following functions from their embedded website:

export function toggleHomeAssistantMenu() {
  window.parent.postMessage({ type: "home-assistant/toggle-menu" }, "*");
}
export function subscribeHomeAssistantProperties(callback) {
  const handler = (event) => {
    if (event.data.type === "home-assistant/properties") {
      callback(event.data);
    }
  };
  window.addEventListener("message", handler);
  window.parent.postMessage({ type: "home-assistant/subscribe-properties", hideToolbar: true }, "*");
  return () => {
    window.removeEventListener("message", handler);
    window.parent.postMessage({ type: "home-assistant/unsubscribe-properties" }, "*");
  }
}
export function navigateHomeAssistant(path, options = {}) {
  window.parent.postMessage({ type: "home-assistant/navigate", path, options }, "*");
}

And a test HTML I used for local testing:

<html>
  <button id="toggleMenu">Toggle Menu</button>
  <button id="navigate">Navigate</button><br />
  <button id="unsubscribe">Unsubscribe</button><br />
  Narrow: <span id="narrow"></span><br />
  Route: <span id="route"></span><br />
  <script>
    // Raw commands:
    document.getElementById("toggleMenu").addEventListener("click", () => {
      window.parent.postMessage({ type: "home-assistant/toggle-menu" }, "*");
    });
    document.getElementById("navigate").addEventListener("click", () => {
      window.parent.postMessage({ type: "home-assistant/navigate", path: "/app/d5369777_music_assistant_beta/" + Math.random() }, "*");
    });
    window.addEventListener("message", (event) => {
      if (event.data.type === "home-assistant/properties") {
        document.getElementById("narrow").textContent = event.data.narrow;
        document.getElementById("route").textContent = JSON.stringify(event.data.route);
      }
    });
    window.parent.postMessage({ type: "home-assistant/subscribe-properties", hideToolbar: true }, "*");
    document.getElementById("unsubscribe").addEventListener("click", () => {
      window.parent.postMessage({ type: "home-assistant/unsubscribe-properties" }, "*");
    });
  </script>
</html>

Type of change

  • Dependency upgrade
  • Bugfix (non-breaking change which fixes an issue)
  • New feature (thank you!)
  • Breaking change (fix/feature causing existing functionality to break)
  • Code quality improvements to existing code or addition of tests

Example configuration

Additional information

  • This PR fixes or closes issue: fixes #
  • This PR is related to issue or discussion:
  • Link to documentation pull request:

Checklist

  • The code change is tested and works locally.
  • There is no commented out code in this PR.
  • Tests have been added to verify that the new code works.

If user exposed functionality or configuration variables are added/changed:


Note

Introduces a new app panel that loads add-ons via ingress with session handling and iframe messaging, refactors route tail logic, and wires the panel into routing.

  • Panels:
    • Add src/panels/app/ha-panel-app.ts to load add-ons via ingress iframe, including:
      • Start-if-needed flow, loading/error dialogs, auto-retry, and session keepalive.
      • postMessage API: navigate, toggle menu, subscribe/unsubscribe properties (narrow, route), optional toolbar hiding.
      • Narrow header with sidebar toggle.
  • Routing/Layout:
    • Extract computeRouteTail to src/data/route.ts and reuse in hass-router-page and ha-panel-app.
    • Register app in partial-panel-resolver and prevent disconnecting app panel when hidden.
  • Build:
    • Add alias for lit/directives/ref in build-scripts/rspack.cjs.
  • Translations:
    • Add ui.panel.app.* strings for app errors and actions.

Written by Cursor Bugbot for commit 45b93c0. This will update automatically on new commits. Configure here.

Copy link

@home-assistant home-assistant bot left a comment

Choose a reason for hiding this comment

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

Hi @claude

It seems you haven't yet signed a CLA. Please do so here.

Once you do that we will be able to review and accept this pull request.

Thanks!

@balloob balloob force-pushed the claude/add-app-panel-01EXDqzJ6v1xjWjfkfbefyZK branch from 7b3873e to 60d00b8 Compare November 29, 2025 21:23
@balloob balloob force-pushed the claude/add-app-panel-01EXDqzJ6v1xjWjfkfbefyZK branch from 60d00b8 to 42cb913 Compare November 29, 2025 21:31
@balloob balloob mentioned this pull request Nov 29, 2025
21 tasks
@github-actions github-actions bot added the Build Related to building the code label Nov 29, 2025
@balloob balloob marked this pull request as ready for review December 1, 2025 22:02
Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

This is the final PR Bugbot will review for you during this billing cycle

Your free Bugbot reviews will reset on December 29

Details

Your team is on the Bugbot Free tier. On this plan, Bugbot will review limited PRs each billing cycle for each member of your team.

To receive Bugbot reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial.

if (addon && addon !== oldAddon) {
this._loadingMessage = undefined;
this._fetchData(addon);
}
Copy link

Choose a reason for hiding this comment

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

Bug: Toolbar state not reset when switching between addons

When switching between addons, the _hideToolbar and _iframeSubscribeUpdates state variables are not reset. If addon A subscribes with hideToolbar: true and the user then navigates to addon B which never subscribes, the toolbar remains hidden for addon B. These variables need to be reset in willUpdate when addon !== oldAddon, restoring them to their default values (false) before loading the new addon.

Fix in Cursor Fix in Web

}, 60000);

this._addon = addon;
}
Copy link

Choose a reason for hiding this comment

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

Bug: Race condition when quickly switching between addons

When navigating between addons quickly, multiple concurrent _fetchData calls can race against each other. The code sets this._addon at line 298 without verifying that the fetched addonSlug still matches the current route. If a user navigates from addon A to addon B, but addon A's network request completes after addon B's, the UI will display addon A instead of the intended addon B. The function lacks either a mechanism to cancel stale requests or a check to verify the fetched addon matches the current _getAddonSlug() before updating state.

Additional Locations (1)

Fix in Cursor Fix in Web

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Build Related to building the code cla-signed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants