TrailRunner: show app icons (Android) / favicons (web) next to each target
- Dominant language
- Kotlin
- Stars
- 310
- Forks
- 28
- Avg merge
- 7h 45m
- Merged PRs (30d)
- 8
Description
## Summary
TrailRunner's target list (the web UI served on `:52525`) shows targets as text-only rows. On a fresh workspace — or any workspace where the author hasn't wired up custom visuals — the list reads as an undifferentiated wall of names with no glyphs, which makes the first-run experience feel empty and makes targets harder to scan/discover at a glance.
Proposal: render a small **icon per target** next to its name — the **Android launcher icon** for app targets and the **site favicon** for web targets. A spike showed both are cheaply obtainable with CLI-only tooling (no browser, no extra services beyond public favicon endpoints), and sample icons rendered cleanly.
## The problem
- The target list has no per-target visual, so an empty/sparse workspace looks like nothing is there.
- Scanning a longer list is slower without recognizable glyphs (app icon / favicon are the fastest "which one is this" cue).
- There's no lightweight way for an author to attach a glyph today.
## Proposed design
Two complementary mechanisms, so the common case needs **zero per-target config**:
1. **Optional `icon:` field on the trailmap `target:` block** — a sibling to `display_name`, holding a workspace-relative path:
```yaml
target:
display_name: Example App
icon: assets/icons/android_com.example.app.png
```
It threads through `trailblaze check`/generation into the resolved target manifest (`AppTargetYamlConfig`) so the TrailRunner UI can render it. The bundled-config generator already passes unknown target fields through into the generated `targets/.yaml`, so once the field is on the model it carries end-to-end.
2. **Convention fallback (no config)** — when `icon:` is absent, the UI resolves by convention against a well-known assets folder:
- Android target → `assets/icons/android_.png`
- Web target → derive the host from the start/base URL → `assets/icons/favicon_.png`
These match the exact filenames the extraction step produces, so **just populating `assets/icons/` fills the empty first-run state** with no per-target authoring. Explicit `icon:` overrides convention when a target wants a custom glyph.
## How the icons are obtained (spike-validated, CLI-only)
**Android app icons — `adb` + `aapt`:**
- Pull `base.apk` (`adb shell pm path ` → `adb pull`).
- Ask `aapt dump badging` which resource is the launcher icon per density; take the highest-density PNG (`application-icon-640` / xxxhdpi, falling back to xxhdpi).
- **Adaptive icons** (`application-icon-*` points at a `.xml`): decode the adaptive XML to find the ``/`` drawable IDs (`aapt dump xmltree`), map IDs → real raster files via the resource table (`aapt dump --values resources`), then alpha-composite foreground over background. Note: real APKs use obfuscated resource paths and non-`ic_launcher` names, so reading the manifest-declared icon via `aapt` is more reliable than globbing `res/mipmap*/ic_launcher*`; and some foreground layers are vector-only (anydpi) — fall back to the mipmap raster foreground in that case.
**Web favicons — public services, no browser:**
- Google s2: `https://www.google.com/s2/favicons?domain=&sz=128` (most reliable; returns whatever size the site publishes).
- DuckDuckGo: `https://icons.duckduckgo.com/ip3/.ico` (sometimes higher-res than s2; may return `.ico` → re-save as PNG).
- Direct `/favicon.ico` and homepage `` scraping are unreliable — some sites bot-block and return an HTML challenge instead of an image. Some sites only publish a 16×16 favicon; usable as a tiny glyph but soft when upscaled.
The spike rendered 3 Android icons (including two adaptive-composited) and 4 favicons, all verified as valid PNGs.
## Where it surfaces
The TrailRunner target list in the web UI on `:52525` — a small icon rendered beside each target's `display_name`. Convention resolution + the assets folder means an author can drop icons in and immediately see the list populate.
## Suggested scope / phasing
- **Model/schema (small, self-contained):** add the optional `icon:` field to the authored target block and the resolved target manifest, thread it through resolution; add a pure resolver helper for the convention filename (`android_.png` / `favicon_.png`) + explicit-override precedence. Unit-testable, no UI dependency.
- **UI wiring:** TrailRunner renders the resolved/convention icon path next to each target (Compose/web-UI change).
- **Optional tooling follow-up:** a helper command to populate `assets/icons/` for a workspace's targets (the `aapt`/favicon steps above), so the convention folder fills itself.
Happy to iterate on the field name, the assets-folder location, and the host-derivation rule for web targets.
Contributor guide
Research direction
Start with the target model/schema around AppTargetYamlConfig and the trailblaze check/generation path; then inspect the TrailRunner target list served on :52525. Use the assets/icons convention and explicit icon override as the acceptance boundary, with tests for resolver precedence and both target filename forms.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- android, kotlin
- Domain
- frontend, mobile
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 45/100