Use the HACS repository icon endpoint for dashboard icons - #945
Conversation
Point integration icons at /api/hacs/repository/{id}/icon.png served by
the HACS integration, instead of the public brands CDN which no longer
accepts images for custom integrations. Repositories without a domain
keep the CDN placeholder fallback.
There was a problem hiding this comment.
Pull request overview
This PR updates how HACS dashboard repository icons are resolved, switching integration icons to the HACS backend’s repository icon endpoint so icons can be served from each repository’s bundled brand/ assets (with backend caching/redirect behavior), instead of relying on the public brands CDN which no longer accepts new custom-integration images.
Changes:
- Use
/api/hacs/repository/{id}/{icon|dark_icon}.pngfor integration repository icons when a domain exists. - Keep the existing
brandsUrl(...)fallback path for repositories without a domain.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
thomasgregg
left a comment
There was a problem hiding this comment.
Thanks for working on this. I checked out both this PR and hacs/integration#5388 and built the frontend locally on Node 18 (with the unrelated dependency fix from #943 applied).
I found one remaining update issue: _columns is memoized using only localizeFunc and narrow, but _brandsAccessToken is populated asynchronously after firstUpdated. Updating that state rerenders HacsDashboard, yet it passes the same memoized columns object to hass-tabs-subpage-data-table. The nested data table only recomputes its rows when its columns property changes, so existing rows can retain the initial CDN/placeholder URL until an unrelated update happens to redraw the child table.
The minimal correction is to make the token and dark-mode state inputs to the memoized column factory, and use those arguments inside the icon template:
.columns=${this._columns(
this.hacs.localize,
this.narrow,
this._brandsAccessToken,
this.hass.themes?.darkMode,
)}private _columns = memoize(
(
localizeFunc: LocalizeFunc<HacsLocalizeKeys>,
narrow: boolean,
brandsAccessToken: string | undefined,
darkMode: boolean | undefined,
): DataTableColumnContainer<RepositoryBase> => ({
// Use brandsAccessToken and darkMode in the icon template.
}),
);I would also keep the previous token when a scheduled refresh fails instead of setting _brandsAccessToken to undefined. Home Assistant retains both the current and previous brands tokens, so the existing token remains useful across a transient WebSocket failure; an expired token will still take the image's normal error fallback path.
With those changes, Prettier and the HACS build-hacs test build both pass locally. I can open a small follow-up patch if that would be useful.
Make the brands access token and dark-mode state inputs to the memoized column factory so the data table recomputes rows when the token arrives or the theme changes. Keep the previous token when a scheduled refresh fails, since it usually remains valid and an expired token already falls back through the image error handler. Also accept null domains in the icon error handler to match RepositoryBase.domain.
|
Thanks for the thorough review — and for actually building both PRs locally, @thomasgregg. Both points are addressed in dd7aace:
While in there I also fixed a small pre-existing type error: Verified locally: Prettier clean, |
Proposed change
Uses the repository icon endpoint provided by hacs/integration#5388 for integration rows instead of relying only on the public brands CDN.
Since Home Assistant 2026.3, custom integrations ship brand icons inside the repository (
custom_components/<domain>/brand/), andhome-assistant/brandsno longer accepts images for custom integrations. The CDN URLs used today therefore show placeholders for integrations that are not grandfathered in.This change:
brands/access_tokenand refreshes it every 30 minutes;referrerpolicy="no-referrer";dark_icon.pngin dark mode andicon.pngotherwise;alt="".Requires hacs/integration#5388.
Release follow-up
The integration currently pins frontend release
20250128065759inscripts/install/frontend. After both PRs merge, a new frontend release must be published and that pin must be bumped before the integration release. Until then, merging this PR alone does not ship the frontend change to HACS users.Related issues and PRs: hacs/integration#5171, hacs/integration#5179, hacs/integration#5223, and earlier installed-only approaches #937 / #929.
Verification
src/dashboards/hacs-dashboard.ts.tsparticles-preset-links@2.12.0package; the same dependency is present onmain.