diff --git a/docs/superpowers/plans/2026-03-24-frontend-overhaul.md b/docs/superpowers/plans/2026-03-24-frontend-overhaul.md new file mode 100644 index 0000000..cc9aade --- /dev/null +++ b/docs/superpowers/plans/2026-03-24-frontend-overhaul.md @@ -0,0 +1,1940 @@ +# Frontend Overhaul Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Redesign all six server-rendered HTML templates to be professional, polished, and fun to use — with MORZ red accent, system-UI font, and interactive inline editing on the playlist page. + +**Architecture:** All templates live as Go `const` string variables in two files. Bulma CSS is customized via CSS custom properties. Inline JS handles interactivity; no new libraries, no build step. One small Go handler change makes `HandleUpdateItemUI` return `204` for fetch callers. + +**Tech Stack:** Go html/template, Bulma CSS (local `/static/bulma.min.css`), SortableJS (local `/static/Sortable.min.js`), vanilla JS ES5+ + +--- + +## Shared CSS Reference + +Every template gets this ` +``` + +Shared JS toast helper (include in every template before closing ``): + +```js +function showToast(msg, type) { + type = type || 'is-success'; + var t = document.createElement('div'); + t.className = 'morz-toast ' + type; + t.innerHTML = msg + ''; + document.body.appendChild(t); + requestAnimationFrame(function() { t.classList.add('show'); }); + setTimeout(function() { t.classList.remove('show'); setTimeout(function() { t.remove(); }, 300); }, 3500); +} +``` + +Shared status-polling helper: + +```js +function pollScreenStatus(slugToId) { + // slugToId: object mapping screen_id -> DOM element id prefix (e.g. 'status-') + function update() { + fetch('/api/v1/screens/status') + .then(function(r) { return r.ok ? r.json() : null; }) + .then(function(data) { + if (!data || !data.screens) return; + data.screens.forEach(function(s) { + var el = document.getElementById('status-' + s.screen_id); + if (!el) return; + var state = s.derived_state || 'unknown'; + el.className = 'status-dot ' + (state === 'online' ? 'online' : state === 'degraded' ? 'stale' : 'offline'); + el.title = state; + }); + }).catch(function(){}); + } + update(); + setInterval(update, 30000); +} +``` + +Shared CSRF helper: + +```js +function getCsrfToken() { + var m = document.cookie.match('(?:^|; )morz_csrf=([^;]*)'); + return m ? decodeURIComponent(m[1]) : ''; +} +function injectCSRF() { + var token = getCsrfToken(); + if (!token) return; + document.querySelectorAll('form[method="POST"],form[method="post"]').forEach(function(f) { + if (!f.querySelector('input[name="csrf_token"]')) { + var inp = document.createElement('input'); + inp.type = 'hidden'; inp.name = 'csrf_token'; inp.value = token; + f.appendChild(inp); + } + }); +} +if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', injectCSRF); +else injectCSRF(); +``` + +--- + +## File Map + +| File | What changes | +|---|---| +| `server/backend/internal/httpapi/manage/templates.go` | Rewrite all 5 template consts: `loginTmpl`, `provisionTmpl`, `adminTmpl`, `manageTmpl`, `screenOverviewTmpl` | +| `server/backend/internal/httpapi/tenant/templates.go` | Rewrite `tenantDashTmpl` | +| `server/backend/internal/httpapi/manage/ui.go` | `HandleUpdateItemUI`: return 204 when `X-Requested-With: fetch` header present | + +--- + +## Task 1: HandleUpdateItemUI — return 204 for fetch callers + +**Files:** +- Modify: `server/backend/internal/httpapi/manage/ui.go:781-788` +- Test: `server/backend/internal/httpapi/router_test.go` (add test) + +The handler currently always redirects. Inline editing JS will call it via `fetch`, which needs a 204 back (not a redirect). + +- [ ] **Step 1: Find the redirect line** + +In `ui.go`, the last line of `HandleUpdateItemUI` is: +```go +http.Redirect(w, r, "/manage/"+screenSlug+"?msg=saved", http.StatusSeeOther) +``` + +- [ ] **Step 2: Replace with conditional response** + +Replace that redirect with: +```go +if r.Header.Get("X-Requested-With") == "fetch" { + w.WriteHeader(http.StatusNoContent) + return +} +http.Redirect(w, r, "/manage/"+screenSlug+"?msg=saved", http.StatusSeeOther) +``` + +- [ ] **Step 3: Build to verify** + +```bash +cd server/backend && go build ./... +``` +Expected: no errors. + +- [ ] **Step 4: Commit** + +```bash +git add server/backend/internal/httpapi/manage/ui.go +git commit -m "fix(manage): HandleUpdateItemUI returns 204 for fetch callers" +``` + +--- + +## Task 2: Login page (`loginTmpl`) + +**Files:** +- Modify: `server/backend/internal/httpapi/manage/templates.go` (replace `loginTmpl` const, lines 3–99) + +- [ ] **Step 1: Replace `loginTmpl`** + +Replace the entire `loginTmpl` const with: + +```go +const loginTmpl = ` + + + + + Anmelden – MORZ Infoboard + + + + +
+
MORZ Infoboard
+ + {{if .Error}} + + {{end}} + +
+ + {{if .Next}}{{end}} + +
+ + +
+ +
+ +
+ + +
+
+ + +
+
+ +` +``` + +- [ ] **Step 2: Build** + +```bash +cd server/backend && go build ./... +``` + +- [ ] **Step 3: Commit** + +```bash +git add server/backend/internal/httpapi/manage/templates.go +git commit -m "feat(ui): Login-Seite neu gestaltet" +``` + +--- + +## Task 3: Provision wizard (`provisionTmpl`) + +**Files:** +- Modify: `server/backend/internal/httpapi/manage/templates.go` (replace `provisionTmpl` const, lines 101–256) + +- [ ] **Step 1: Replace `provisionTmpl`** + +```go +const provisionTmpl = ` + + + + + Einrichten – {{.Screen.Name}} + + + + + + +
+
+ +
+ ✓ Screen «{{.Screen.Name}}» ({{.Screen.Slug}}) wurde angelegt.
+ Führe die folgenden Schritte aus, um den Bildschirm zu provisionieren. +
+ +
+
+ 1 +
+

Host zur Ansible-Inventardatei hinzufügen

+

Öffne ansible/inventory.yml und füge ein:

+
        {{.Screen.Slug}}:
+ +
+
+
+ +
+
+ 2 +
+

Host-Variablen anlegen

+

Erstelle ansible/host_vars/{{.Screen.Slug}}/vars.yml:

+
---
+ansible_host: {{.IP}}
+ansible_user: {{.SSHUser}}
+screen_id: {{.Screen.Slug}}
+screen_name: "{{.Screen.Name}}"
+screen_orientation: {{.Orientation}}
+
+ + +
+

Tipp: mkdir -p ansible/host_vars/{{.Screen.Slug}}

+
+
+
+ +
+
+ 3 +
+

SSH-Zugang sicherstellen

+
ssh-copy-id {{.SSHUser}}@{{.IP}}
+ +
+
+
+ +
+
+ 4 +
+

Ansible-Playbook ausführen

+
cd /path/to/morz-infoboard
+ansible-playbook -i ansible/inventory.yml ansible/site.yml --limit {{.Screen.Slug}}
+ +

Mit Vault: --vault-password-file ansible/.vault_pass

+
+
+
+ +
+
+ 5 +
+

Fertig — Playlist befüllen

+

Nach dem Ansible-Lauf meldet sich der Bildschirm automatisch an.

+ +
+
+
+ +
+
+ + +` +``` + +- [ ] **Step 2: Build & commit** + +```bash +cd server/backend && go build ./... && git add server/backend/internal/httpapi/manage/templates.go && git commit -m "feat(ui): Provision-Wizard neu gestaltet" +``` + +--- + +## Task 4: Admin dashboard (`adminTmpl`) + +**Files:** +- Modify: `server/backend/internal/httpapi/manage/templates.go` (replace `adminTmpl` const, lines 258–877) + +This is the largest template in the admin area. Key changes: +- Screen list becomes a card grid (not a table) +- Underline-style tabs (not boxed) +- Polished modals +- Status dots via CSS (not emoji) +- Shared CSS tokens + +- [ ] **Step 1: Replace `adminTmpl`** + +```go +const adminTmpl = ` + + + + + Admin – MORZ Infoboard + + + + + + + + + + + + + + + + +
+
+ +
+ +
+
+
+ +
+
+ + +
+ +
+
+

Bildschirme

+ +
+ + {{if .Screens}} +
+ {{range .Screens}} + {{$users := index $.ScreenUserMap .ID}} +
+
+
+ {{if eq .Orientation "portrait"}}📱{{else}}🖥{{end}} + {{.Name}} +
+
{{.Slug}}
+
+ {{orientationLabel .Orientation}} + + +
+
+ Playlist + +
+
+
+ {{end}} +
+ {{else}} +
Noch keine Bildschirme angelegt.
+ {{end}} +
+ + +
+

Neuen Bildschirm einrichten

+

Bildschirm anlegen und Ansible-Deployment-Anleitung generieren.

+
+
+
+
+ + +

Als screen_id verwendet

+
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+ +
+ +
+
+
+
+ +
+ +
+ Nur anlegen (ohne Deployment) +
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+ +
+ +
+
+
+
+
+ + +
+
+
+
+
+
+
+ + +
+
+
+
+

Screen-Benutzer

+

Können sich einloggen und nur ihre zugeordneten Bildschirme verwalten.

+
+
+ + {{if .ScreenUsers}} +
+ + + + + + + + + + {{range .ScreenUsers}} + + + + + + {{end}} + +
BenutzernameErstellt
{{.Username}}{{.CreatedAt.Format "02.01.2006 15:04"}} + +
+
+ {{else}} +
Noch keine Benutzer angelegt.
+ {{end}} + +
+

Neuen Benutzer anlegen

+
+
+
+
+ + +
+
+
+
+ +
+ + + + +
+

Mind. 8 Zeichen

+
+
+
+
+ + +
+
+
+
+
+
+ +
+
+ + + +` +``` + +- [ ] **Step 2: Build & commit** + +```bash +cd server/backend && go build ./... && git add server/backend/internal/httpapi/manage/templates.go && git commit -m "feat(ui): Admin-Dashboard neu gestaltet (Karten-Grid, Tabs, Modals)" +``` + +--- + +## Task 5: Manage / Playlist editor (`manageTmpl`) + +**Files:** +- Modify: `server/backend/internal/httpapi/manage/templates.go` (replace `manageTmpl` const, lines 879–1502) + +Key changes from current: +- Two-column layout (playlist 60% / library 40%) on desktop, stacked on mobile +- Playlist items: draggable cards (not table rows), inline-editable title/duration, pill toggle for enabled/disabled, collapsible validity dates +- Library: card grid (not table), "Hinzufügen" CTA per card +- Upload: collapsed by default, expandable +- `HandleUpdateItemUI` called via `fetch` (returns 204) +- CSRF token sent via hidden field in FormData for fetch calls + +- [ ] **Step 1: Replace `manageTmpl`** + +```go +const manageTmpl = ` + + + + + Playlist – {{.Screen.Name}} + + + + + + + + + + + +
+
+ + +
+ Screenshot {{.Screen.Name}} +
+ {{orientationLabel .Screen.Orientation}} + {{.Screen.Slug}} +
+
+ +
+ + +
+
+

Playlist

+ + {{if .Items}} +
+ {{range .Items}} +
+ + +
+
+ {{typeIcon .Type}} {{.Type}} + + +
+ +
+
+ + s + +
+ + + + {{if or .ValidFrom .ValidUntil}} + + {{else}} + + {{end}} +
+ + +
+ + +
+ {{end}} +
+

Per Drag & Drop sortieren oder Felder direkt bearbeiten.

+ {{else}} +
+ Die Playlist ist leer. Füge Medien aus der Bibliothek hinzu. +
+ {{end}} +
+
+ + +
+ +
+
+ + + Medium hinzufügen + + +
+
+ +
+ +
+
+
+ +
+ +
+
+
+ + +
+
+ +
+

📂

+

Klicken oder Datei hierher ziehen

+ +
+ +
+ + + +
+
+ + +
+
+
+ + +
+

Medienbibliothek

+ {{if .Assets}} +
+ {{range .Assets}} +
+
+ {{if eq .Type "image"}} + {{else if eq .Type "video"}}🎬 + {{else if eq .Type "pdf"}}📄 + {{else}}🌐{{end}} +
+
+
{{.Title}}
+
{{typeIcon .Type}} {{.Type}}
+
+
+ {{if index $.AddedAssets .ID}} + ✓ In Playlist + {{else}} +
+ + +
+ {{end}} + +
+
+ {{end}} +
+ {{else}} +

Noch keine Medien. Lade oben eine Datei hoch.

+ {{end}} +
+
+ +
+
+
+ + + +` +``` + +- [ ] **Step 2: Build & commit** + +```bash +cd server/backend && go build ./... && git add server/backend/internal/httpapi/manage/templates.go && git commit -m "feat(ui): Playlist-Editor neu gestaltet (Karten, Inline-Edit, Zwei-Spalten)" +``` + +--- + +## Task 6: Screen overview (`screenOverviewTmpl`) + +**Files:** +- Modify: `server/backend/internal/httpapi/manage/templates.go` (replace `screenOverviewTmpl` const, lines 1504–1567) + +- [ ] **Step 1: Replace `screenOverviewTmpl`** + +```go +const screenOverviewTmpl = ` + + + + + Meine Bildschirme – MORZ Infoboard + + + + + + +
+
+

Meine Bildschirme

+
+ {{range .Cards}} +
+
+
+ {{.Screen.Name}} + +
+
+
+ {{if eq .Screen.Orientation "portrait"}}📱{{else}}🖥{{end}} + {{.Screen.Name}} +
+
{{orientationLabel .Screen.Orientation}} · {{.Screen.Slug}}
+ Verwalten → +
+
+
+ {{end}} +
+
+
+ + + +` +``` + +- [ ] **Step 2: Build & commit** + +```bash +cd server/backend && go build ./... && git add server/backend/internal/httpapi/manage/templates.go && git commit -m "feat(ui): Screen-Übersicht neu gestaltet" +``` + +--- + +## Task 7: Tenant dashboard (`tenantDashTmpl`) + +**Files:** +- Modify: `server/backend/internal/httpapi/tenant/templates.go` (replace `tenantDashTmpl` const) + +- [ ] **Step 1: Replace `tenantDashTmpl`** + +```go +const tenantDashTmpl = ` + + + + + Dashboard – {{.Tenant.Name}} + + + + + + +
+
+ +

{{.Tenant.Name}}

+ + {{if .Flash}} + + {{end}} + + + + +
+
+ {{if .Screens}} +
+ {{range .Screens}} +
+
+
+ {{if eq .Orientation "portrait"}}📱{{else}}🖥{{end}} + {{.Name}} +
+
+ + {{orientationLabel .Orientation}} +
+ Playlist bearbeiten → +
+
+ {{end}} +
+ {{else}} +

Noch keine Monitore zugewiesen.

+ {{end}} +
+
+ + +
+
+ +

Medium hochladen

+
+
+ +
+ +
+
+
+ + +
+
+ +
+

📂

+

Klicken oder Datei hierher ziehen

+ +
+ +
+ + + +
+
+ +
+

Vorhandene Medien

+ {{if .Assets}} +
+ + + + + + {{range .Assets}} + + + + + + + {{end}} + +
TypTitelGröße
{{typeIcon .Type}}{{.Title}}{{if .SizeBytes}}{{humanSize .SizeBytes}}{{else}}–{{end}} +
+ +
+
+
+ {{else}} +

Noch keine Medien hochgeladen.

+ {{end}} +
+
+ +
+
+ + + +` +``` + +- [ ] **Step 2: Build & commit** + +```bash +cd server/backend && go build ./... && git add server/backend/internal/httpapi/tenant/templates.go && git commit -m "feat(ui): Tenant-Dashboard neu gestaltet" +``` + +--- + +## Task 8: Final build verification & integration test run + +**Files:** none (verification only) + +- [ ] **Step 1: Full build** + +```bash +cd server/backend && go build ./... +``` +Expected: no errors. + +- [ ] **Step 2: Run tests** + +```bash +cd server/backend && go test ./... +``` +Expected: all tests pass. If any test fails, investigate — do not skip. + +- [ ] **Step 3: Commit if tests required any fixes** + +Only if Step 2 required code changes: +```bash +git add -p && git commit -m "fix(ui): Test-Fixes nach Frontend-Overhaul" +```