8.5 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). Authenticated with a real session cookie (POST /login → 302) — the full server-side pipeline, no state hand-set.
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 (Group D on the real box).GET /launcher(unauthenticated) → 302 →/login?next=%2Flauncher— correctly behindRequireAuth,next=round-trips the launcher path.GET /launcher(authenticated, 34,630 bytes) — the real page:- nav:
href="/launcher" class="active"— Indítópult is the first item and marked active. - one
launch-grid, 4 tiles (4launch-cell, 4launch-tile, 4 monograms, 0 greyed — all apps running today): BookStack →https://wiki.demo-felhom.eu, Calibre-Web Automated →https://books.demo-felhom.eu, Filebrowser →https://files.demo-felhom.eu, Immich →https://photos.demo-felhom.eu— all 4target="_blank" rel="noopener". - deterministic per-app tile colors:
hsl(112,…),hsl(274,…),hsl(217,…),hsl(18,…)— each a distinct hue, S/L fixed. Nobrand_coloris set on any app yet (so all four are hash colors). - Filebrowser appears — the protected stack whose subdomain comes from
protectedStackSubdomains(filebrowser → files), confirming the protected-with-subdomain inclusion; the controller stack does NOT appear (excluded by name); no subdomain-less app appears.
- nav:
The one path NOT exercised by today's live data is the greyed stopped tile (all four deployed apps
are running) — covered by TestLauncherTemplate_StoppedTileGreyedNoLink + its red-proof.
8. Not yet live-validated (expected residual)
- Human tile click-through in a real browser (I verified the tile
<a href>targets end-to-end via curl, but not a rendered click) — HUMAN, browser automation unavailable on DooPlex. - Greyed stopped tile on live data — no app is stopped on 9201 right now; covered by the render test + red-proof (Group B).
- Placeholder on a real list surface — every deployed catalog app on 9201 has a logo, so the
placeholder does not appear on a live list today; covered by
TestAppRow_DefaultPlaceholderFallback- the live
GET /static/app-placeholder.svg(200,image/svg+xml, verified above). Stated per §15.8.
- the live
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.