From d0cc69410f503483d59e8c8f5737e72b4de02c6d Mon Sep 17 00:00:00 2001 From: ki Date: Sat, 29 Aug 2026 17:47:23 +0400 Subject: [PATCH] BUG-05: Speichern-Button weg, Save-Status-Indikator mit States MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Der 'Speichern'-Button war redundant — debouncedSave() lief schon bei jedem Drop/Resize/Add/Delete. Klick auf den Button machte nur einen sofortigen POST statt 500ms-debounce. Verwirrend statt hilfreich. Fix: - Speichern-Button aus dem Toolbar entfernt. - Stattdessen:
mit .save-dot und .save-label — rechtsbündig im Toolbar (margin-left:auto). - States: idle | pending | saving | error (CSS-Data-Attr-basiert). - idle: grüner Punkt, 'Gespeichert' - pending: warn-gelber Punkt pulsiert, 'Ungespeichert…' (debouncedSave-Start) - saving: info-blauer Punkt pulsiert schneller, 'Speichern…' (saveLayout-Start) - error: alert-roter Punkt, 'Fehler: ...' (j.ok=false) - setSaveState(state, label?) als zentraler Helper. - saveLayout() macht jetzt success-toast weg — der Indicator reicht. - Fehlerfall: error-toast bleibt (kritisch, separat sichtbar). Vorteile: - Toolbar ist aufgeräumter, +Button ist eindeutig der primäre Action. - User sieht Save-Status ohne Klick auf irgendwas — visuelles Feedback für Auto-Save. - Fehler-State zeigt sofort 'Fehler: ...' mit Details. Beweis: tests/test_save_indicator.js (11/11 grün) 1. Speichern-Button entfernt 2. Save-Indicator #saveIndicator vorhanden 3. setSaveState() Funktion definiert 4. setSaveState setzt dataset.state 5. debouncedSave setzt pending-Status 6. saveLayout setzt saving am Anfang 7. saveLayout setzt idle bei Erfolg 8. saveLayout setzt error bei j.ok=false 9. CSS für alle 4 Indicator-States 10. Kein success-toast in saveLayout mehr 11. Save-Indicator rechtsbündig (margin-left:auto) Closes #7 --- templates/index.html | 95 ++++++++++++++++++++++++++++++------ tests/test_save_indicator.js | 74 ++++++++++++++++++++++++++++ 2 files changed, 155 insertions(+), 14 deletions(-) create mode 100644 tests/test_save_indicator.js diff --git a/templates/index.html b/templates/index.html index ab7d1fe..0314035 100644 --- a/templates/index.html +++ b/templates/index.html @@ -330,6 +330,38 @@ display: flex; gap: 8px; flex-wrap: wrap; align-items: center; margin-bottom: 16px; } + /* BUG-05: Save-Status-Indikator (ersetzt den redundanten Speichern-Button) */ + .save-indicator { + display: inline-flex; align-items: center; gap: 6px; + margin-left: auto; /* rechtsbündig im toolbar */ + padding: 6px 10px; + border-radius: 6px; + font-size: 0.82em; + color: var(--fg-muted); + transition: color 0.15s ease; + } + .save-indicator .save-dot { + width: 8px; height: 8px; border-radius: 50%; + background: var(--ok); + box-shadow: 0 0 6px rgba(74, 222, 128, 0.5); + transition: background 0.15s ease, box-shadow 0.15s ease; + } + /* Zustände */ + .save-indicator[data-state="idle"] .save-dot { background: var(--ok); } + .save-indicator[data-state="pending"] .save-dot { + background: var(--warn); animation: savePulse 1s ease-in-out infinite; + } + .save-indicator[data-state="saving"] .save-dot { + background: var(--info); animation: savePulse 0.6s ease-in-out infinite; + } + .save-indicator[data-state="error"] .save-dot { + background: var(--alert); box-shadow: 0 0 6px rgba(248, 113, 113, 0.5); + } + .save-indicator[data-state="error"] { color: var(--alert); } + @keyframes savePulse { + 0%, 100% { opacity: 1; } + 50% { opacity: 0.35; } + } .add-form { display: flex; gap: 8px; align-items: center; background: var(--surface-2); @@ -1014,7 +1046,12 @@ - + +
+ + Gespeichert +
@@ -1244,8 +1281,50 @@ updateClock(); // ============ DRAG & DROP LAYOUT EDITOR ============ + async function saveLayout() { + // BUG-05: Status-Indikator statt redundantem "Speichern"-Button. + // Status-Lebenszyklus: + // pending → saving → idle (success) | error + setSaveState('saving'); + const r = await fetch('/api/layout', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ items: layoutItems }), + }); + const j = await r.json(); + if (!j.ok) { + setSaveState('error', 'Fehler: ' + (j.error || '?')); + toast('Layout speichern fehlgeschlagen: ' + (j.error || '?'), 'error', 5000); + return; + } + layoutItems = j.items; + renderGrid(); + setTimeout(refreshPreview, 500); + setSaveState('idle', 'Gespeichert'); + // nicht mehr länger toasten — der Indicator reicht + } + + // BUG-05: Save-Status-Indikator Helper + function setSaveState(state, label) { + const el = document.getElementById('saveIndicator'); + if (!el) return; + el.dataset.state = state; + const lbl = el.querySelector('.save-label'); + if (lbl && label) lbl.textContent = label; + // Idle-Reset: nach kurzer Anzeige "Gespeichert" zurück + if (state === 'saving' && !label) { + const lblEl = el.querySelector('.save-label'); + if (lblEl) lblEl.textContent = 'Speichern…'; + } + if (state === 'idle' && !label) { + const lblEl = el.querySelector('.save-label'); + if (lblEl) lblEl.textContent = 'Gespeichert'; + } + } + // BUG-05: debouncedSave setzt pending-Status let saveTimer = null; function debouncedSave() { + setSaveState('pending', 'Ungespeichert…'); if (saveTimer) clearTimeout(saveTimer); saveTimer = setTimeout(saveLayout, 500); } @@ -1608,19 +1687,7 @@ renderGrid(); toast('Widget hinzugefügt', 'success', 2000); } - async function saveLayout() { - const r = await fetch('/api/layout', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ items: layoutItems }), - }); - const j = await r.json(); - if (!j.ok) { toast('Layout speichern fehlgeschlagen: ' + (j.error || '?'), 'error', 5000); return; } - layoutItems = j.items; - renderGrid(); - setTimeout(refreshPreview, 500); - toast('Layout gespeichert', 'success'); - } + // BUG-05: alte saveLayout entfernt — siehe oben (mit setSaveState). async function packAll() { const ok = await modalConfirm({ icon: 'warn', diff --git a/tests/test_save_indicator.js b/tests/test_save_indicator.js new file mode 100644 index 0000000..8d901ca --- /dev/null +++ b/tests/test_save_indicator.js @@ -0,0 +1,74 @@ +// BUG-05 Test: Speichern-Button entfernt, Save-Indicator hinzugefügt. +// +// Erwartung: +// 1) KEIN