# 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}}
Benutzername Erstellt
{{.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" ```