Skip to content

favicons

Derives raster favicon assets from light-logo.svg, for browser/OS chrome that can't use an SVG favicon at all.

Mirrors the approach used by the (separate) flask-website project for the same problem:

  • Safari's tab bar, bookmarks, and iOS's "Add to Home Screen" only read a raster apple-touch-icon, never an SVG favicon.
  • GET /favicon.ico at the site root is still probed by a lot of browser/OS UI (bookmarks, tab switchers) independent of any <link rel="icon"> in the page at all.
  • Some browser chrome still only understands PNG favicons, at the conventional 32x32/16x16 sizes.

Rendered from light-logo.svg specifically, not dark-logo.svg -- these derivatives are static files and can't respond to Dojo's manual dark-mode toggle the way the primary SVG <link> does (see base.html), so they mirror that link's own default, pre-toggle state instead of trying to track it.

:func:ensure_favicons regenerates each derivative once, only when the source SVG has actually changed since the last time it ran (see main.py for where this gets called, and CACHE_DIR for where the results land).

ensure_favicons

Python
ensure_favicons(
    static_dir: Path, cache_dir: Path = CACHE_DIR
) -> None

(Re)generates every favicon derivative that's missing or stale.

Covers every PNG in _FAVICON_TARGETS plus favicon.ico. Each is handled independently -- one failing to render (a corrupt SVG, a read-only cache directory) is logged and skipped rather than raised, so it can't block the others or take down app startup over what's ultimately a cosmetic asset.

Parameters:

Name Type Description Default
static_dir Path

Dojo's templates/static directory, where light-logo.svg lives.

required
cache_dir Path

Where derived PNG/ICO files are written. Defaults to CACHE_DIR.

CACHE_DIR
Source code in src/mujoco_mojo/utils/layers/dojo/favicons.py
Python
def ensure_favicons(static_dir: Path, cache_dir: Path = CACHE_DIR) -> None:
    """
    (Re)generates every favicon derivative that's missing or stale.

    Covers every PNG in `_FAVICON_TARGETS` plus favicon.ico. Each is handled
    independently -- one failing to render (a corrupt SVG, a read-only cache
    directory) is logged and skipped rather than raised, so it can't block
    the others or take down app startup over what's ultimately a cosmetic
    asset.

    Args:
        static_dir: Dojo's `templates/static` directory, where light-logo.svg lives.
        cache_dir: Where derived PNG/ICO files are written. Defaults to `CACHE_DIR`.

    """
    svg_path = static_dir / _SOURCE_SVG
    if not svg_path.exists():
        return

    try:
        svg_bytes = svg_path.read_bytes()
    except OSError:
        logger.warning("Failed to read %s", _SOURCE_SVG, exc_info=True)
        return

    cache_dir.mkdir(parents=True, exist_ok=True)
    source_hash = hashlib.sha256(svg_bytes).hexdigest()

    for filename, size in _FAVICON_TARGETS:
        try:
            _generate_if_stale(cache_dir, filename, size, svg_bytes, source_hash)
        except Exception:
            logger.warning(
                "Failed to (re)generate %s from %s",
                filename,
                _SOURCE_SVG,
                exc_info=True,
            )

    try:
        _generate_ico_if_stale(cache_dir, svg_bytes, source_hash)
    except Exception:
        logger.warning(
            "Failed to (re)generate favicon.ico from %s", _SOURCE_SVG, exc_info=True
        )