UI Phase 2: Einstellungen-Tab neu strukturiert (Untergruppen + Erklaertexte) + konfigurierbarer Verlauf (Obergrenze + Zeitlimit)

Settings-Rework (User-Wunsch, mit Multi-Agent-Design-Runde fuer die Taxonomie:
3 Lenses -> Synthese, gegen das echte settingsDraft-Inventar auf Vollstaendigkeit
geprueft, 51 Einstellungen je genau einmal platziert).

UI (App.tsx + styles.css):
- 5 der 6 Bereiche (Allgemein/Entpacken/Geschwindigkeit/Bereinigung/Updates) in
  klare Untergruppen mit Unter-Ueberschriften (.settings-subhead) gegliedert, je
  Bereich ein Intro, und unter JEDER Einstellung ein knapper Erklaertext
  (.setting-hint, <= ~90 Zeichen, echte Umlaute). Reihenfolge nach Aufgaben-Logik
  (z.B. Allgemein: Speicherort / Download-Verhalten / Verlauf / Oberflaeche /
  Discord; Entpacken: Ziel & Ablauf / Deutsche Tonspur / Ablageform / Leistung /
  Passwoerter). Einige Labels praezisiert (z.B. "Codeberg Repo" -> "Update-Quelle",
  "Light Mode" -> "Heller Modus"). Account-Bereich bleibt fuer Phase 3 unangetastet.

Verlauf-Retention konfigurierbar (vorher: hart 500, kein Zeitlimit):
- Neue Settings historyMaxEntries (Standard 500) + historyMaxAgeDays (Standard 0=aus)
  in types/constants/normalizeSettings (geclamped 50..100000 bzw. 0..3650) + App-
  Default-Snapshot. Zwei neue Zahlenfelder im Bereich Allgemein -> Verlauf, direkt
  unter "Verlauf speichern"; ausgegraut wenn nicht "Dauerhaft".
- storage.ts: pruneHistoryEntries(entries, limits) wendet Alters- (completedAt <
  jetzt - Tage) und Anzahl-Grenze an; load/save/addHistoryEntry + die *ForRetention-
  Wrapper nehmen optionale Limits. app-controller reicht die Limits aus den Settings
  durch (historyLimits()) und schneidet den Verlauf bei Aenderung der Grenzen aktiv
  neu (damit "aelter als X Tage" wirklich von der Platte fliegt).

2 neue storage-Tests (Anzahl-Cap, Alters-Pruning). 810 Tests gruen, tsc=6 Baseline,
self-check + build ok.
This commit is contained in:
Sucukdeluxe
2026-06-15 00:49:19 +02:00
parent 3a37c5a537
commit de154ab783
7 changed files with 296 additions and 94 deletions
+53
View File
@@ -332,6 +332,59 @@ describe("settings storage", () => {
expect(loadHistory(paths)).toEqual([]);
});
it("caps persisted history to the configured maxEntries", () => {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "rd-store-"));
tempDirs.push(dir);
const paths = createStoragePaths(dir);
const now = Date.now();
const entries = Array.from({ length: 10 }, (_unused, i) => ({
id: `h-${i}`,
name: `e${i}`,
totalBytes: 1,
downloadedBytes: 1,
fileCount: 1,
provider: "realdebrid" as const,
completedAt: now - i * 1000,
durationSeconds: 1,
status: "completed" as const,
outputDir: path.join(dir, "out"),
urls: []
}));
saveHistory(paths, entries, { maxEntries: 3, maxAgeDays: 0 });
const loaded = loadHistory(paths, { maxEntries: 3, maxAgeDays: 0 });
expect(loaded).toHaveLength(3);
expect(loaded.map((e) => e.id)).toEqual(["h-0", "h-1", "h-2"]);
});
it("drops history entries older than maxAgeDays", () => {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "rd-store-"));
tempDirs.push(dir);
const paths = createStoragePaths(dir);
const now = Date.now();
const day = 24 * 60 * 60 * 1000;
const fresh = {
id: "fresh",
name: "fresh",
totalBytes: 1,
downloadedBytes: 1,
fileCount: 1,
provider: "realdebrid" as const,
completedAt: now - 2 * day,
durationSeconds: 1,
status: "completed" as const,
outputDir: path.join(dir, "out"),
urls: []
};
const old = { ...fresh, id: "old", name: "old", completedAt: now - 40 * day };
saveHistory(paths, [fresh, old], { maxEntries: 500, maxAgeDays: 30 });
const loaded = loadHistory(paths, { maxEntries: 500, maxAgeDays: 30 });
expect(loaded.map((e) => e.id)).toEqual(["fresh"]);
});
it("assigns and preserves bandwidth schedule ids", () => {
const normalized = normalizeSettings({
...defaultSettings(),