Use authenticated brands proxy for repository icons - #937
Conversation
The dashboard constructed icon URLs via the upstream `brandsUrl` helper
at a pinned submodule (3ffbd435, Jan 2025) that predates the
brands-proxy API introduced in HA 2026.3. After upstream rewrote
`brands-url.ts` to return `/api/brands/integration/{domain}/icon.png`
with an access token, custom integrations shipping inline brand assets
no longer render (the legacy CDN no longer accepts new custom
integration submissions).
Bump `homeassistant-frontend` to current master, drop the now-removed
`useFallback` parameter, and fetch/schedule the brands access token
from the dashboard's own bundle (module-level token state is
bundle-scoped, so HACS must populate its own copy). Clear the refresh
interval on disconnect.
Fixes hacs/integration#5171
Fixes hacs/integration#5223
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
@ludeeus sorry for pinging you, but any chance to take a look at this PR and additionally hacs/integration#5228? We would love to have icons back for integrations. It's fixing hacs/integration#5171 and hacs/integration#5223 Hope to hear from you! |
|
I don't think that will work. If the integration is not installed, the local image will not be available, right? |
|
That is correct @piitaya. |
|
You are right, but that's why I have another PR: hacs/integration#5228 |
|
The solution I had in mind : a HACS endpoint ( @manuveli This PR is only about |
|
It would be great to have this supported soon, since |
Nachgeprüft statt vermutet, und das Ergebnis widerlegt den bisherigen Text: ein Eintrag im brands-Repo ist für Custom-Integrationen seit Home Assistant 2026.3 nicht mehr nötig - und nicht mehr möglich. Der Versuch (PR #10853) wurde binnen einer Minute vom Bot geschlossen: "we no longer accept brand icons for custom integrations in this repository". Dass die HACS-Übersicht trotzdem den Platzhalter zeigt, hat einen anderen Grund: HACS 2.0.5 baut seine Bild-URLs fest gegen brands.home-assistant.io, statt den lokalen Proxy zu nutzen, den HA für genau diesen Fall anbietet (im mitgelieferten hacs_frontend nachgesehen: kein einziger Treffer auf /api/brands). Dort ist das bekannt (hacs/frontend#937, hacs/integration#5179); von dieser Integration aus lässt sich daran nichts ändern. Home Assistant selbst zeigt das Icon längst - der Proxy liefert an der Produktivinstanz die installierte brand/icon.png md5-identisch aus. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Restores custom_components/noaa_it_all/brand/ byte-for-byte to its previous state. The wordmark logos and the dark_icon.png deletion are reverted, and tests/test_manifest.py goes back to the original pinned dimensions. Also corrects docs/BRANDING.md, which overstated the problem. The earlier claim that "no change to this repository can fix it" was wrong, and the observation that disproves it is simple: other custom integrations do show icons in the HACS store list. Verified: home-assistant/brands/custom_integrations/alarmo -> exists, icon shows home-assistant/brands/custom_integrations/frigate -> exists, icon shows home-assistant/brands/custom_integrations/noaa_it_all -> 404, placeholder So the store-list icon depends on exactly one thing: whether the domain is in the home-assistant/brands repo. Ours is not. Being in the HACS default store is separate and does not grant a CDN icon -- dawg-io/noaa_it_all is already listed in hacs/default and still shows the placeholder. That makes submitting custom_integrations/noaa_it_all/ to home-assistant/brands the actual fix, and worth one attempt. The repo has a workflow that auto-closes PRs adding new custom-integration folders, but its history shows new entries still landing through spring 2026, so the outcome is not certain until tried. Waiting on hacs/frontend#937 is the fallback, not the only option. The README /blob/ URL fix, its regression test, and the hacs.json tidy are unchanged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011TA3si1SuRW4W8MD7N9x7a
Removes docs/BRANDING.md and reverts .github/copilot-instructions.md, per the decision to keep only the README fix. The copilot-instructions changes included three accurate corrections to a stale "manual trigger only" claim about hacs-validate.yml, but those are out of scope here and are reverted rather than carried along; they remain worth a separate change. test_root_icon_exists lost its pointer to the deleted doc and now states the reason inline: the root icon.png is the README header source and is not read by HACS or Home Assistant for store or UI display. What remains in this branch: - README.md: the header image used a github.com/.../blob/... URL, which serves an HTML page rather than an image, so it rendered broken on GitHub and on the HACS repository page (hacs.json sets render_readme). Now points at raw.githubusercontent.com. - tests/test_manifest.py: TestReadmeImages strips fenced code blocks, then fails on any github.com /blob/ image URL. Verified by reintroducing the old URL -- the test fails, and passes again once restored. - hacs.json: stray tab replaced with spaces. custom_components/noaa_it_all/brand/ is byte-for-byte identical to main. For the record, on the HACS store-list icon this branch does not address: it is served from brands.home-assistant.io, backed by home-assistant/brands, where noaa_it_all is absent and can no longer be added -- close-new-custom-integrations.yml (merged 2026-03-03) auto-closes any PR adding a new custom_integrations folder, confirmed on brands PRs #10952 and #10930 from this month. Integrations that do show icons were added before that cutoff; open_pico, for instance, landed 2025-10-18. It resolves only when HACS ships hacs/frontend#937. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011TA3si1SuRW4W8MD7N9x7a
Home Assistant only discovers custom-integration brand icons from inside the integration's own brand/ folder (since 2026.3's brands proxy API), not the repo root -- icon.png/logo.png sitting at the top level were never being picked up by HA at all. Note: a currently-open HACS frontend bug (hacs/integration#5223) means HACS's own downloads-panel UI may still show "icon not available" even with this correctly in place -- a fix exists (hacs/integration#5228, hacs/frontend#937) but isn't merged yet. Home Assistant's own Settings > Devices & Services page should pick this up correctly regardless.
Summary
The HACS dashboard shows "icon not available" for custom integrations that ship inline brand assets (a feature introduced in Home Assistant 2026.3). Root cause is a combination of three things:
homeassistant-frontendsubmodule is pinned to3ffbd435(2025-01-09) — pre-brands-proxy.brandsUrl({...})still returnshttps://brands.home-assistant.io/_/{domain}/icon.png. That CDN no longer accepts new custom-integration submissions (see the brands-proxy API blog post), so custom-integration icons 404.useFallback: true— a parameter that has since been removed from upstreamBrandsOptions, so a naive submodule bump breaks the TypeScript compile.Changes
homeassistant-frontendsubmodule to current upstreammaster— brings in the newbrands-url.tsthat returns/api/brands/integration/{domain}/icon.png?token=…, along withfetchAndScheduleBrandsAccessToken/clearBrandsTokenRefreshexports.useFallback: truefrom thebrandsUrl({...})call insrc/dashboards/hacs-dashboard.ts— the field no longer exists onBrandsOptions. Fallback is now the HA backend's job (stale-while-revalidate through the proxy)._brandsAccessTokeninbrands-url.tsis bundle-scoped, so HACS must populate its own copy; upstream'sfetchAndScheduleBrandsAccessTokensilently swallows failures on older HA backends. Clear the refresh interval indisconnectedCallbackto avoid leaking thesetIntervalwhen the panel unmounts.Fixes hacs/integration#5171
Fixes hacs/integration#5223
A companion PR in the integration repo (hacs/integration) updates the HACS Update entity's
entity_pictureto use the same authenticated proxy path.Test plan
script/bootstrap && script/buildcompiles clean (CI via.github/workflows/test.yml).brand/icon.pnginline: downloads table shows real icons; rendered<img>hassrc=/api/brands/integration/{domain}/icon.png?token=….darkOptimized(filename prefixed withdark_).brands/access_tokenWS command): dashboard renders without thrown errors —fetchAndScheduleBrandsAccessTokencatches silently.🤖 Generated with Claude Code