Spaces:
Running
App icon convention
Status: convention v1 Audience: authors shipping a Reachy Mini app to the Hugging Face Hub Implemented by:
reachy-mini-websitecatalog server (this repo) +reachy_mini_mobile_app,reachy_mini_desktop_appSource of truth:server/index.js→findIconUrl()
This document specifies how a Reachy Mini app declares a custom icon.
Apps that don't follow it keep working - the surface falls back to the
front-matter emoji: glyph, which is the existing behaviour.
1. The convention in three lines
To ship a custom icon for your Reachy Mini app:
- Commit
icon.svg(preferred) oricon.pngat the root of your Hugging Face Space repository. - That's it. Within ~5 minutes (the catalog cache TTL) the mobile shell, the desktop app and the website surface your icon automatically, replacing the README front-matter emoji.
- If both files are present,
icon.svgwins.
No README change required. No tag to add. No PR to file against this repo. The catalog server scans the file list once per refresh and publishes a resolved URL on the app entry; every client consumes it.
2. Why a file convention and not cardData.thumbnail
HF Spaces support a thumbnail: field in README front-matter, but:
thumbnailis full-bleed marketing artwork (typically 1200x630), not a square avatar. Scaling it to a 22 px or 44 px tile produces muddy thumbnails.- We want app authors to ship a dedicated, optimised glyph they control without learning the HF metadata schema.
- SVG support means the icon scales cleanly across every mount point (rail tile, pinned grid, iframe header) from a single asset.
thumbnail: keeps its existing role (banner artwork on the Space's
HF page) and is not consulted by this resolution path.
3. Format & dimension recommendations
| Property | Recommended | Hard requirement |
|---|---|---|
| Format | icon.svg (vector) |
icon.svg or icon.png |
| Aspect ratio | 1:1 (square) | Renderers crop with object-fit: contain, but non-square icons render with letterboxing - prefer a true square |
| Min PNG size | 256x256 | None enforced. PNGs below 64x64 will look soft on the pinned grid (44 px on retina ≈ 88 effective px) |
| Background | Transparent OR solid colour | None - your call. Renderers don't add their own plate, so an icon with no background renders directly on the tile colour |
| Padding | Bake ~10% inner padding into the asset | None - but icons that bleed edge-to-edge will touch the tile's rounded corners |
| Light/dark variants | Single asset that works on both | None - if you must, ship two SVGs and use prefers-color-scheme inside the SVG via CSS |
Style notes
- Iconic, not photographic. A solid filled silhouette reads at 22 px; a screenshot doesn't.
- High contrast against
background.paper. The mobile app paints the tile background with the surface colour (very light grey on light, near-black on dark). A pure white icon disappears on light. - No drop shadow baked into the asset. The renderer doesn't add one either, and a baked shadow won't scale across sizes.
4. How resolution works (for the curious)
The catalog server calls
https://huggingface.co/api/spaces?filter=reachy_mini&full=true. Withfull=true, the HF Hub returnssiblings: [{ rfilename: ... }]for every Space - the complete file list.For each app,
findIconUrl()(inserver/index.js) scans the list for root-level filenames matchingICON_CANDIDATESin order (icon.svg→icon.png).The first match becomes:
https://huggingface.co/spaces/<author>/<repo>/resolve/main/<filename>resolve/main/(notraw/main/) so LFS pointers follow through transparently and theContent-Typeis set from the extension, which<img>needs.The URL is published on the app entry as a top-level
iconUrlfield.nullwhen neither candidate exists.Clients (
reachy_mini_mobile_app,reachy_mini_desktop_app) readiconUrland render an<img>when present, falling back to the front-matter emoji otherwise. A runtime image load failure re-falls-back to the emoji without a refresh.
The whole resolution path is server-side, behind the 5-minute catalog cache. Adding 100 more apps adds zero per-client probes.
5. Adding new icon formats
If you need to support a new format (say, icon.webp), edit
ICON_CANDIDATES in server/index.js:
const ICON_CANDIDATES = ['icon.svg', 'icon.png', 'icon.webp'];
Order matters - the first hit wins, so put the preferred format first.
Bumping the catalog cache (POST /api/js-apps/refresh-categories or
just wait 5 minutes) picks up the new resolution rule.