diff --git a/REPORT.md b/REPORT.md index 48cdfa9..889dd22 100644 --- a/REPORT.md +++ b/REPORT.md @@ -1,133 +1,95 @@ -# REPORT — Indítópult (app launcher) + universal app placeholder icon (v0.163.0) +# 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.0 · No agent coupling; MinAgent unchanged. +**Date:** 2026-07-24 · **Repo:** felhom-controller · **Target:** v0.163.1 · No agent coupling; MinAgent unchanged. ## 1. Baseline used -- felhom-controller `main` @ `a71cc583272a`, version `v0.162.0` — **matched §1 exactly** (clean tree, - HEAD == origin/main, CHANGELOG top = "v0.162.0 — R-71(a) settle-gate"). No drift. +- felhom-controller `main` @ `7a53cb4`, version `v0.163.0` — **matched exactly** (clean tree, + HEAD == origin/main, CHANGELOG top = v0.163.0). No drift. -## 2. Files created / modified +## 2. Files modified -**Backend** -- `controller/internal/stacks/metadata.go` — new `Metadata.BrandColor` (`brand_color`, omitempty). -- `controller/internal/web/funcmap.go` — new `tileColor` (returns `template.CSS`) + `initial` - (rune-safe monogram) + `hexColorRe`; both registered in the funcmap. -- `controller/internal/web/handlers.go` — extracted `Server.subdomainMap` (replaced BOTH inline - copies in dashboard + stacks handlers); new `LauncherApp` type, `buildLauncherApps`, - `launcherHandler`, `controllerStackName` const. -- `controller/internal/web/inframeta.go` — new `AppPlaceholderSVG` constant. -- `controller/internal/web/server.go` — `/launcher` route; `/static/app-placeholder.svg` route. - -**Templates / nav / CSS** -- `controller/internal/web/templates/launcher.html` — NEW page template. -- `controller/internal/web/templates/app_row.html` — `data-fallback` now defaults to - `/static/app-placeholder.svg`; header comment updated. -- `controller/internal/web/templates/icons.html` — new `#i-rocket` sprite (nav glyph — `layout-grid` - is 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 `
  • `, 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. +**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_test.go` — NEW (8 test functions). -- `controller/internal/web/app_row_test.go` — 2 test functions added (Group D). +- `controller/internal/web/launcher_polish_test.go` — NEW (5 test functions). -**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"). +**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` -- `987e915` — Indítópult launcher + universal app placeholder (code + CHANGELOG + CONTEXT + README). -- `` — this REPORT.md (see git log). -- felhom.eu: `` — capability-map + ROADMAP (explicit paths only; shared worktree). +- `2c80868` — templates + CSS + tests + CHANGELOG + CONTEXT. +- `` — this REPORT.md. ## 4. Tests + red-proofs -All groups green. **4 red-proofs applied → observed FAIL → restored → re-green** (tree clean after each): +Five new tests, all green. **2 red-proofs applied → observed FAIL → restored → re-green:** | # | 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 `` 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 | +| 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 (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`. +**New test functions (5):** `TestLauncher_MonogramRevealOnFailure` (Group A), +`TestBackupsApps_IconPlaceholder`, `TestStacks_IconPlaceholderAndInfra`, +`TestAppInfo_HeroPlaceholder_ScreenshotsUntouched`, `TestDeploy_LogoPlaceholder` (Group B). -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). +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, 25 packages OK, 0 FAIL/panic.** +- `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: 335 `=== RUN` entries after (+10 top-level funcs + subtests). +- Web package: +5 test functions vs v0.163.0. ## 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) - ``` +- 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** (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. +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). -- `GET /static/app-placeholder.svg` → **200, `Content-Type: image/svg+xml`, 359 bytes**, body = - the `` app-grid glyph. The new asset serves live (Group D on the real box). -- `GET /launcher` (unauthenticated) → **302 → `/login?next=%2Flauncher`** — correctly behind - `RequireAuth`, `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** (4 `launch-cell`, 4 `launch-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 4 `target="_blank" rel="noopener"`. - - deterministic per-app tile colors: `hsl(112,…)`, `hsl(274,…)`, `hsl(217,…)`, `hsl(18,…)` — each a - distinct hue, S/L fixed. No `brand_color` is 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. +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. -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. Observations (not acted on) -## 8. Not yet live-validated (expected residual) - -- **Human tile click-through in a real browser** (I verified the tile `` 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. - -## 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_color` curation for top catalog apps is parked as ROADMAP R-72 (catalog-only). -- `protectedStackSubdomains` currently maps only `filebrowser → files`, so FileBrowser is the one - protected stack eligible for the launcher. +- 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.