# REPORT — Launcher polish: monogram reveal-on-failure + placeholder on every icon surface (v0.163.1) **Date:** 2026-07-24 · **Repo:** felhom-controller · **Target:** v0.163.1 · No agent coupling; MinAgent unchanged. ## 1. Baseline used - felhom-controller `main` @ `7a53cb4`, version `v0.163.0` — **matched exactly** (clean tree, HEAD == origin/main, CHANGELOG top = v0.163.0). No drift. ## 2. Files modified **Templates / CSS** - `controller/internal/web/templates/launcher.html` — both tile branches' `onerror` final step now adds `this.parentElement.classList.add('launch-tile--noimg')`. - `controller/internal/web/templates/style.css` — `.launch-mono { display: none; inset:0; … }` + `.launch-tile--noimg .launch-mono { display: flex; }` (monogram = failure-only). - `controller/internal/web/templates/backups_apps.html` — the allowlisted aligned row's icon now uses the canonical `data-fallback="/static/app-placeholder.svg"` + 3-step chain; header comment updated. - `controller/internal/web/templates/stacks.html` — `data-fallback` always present: `{{if $im}}/static/infra-logo.svg{{else}}/static/app-placeholder.svg{{end}}`. - `controller/internal/web/templates/app_info.html` — hero logo aligned to the 3-step chain + placeholder; **screenshots untouched** (still `onerror="this.style.display='none'"`). - `controller/internal/web/templates/deploy.html` — deploy logo aligned; kept its `.LogoURL`/`.LogoPNGURL` data source. **Tests** - `controller/internal/web/launcher_polish_test.go` — NEW (5 test functions). **Docs**: `CHANGELOG.md` (v0.163.1), `CONTEXT.md` (rule recorded), `REPORT.md` (this file). **Gate script:** `app_row_dedup_gate.py` does NOT pin the `onerror` chain (only `app-row-icon` presence + forbidden structures), so no gate-script edit was needed; all three gates stay green. ## 3. Commits pushed to `main` - `2c80868` — templates + CSS + tests + CHANGELOG + CONTEXT. - `` — this REPORT.md. ## 4. Tests + red-proofs Five new tests, all green. **2 red-proofs applied → observed FAIL → restored → re-green:** | # | Group | Mutation | Test | Observed FAIL | |---|-------|----------|------|---------------| | A | launcher | removed `classList.add('launch-tile--noimg')` from both `onerror` else branches | `TestLauncher_MonogramRevealOnFailure` | reveal-hook count 0 (want 2) — the monogram would never re-appear for a logo-less tile | | B | placeholder | reverted `backups_apps.html` to the old 2-step `onerror` chain | `TestBackupsApps_IconPlaceholder` | canonical 3-step chain absent → logo-less row dead-ends hidden | **New test functions (5):** `TestLauncher_MonogramRevealOnFailure` (Group A), `TestBackupsApps_IconPlaceholder`, `TestStacks_IconPlaceholderAndInfra`, `TestAppInfo_HeroPlaceholder_ScreenshotsUntouched`, `TestDeploy_LogoPlaceholder` (Group B). Two test-only fixes made while writing them (not code bugs): the reveal class legitimately appears inside the `onerror` JS string, so Group A asserts its count == 2 (both are `classList.add` hooks, none in a `class=` attribute); and `{{.LogoPNGURL}}` is JS-escaped (`\/…`) inside `onerror`, so the deploy test asserts the slash-free filename `docmost-logo.png`. ## 5. Test count & suite - `go build ./... && go vet ./... && go test ./...` — **all green, 0 FAIL/panic.** - Gates green: `app_row_dedup_gate.py`, `template_id_gate.py`, `emoji_gate.py`. - Web package: +5 test functions vs v0.163.0. ## 6. Deployed version + verification - Built + pushed `gitea.dooplex.hu/admin/felhom-controller:0.163.1` (145M) from the clean pushed tree (`2c80868`, HEAD == origin/main); deployed to guest 9201 via the bootstrap flow. - Live: `docker ps` → `…felhom-controller:0.163.1 Up (healthy)`; log → `controller_started (info) — Controller elindult (0.163.1)` + `settle-gate: GO … we are 0.163.1`. ## 7. Live validation Method: **endpoint-level, authenticated** (real session cookie via `POST /login`; browser automation unavailable on DooPlex), `curl` inside the `felhom-controller` container on guest 9201. The 9201 box now has 5 launcher apps incl. Docmost (which has no logo asset — the exact reported case). 1. **Launcher monogram (check 1).** On the live `/launcher`: `launch-tile--noimg` appears **5 times, all 5 inside `onerror` `classList.add(...)` hooks, 0 in any `class=` attribute** — i.e. no tile statically carries the reveal class, so no white letter bleeds through a loaded transparent glyph. 5 tiles / 5 monogram spans (4 operational + 1 stopped — both branches exercised live). Served `style.css` carries `.launch-mono { display: none; inset:0; … }` and `.launch-tile--noimg .launch-mono { display: flex; }`. 2. **Placeholder on Biztonsági mentés → Alkalmazások (check 2).** The live `/backups/apps` page's **Docmost row now carries `data-fallback="/static/app-placeholder.svg"`** and the canonical 3-step chain (4 rows total with the placeholder default) — the reported hidden-icon dead-end is gone. 3. **No regression on Alkalmazások (check 3).** The live `/stacks` page: **all 56 `stack-logo-lg` imgs still render** with a fallback — 52 default to `/static/app-placeholder.svg`, 4 infra cards keep `/static/infra-logo.svg`. Every card keeps its logo source; no logo vanished. ## 8. Observations (not acted on) - The four aligned copies (`backups_apps` aligned row, `stacks`, `app_info` hero, `deploy`) share the same `onerror` grammar but differ in class/size/data-source, so consolidating them into one partial is a possible future cleanup — deliberately NOT done here (backups_apps is gate-exempted because it owns its collapse toggle; the task scoped this to chain-grammar alignment only). - The launcher now has a live stopped tile (a greyed app on 9201), so the stopped-branch reveal hook is exercised on real data, not only in the render test.