release: v2.0.6

Redesign the desktop workspace with task sidebars, live filters, clearer settings, and an accessible update dialog.

Harden encrypted backup imports, configuration persistence, history retention, queue snapshots, shutdown recovery, and update installation ordering.

Publish verified Windows artifacts and refreshed English documentation.
This commit is contained in:
Sucukdeluxe
2026-08-10 09:28:28 +02:00
commit 76a228fb7c
113 changed files with 36923 additions and 0 deletions
+115
View File
@@ -0,0 +1,115 @@
const { test } = require('node:test');
const assert = require('node:assert/strict');
const { pruneOldestTerminalJobs, TERMINAL_STATUSES } = require('../lib/queue-prune');
const j = (id, status) => ({ id, status });
test('returns null on empty / non-array input', () => {
assert.equal(pruneOldestTerminalJobs([], 5), null);
assert.equal(pruneOldestTerminalJobs(null, 5), null);
assert.equal(pruneOldestTerminalJobs(undefined, 5), null);
});
test('returns null when all jobs are non-terminal regardless of limit', () => {
const jobs = [j('a', 'queued'), j('b', 'uploading'), j('c', 'preview')];
assert.equal(pruneOldestTerminalJobs(jobs, 0), null);
assert.equal(pruneOldestTerminalJobs(jobs, 100), null);
});
test('returns null when terminal count is at or under the limit', () => {
const jobs = [j('a', 'done'), j('b', 'done'), j('c', 'queued')];
assert.equal(pruneOldestTerminalJobs(jobs, 2), null, 'terminal=2, limit=2 → no-op');
assert.equal(pruneOldestTerminalJobs(jobs, 3), null, 'terminal=2, limit=3 → no-op');
});
test('drops oldest terminal jobs when over the limit, keeps non-terminal', () => {
const jobs = [
j('t1', 'done'), // oldest terminal — should be dropped
j('t2', 'done'), // should be dropped
j('queued1', 'queued'),
j('t3', 'error'), // newest of the dropped block
j('uploading1', 'uploading'),
j('t4', 'done'), // kept (within limit window)
j('t5', 'skipped'), // kept
j('t6', 'aborted'), // kept
];
// 6 terminal, limit 3 → drop 3 oldest (t1, t2, t3)
const result = pruneOldestTerminalJobs(jobs, 3);
assert.notEqual(result, null);
const droppedIds = result.dropped.map(x => x.id).sort();
assert.deepEqual(droppedIds, ['t1', 't2', 't3']);
// Non-terminal jobs always kept; surviving terminals are the newest 3
const keptIds = result.kept.map(x => x.id);
assert.deepEqual(keptIds, ['queued1', 'uploading1', 't4', 't5', 't6']);
});
test('respects insertion order (oldest by index, not by status)', () => {
const jobs = [
j('older-error', 'error'),
j('newer-done', 'done'),
j('newest-aborted', 'aborted'),
];
const result = pruneOldestTerminalJobs(jobs, 1);
assert.deepEqual(result.dropped.map(x => x.id), ['older-error', 'newer-done']);
assert.deepEqual(result.kept.map(x => x.id), ['newest-aborted']);
});
test('drops everything terminal when limit is 0', () => {
const jobs = [
j('q', 'queued'),
j('d1', 'done'),
j('d2', 'done'),
j('e1', 'error'),
];
const result = pruneOldestTerminalJobs(jobs, 0);
assert.deepEqual(result.dropped.map(x => x.id), ['d1', 'd2', 'e1']);
assert.deepEqual(result.kept.map(x => x.id), ['q']);
});
test('rejects negative or non-finite limits', () => {
const jobs = [j('a', 'done'), j('b', 'done')];
assert.equal(pruneOldestTerminalJobs(jobs, -1), null);
assert.equal(pruneOldestTerminalJobs(jobs, NaN), null);
assert.equal(pruneOldestTerminalJobs(jobs, Infinity), null,
'Infinity is technically not finite; safer to treat as no-op');
});
test('TERMINAL_STATUSES set covers all 4 terminal kinds', () => {
assert.ok(TERMINAL_STATUSES.has('done'));
assert.ok(TERMINAL_STATUSES.has('skipped'));
assert.ok(TERMINAL_STATUSES.has('error'));
assert.ok(TERMINAL_STATUSES.has('aborted'));
assert.equal(TERMINAL_STATUSES.size, 4);
// Non-terminal must not be in the set
for (const s of ['queued', 'preview', 'uploading', 'retrying', 'getting-server']) {
assert.equal(TERMINAL_STATUSES.has(s), false, `${s} must not be terminal`);
}
});
test('handles malformed entries (null / missing status) without throwing', () => {
const jobs = [
null,
j('a', 'done'),
{ id: 'no-status' }, // no status
j('b', 'done'),
];
// 2 terminal, limit 1 → drop oldest (a). null and no-status entries stay
// because they aren't terminal. The function must not throw on them.
const result = pruneOldestTerminalJobs(jobs, 1);
assert.notEqual(result, null);
assert.deepEqual(result.dropped.map(x => x && x.id), ['a']);
assert.equal(result.kept.length, 3);
});
test('large queue: keeps the newest `limit` terminals', () => {
const jobs = [];
for (let i = 0; i < 5000; i++) jobs.push(j(`done-${i}`, 'done'));
const result = pruneOldestTerminalJobs(jobs, 500);
assert.notEqual(result, null);
assert.equal(result.dropped.length, 4500);
assert.equal(result.kept.length, 500);
// First kept = done-4500 (the 4501st original entry)
assert.equal(result.kept[0].id, 'done-4500');
assert.equal(result.kept[result.kept.length - 1].id, 'done-4999');
});