BUG-01: Resize-Handle griff nicht
Items hatten `draggable=true` (HTML5-DnD). Der Resize-Handle war Kind des
Items, also fired der Browser `dragstart` am Parent, sobald die Maus
überhaupt auf irgendeinem Kind war. `e.preventDefault()` im
`startResize()` mousedown-Handler kam zu spät — HTML5-DnD hatte den
Move-Drag bereits übernommen.
BUG-02: Resize-Drag war zäh
Pro mousemove rief der alte Code `renderGrid()` auf, das die komplette
Cell- und Item-Struktur neu aufbaute. Bei 60 events/s × 100+ DOM-Ops
pro Render entstand sichtbarer Jank.
Fix:
- HTML5-draggable komplett raus.
- Eigene Pointer-Events (`pointerdown`/`pointermove`/`pointerup`)
für Move (startItemPointer) und Resize (startResizePointer).
- Resize-Handle: `pointer-events:auto` auf Handle,
`pointer-events:none` auf `::before` (Dreieck-Deko), eigene
Listener, `stopPropagation()` damit Item-Handler nicht mitfeuert.
- `touch-action:none` auf Handle und Item-Body → Touch-Devices
scrollen nicht versehentlich beim Drag.
- Beide Aktionen nutzen requestAnimationFrame, und nur die
CSS-Geometrie (grid-column/grid-row) wird aktualisiert — kein
renderGrid() während Drag. Erst beim Drop / Resize-End kommt
der volle re-render + save.
- Resize nutzt `setPointerCapture` damit der Move auch dann
weiterläuft, wenn die Maus den Handle kurz verlässt.
Beweis: tests/test_pointer_events.js (Node-basiert, ohne Browser)
1. Resize stopPropagation wird aufgerufen
2. Item-Body pointerdown erreicht nur Item-Handler (nicht Resize)
3. CSS pointer-events korrekt gesetzt
4. renderGrid() in keiner onMove-Funktion (0/2)
5. requestAnimationFrame wird genutzt (3 calls)
6. draggable=true komplett entfernt (0 assignments)
7. touch-action:none auf Resize-Handle
ALLE 7 grün.
Manuelle Verifikation gegen laufenden Server (?demo=1):
GET /?demo=1 → 200, 91685 bytes
grep draggable=true: 0
grep startItemPointer|startResizePointer: 6
grep pointerdown: 3
grep requestAnimationFrame: 3
grep HTML5-drag handlers: 0 (nur 1 in Kommentar)
grep renderGrid(): 12, davon 0 in onMove-Handlern
Closes#3#4
Bei API-Fehler (HTTP 5xx, Timeout, DNS-Fehler, Connection refused) wird
3x retry mit backoff versucht. Bei 3x fail wird ein auffaelliges rotes
"!"-Schild mit Plugin-Name und Fehlermeldung statt leeren Slot gerendert.
base.py: NEU - fetch_url() Helper mit Retry-Logik + Error-Formatting
+ render_error_banner() Funktion fuer Plugin-Slots.
Co-Authored-By: Hermes <noreply@hermes.local>
Aus dem Backup und Live-Pull vom Pi (10.11.3.144):
- dashboard.py: Grid-Linien nur im freien Hintergrund (nicht durch Widgets)
- templates/index.html: komplett redesigned mit Sidebar + Topbar + Toast + Modal
- plugins/clock.py: responsive Layout (1x1 bis 4x4)
- plugins/system.py, weather.py, minimax.py: mit Threshold-Bars und Color-Variants
- plugins/base.py: NEU — fetch_with_retry Helper (3x retry mit backoff)
+ render_error_banner für fehlgeschlagene API-Plugins
(grosses rotes "!" Icon mit Plugin-Name und Fehler statt Crash)
Cleanup: Helfer-Chaos (renderer.py/2/3, design_a/b/c.html, clock_classic.py,
23x clock_*.png, alte test_*.py) wurde bereits im vorherigen Commit entfernt.
Co-Authored-By: Hermes <noreply@hermes.local>
Admin UI: Dark Minimal / Retro Terminal / Warm Editorial via header buttons
Display: Classic White / Dark / Sepia / Nord / Terminal via Settings tab
Both switchers persist via localStorage / config.json