Prepare v2.0.74 sidepanel reliability release

Restore and persist the package-based link collector with progressive metadata, bounded high-volume updates, safe hydration, visible selection, and complete localization.

Harden download controls, snapshot ordering, history pagination, statistics recovery, settings saves, backup imports, notification persistence, and Windows storage races with regression coverage.
This commit is contained in:
Sucukdeluxe
2026-08-26 20:35:06 +02:00
parent bdcf9e3754
commit 2ea494cd5c
69 changed files with 7660 additions and 883 deletions
+120 -2
View File
@@ -1,8 +1,9 @@
import { describe, expect, it } from "vitest";
import { describe, expect, it, vi } from "vitest";
import { validateCollectorPersistenceState } from "../src/shared/collector";
import {
createCollectorPersistenceCoordinator,
restoreCollectorPersistenceState
restoreCollectorPersistenceState,
type CollectorPersistenceFailure
} from "../src/renderer/views/collector/collector-persistence";
const packageEntry = {
@@ -45,6 +46,123 @@ describe("collector persistence payload", () => {
});
describe("collector persistence renderer flow", () => {
it("does not inspect or clone scheduled state", () => {
const coordinator = createCollectorPersistenceCoordinator(async (state) => state, 60_000);
const inaccessibleState = new Proxy({ packages: [], collapsedPackageIds: [] }, {
ownKeys: () => {
throw new Error("scheduled state was inspected");
}
});
expect(() => coordinator.schedule(inaccessibleState)).not.toThrow();
coordinator.dispose();
});
it("does not throw when timer scheduling fails", async () => {
const saved: string[] = [];
const coordinator = createCollectorPersistenceCoordinator(async (state) => {
saved.push(state.packages[0]?.id || "empty");
return state;
});
const timerSpy = vi.spyOn(globalThis, "setTimeout").mockImplementationOnce(() => {
throw new Error("timer failed");
});
try {
expect(() => coordinator.schedule({ packages: [packageEntry], collapsedPackageIds: [] })).not.toThrow();
} finally {
timerSpy.mockRestore();
}
await coordinator.flush();
expect(saved).toEqual(["package-one"]);
});
it("debounces twenty thousand progressive schedules before validating only the newest state", async () => {
const saved: string[] = [];
const coordinator = createCollectorPersistenceCoordinator(async (state) => {
saved.push(state.packages[0]?.id || "empty");
return state;
}, 60_000);
const cloneSpy = vi.spyOn(globalThis, "structuredClone");
try {
for (let index = 0; index < 20_000; index += 1) {
coordinator.schedule({
packages: [{ ...packageEntry, id: `progress-${index}` }],
collapsedPackageIds: []
});
}
expect(cloneSpy).not.toHaveBeenCalled();
await coordinator.flush();
expect(saved).toEqual(["progress-19999"]);
expect(cloneSpy).toHaveBeenCalledTimes(1);
} finally {
coordinator.dispose();
cloneSpy.mockRestore();
}
});
it("reports validation failures with the attempted state and configured rollback baseline", async () => {
const baseline = { packages: [packageEntry], collapsedPackageIds: ["package-one"] };
const attemptedState = {
packages: [{ ...packageEntry, name: "" }],
collapsedPackageIds: []
};
const failures: CollectorPersistenceFailure[] = [];
let saveCalls = 0;
const coordinator = createCollectorPersistenceCoordinator(async (state) => {
saveCalls += 1;
return state;
}, 60_000, (failure) => {
failures.push(failure);
});
coordinator.setBaseline(baseline);
expect(() => coordinator.schedule(attemptedState)).not.toThrow();
await expect(coordinator.flush()).resolves.toBeUndefined();
expect(saveCalls).toBe(0);
expect(failures).toHaveLength(1);
expect(failures[0]?.error).toBeInstanceOf(Error);
expect(failures[0]?.attemptedState).toBe(attemptedState);
expect(failures[0]?.rollbackState).toBe(baseline);
});
it("reports save failures without rejecting flush and rolls back to the last successful save", async () => {
const failures: CollectorPersistenceFailure[] = [];
const coordinator = createCollectorPersistenceCoordinator(async (state) => {
if (state.packages[0]?.id === "save-fails") throw new Error("save failed");
return state;
}, 60_000, (failure) => {
failures.push(failure);
});
const successfulState = {
packages: [{ ...packageEntry, id: "saved" }],
collapsedPackageIds: []
};
const failedState = {
packages: [{ ...packageEntry, id: "save-fails" }],
collapsedPackageIds: []
};
coordinator.schedule(successfulState);
await coordinator.flush();
coordinator.schedule(failedState);
await expect(coordinator.flush()).resolves.toBeUndefined();
expect(failures).toHaveLength(1);
expect(failures[0]?.error).toEqual(new Error("save failed"));
expect(failures[0]?.attemptedState).toBe(failedState);
expect(failures[0]?.rollbackState).toEqual(successfulState);
expect(failures[0]?.rollbackState).not.toBe(successfulState);
});
it("merges a late restore with current imports and keeps collapse state from both sides", () => {
const persisted = { packages: [packageEntry], collapsedPackageIds: ["package-one"] };
const currentPackage = {