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