Reachy_Mini / docs /APP_ICON_CONVENTION.md
tfrere's picture
tfrere HF Staff
feat(api): publish iconUrl on /api/js-apps from Space siblings
07a94f1
|
Raw
History Blame Contribute Delete
4.78 kB
# App icon convention
> Status: convention v1
> Audience: authors shipping a Reachy Mini app to the Hugging Face Hub
> Implemented by: `reachy-mini-website` catalog server (this repo) +
> `reachy_mini_mobile_app`, `reachy_mini_desktop_app`
> Source 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:
1. Commit `icon.svg` (preferred) **or** `icon.png` at the root of your
Hugging Face Space repository.
2. 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.
3. If both files are present, `icon.svg` wins.
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:
- `thumbnail` is 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)
1. The catalog server calls
`https://huggingface.co/api/spaces?filter=reachy_mini&full=true`.
With `full=true`, the HF Hub returns `siblings: [{ rfilename: ... }]`
for every Space - the complete file list.
2. For each app, `findIconUrl()` (in `server/index.js`) scans the
list for root-level filenames matching `ICON_CANDIDATES` in order
(`icon.svg``icon.png`).
3. The first match becomes:
```
https://huggingface.co/spaces/<author>/<repo>/resolve/main/<filename>
```
`resolve/main/` (not `raw/main/`) so LFS pointers follow through
transparently and the `Content-Type` is set from the extension,
which `<img>` needs.
4. The URL is published on the app entry as a top-level `iconUrl`
field. `null` when neither candidate exists.
5. Clients (`reachy_mini_mobile_app`, `reachy_mini_desktop_app`) read
`iconUrl` and 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`:
```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.