8.2 KiB
REPORT — Indítópult (app launcher) + universal app placeholder icon (v0.163.0)
Date: 2026-07-24 · Repo: felhom-controller · Target: v0.163.0 · No agent coupling; MinAgent unchanged.
1. Baseline used
- felhom-controller
main@a71cc583272a, versionv0.162.0— matched §1 exactly (clean tree, HEAD == origin/main, CHANGELOG top = "v0.162.0 — R-71(a) settle-gate"). No drift.
2. Files created / modified
Backend
controller/internal/stacks/metadata.go— newMetadata.BrandColor(brand_color, omitempty).controller/internal/web/funcmap.go— newtileColor(returnstemplate.CSS) +initial(rune-safe monogram) +hexColorRe; both registered in the funcmap.controller/internal/web/handlers.go— extractedServer.subdomainMap(replaced BOTH inline copies in dashboard + stacks handlers); newLauncherApptype,buildLauncherApps,launcherHandler,controllerStackNameconst.controller/internal/web/inframeta.go— newAppPlaceholderSVGconstant.controller/internal/web/server.go—/launcherroute;/static/app-placeholder.svgroute.
Templates / nav / CSS
controller/internal/web/templates/launcher.html— NEW page template.controller/internal/web/templates/app_row.html—data-fallbacknow defaults to/static/app-placeholder.svg; header comment updated.controller/internal/web/templates/icons.html— new#i-rocketsprite (nav glyph —layout-gridis already the Vezérlőpult icon, so a distinct glyph was added rather than reused).controller/internal/web/templates/layout.html— Indítópult as the FIRST nav<li>, above Vezérlőpult.controller/internal/web/templates/style.css—launch-grid/launch-cell/launch-tile(+--off) /launch-mono/launch-logo/launch-name/empty-state-link, tokens only.
Tests
controller/internal/web/launcher_test.go— NEW (8 test functions).controller/internal/web/app_row_test.go— 2 test functions added (Group D).
Docs
CHANGELOG.md(v0.163.0 entry),REPORT.md(this file),CONTEXT.md(design rulings),controller/README.md(launcher + placeholder sections).- felhom.eu (separate repo):
documentation/architecture/00-capability-map.md(launcher row, IMPLEMENTED),documentation/backlog/ROADMAP.md(R-15 launcher-as-member-landing coupling note + new R-72 "curate brand_color for top catalog apps").
3. Commits pushed to main
987e915— Indítópult launcher + universal app placeholder (code + CHANGELOG + CONTEXT + README).<REPORT commit>— this REPORT.md (see git log).- felhom.eu:
<docs commit>— capability-map + ROADMAP (explicit paths only; shared worktree).
4. Tests + red-proofs
All groups green. 4 red-proofs applied → observed FAIL → restored → re-green (tree clean after each):
| # | Group | Mutation | Test | Observed FAIL |
|---|---|---|---|---|
| 1 | A | dropped the if !ok || sd == "" subdomain filter in buildLauncherApps |
TestBuildLauncherApps_SelectionAndOrder |
apps = [filebrowser mealie paperless-ngx worker-app], want without worker-app |
| 2 | B | forced the launcher <a> branch ({{if isOperational}}→{{if true}}) |
TestLauncherTemplate_StoppedTileGreyedNoLink |
stopped tile became a link to media.demo-felhom.eu; greyed classes absent |
| 3 | C | tileColor returns brand unvalidated (if brand != "") |
TestLauncherTemplate_TileColorSafety |
injection payload reached the rendered style; hsl-fallback assertion failed |
| 4 | D | reverted app_row.html data-fallback to {{with .FallbackIcon}}…{{end}} |
TestAppRow_DefaultPlaceholderFallback |
logo-less row had no data-fallback attribute |
New test functions (10): Group A TestBuildLauncherApps_SelectionAndOrder,
TestLauncherTemplate_OpenableTiles; Group B TestLauncherTemplate_StoppedTileGreyedNoLink;
Group C TestLauncherTemplate_TileColorSafety, TestTileColor, TestInitial; Group E
TestLauncherTemplate_EmptyState; wiring TestLauncherRoute_EndToEnd; Group D
TestAppRow_DefaultPlaceholderFallback, TestAppPlaceholderRoute.
Note on tileColor returning template.CSS: html/template's CSS value filter mangles a legitimate
hsl() from a func pipeline to ZgotmplZ (verified live). The value is trusted-by-construction (the
brand is validated against hexColorRe in Go; the hsl is fully computed by us), so template.CSS is
correct — and red-proof #3 shows exactly why the Go-side validation is load-bearing (an unvalidated
brand as template.CSS injects verbatim).
5. Test count & suite
go build ./... && go vet ./... && go test ./...— all green, 25 packages OK, 0 FAIL/panic.- Gates green:
app_row_dedup_gate.py,template_id_gate.py,emoji_gate.py. - Web package: 335
=== RUNentries after (+10 top-level funcs + subtests).
6. Deployed version + verification
-
Built + pushed
gitea.dooplex.hu/admin/felhom-controller:0.163.0(145M) from the clean pushed tree (4aa7d41, HEAD == origin/main).build.sh 0.163.0 --push→ "Build complete". -
Deployed to guest 9201 via the bootstrap flow (docker pull →
/etc/felhom-controller-image→systemctl restart felhom-controller-bootstrap.service). Live:docker ps: gitea.dooplex.hu/admin/felhom-controller:0.163.0 Up (healthy) logs: [INFO] Event pushed: controller_started (info) — Controller elindult (0.163.0) [INFO] [offsite-apply] settle-gate: GO — at/above floor 0.156.0 (we are 0.163.0)
7. Live validation
Method: endpoint-level (browser automation / claude-in-chrome is not available on DooPlex),
run with curl inside the felhom-controller container on guest 9201 (the controller listens in its
own container netns, not the guest's 127.0.0.1). Verified:
GET /static/app-placeholder.svg→ 200,Content-Type: image/svg+xml, 359 bytes, body = the<svg … stroke="#94A6BF" …>app-grid glyph. The new asset serves live. (Confirms Group D's route on the real box.)GET /launcher(unauthenticated) → 302 →/login?next=%2Flauncher— the page is correctly behindRequireAuth, and thenext=round-trips the launcher path. A wrong-password POST to/loginis rejected ("Hibás jelszó") — auth is live.protectedStackSubdomainson the live box maps onlyfilebrowser → files, so FileBrowser is the single protected stack eligible for a launcher tile.
Not done live: the authenticated /launcher visual render on this box. The demo box's dashboard
password has been rotated/customer-claimed away from the stored ~/.config/credentials value
(effectivePasswordHash() DB override now wins), so I could not log in, and I deliberately did NOT
reset the operator's own box password just to render a page. The authenticated render is covered by:
(a) the production-template render tests (renderBackupPage loads the real template tree — the HTML
the live box emits is byte-identical to what those tests assert), (b) TestLauncherRoute_EndToEnd
(the route + handler + template integrated on a seeded server), and (c) the live 302-to-login above
proving the route + auth-gate are wired. The residual is a manual operator click-through (§8).
8. Not yet live-validated (expected residual)
- Authenticated
/launchervisual render + a real tile click-through to an app on guest 9201 — HUMAN (needs the current customer-owned dashboard password; browser automation unavailable). Everything else in the pipeline is proven (route, auth-gate, handler, template, placeholder asset). - Placeholder on a real list surface: every currently-deployed catalog app on 9201 has a logo,
so the placeholder does not appear on a live list today; it is covered by
TestAppRow_DefaultPlaceholderFallback+ the liveGET /static/app-placeholder.svg(200,image/svg+xml, verified above). Stated explicitly per §15.8.
9. Observations (not acted on)
- Tile S/L constants (
hsl(H, 45%, 38%)) are a first pick; worth eyeballing live and tuning while keeping S/L fixed (only H per app). Not changed. brand_colorcuration for top catalog apps is parked as ROADMAP R-72 (catalog-only).protectedStackSubdomainscurrently maps onlyfilebrowser → files, so FileBrowser is the one protected stack eligible for the launcher.