feat(tasks): completed tasks are deleted after a delay (default 30 days), "Erledigte leeren"
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 2m34s

thoughts.completed_at is set by triggers on every way a task is completed (migration 14; tasks
completed earlier count from the update). Users choose the delay (7/30/90/365 days or never,
users.done_retention_days); for tasks in a list the owner's delay counts. An hourly job deletes
expired completed tasks (not in the demo) and tells their viewers. POST /tasks/clear-done clears
completed tasks the user may edit. The done tab shows the setting and the button. v0.35.0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
sandrodachandClaude Opus 5.5 committed 2026-11-01 12:18:54 +01:00
1 parent 66b28e1998
commit f91926c208
16 files changed
+433 -6

No files matched your search

@@ -690,6 +690,31 @@ export const TasksView: React.FC<TasksViewProps> = ({
onRefreshStats();
};
// Completed tasks: cleared by hand or deleted after the owner's delay (done_cleanup.go)
const [doneRetentionDays, setDoneRetentionDays] = useState<number>(currentUser?.doneRetentionDays ?? 30);
useEffect(() => {
if (currentUser?.doneRetentionDays !== undefined) setDoneRetentionDays(currentUser.doneRetentionDays);
}, [currentUser?.doneRetentionDays]);
const handleChangeDoneRetention = async (days: number) => {
const previous = doneRetentionDays;
setDoneRetentionDays(days);
try {
await api.updateSettings({ doneRetentionDays: days });
} catch {
setDoneRetentionDays(previous);
}
};
const handleClearDone = async () => {
const scope = selectedListId === 'all' ? 'aller Listen, die du bearbeiten darfst,' : `aus „${currentSelectedList?.title || 'dieser Liste'}“`;
if (!window.confirm(`Alle erledigten Aufgaben ${scope} endgültig löschen?`)) return;
triggerHaptic(20);
await api.clearDoneTasks(selectedListId === 'all' ? '' : selectedListId);
await loadData();
onRefreshStats();
};
const handlePurgeAllDiscarded = async () => {
if (!window.confirm('Alle verworfenen Gedanken endgültig löschen?')) return;
triggerHaptic(20);
@@ -2586,8 +2611,44 @@ export const TasksView: React.FC<TasksViewProps> = ({
Papierkorb leeren
</button>
)}
{filter === 'done' && doneTasks.length > 0 && !isSelectedListReadOnly && (
<button onClick={handleClearDone} className="btn-action min-h-[38px] sm:min-h-0 ml-auto">
<Trash2 className="w-3.5 h-3.5" />
Erledigte leeren
</button>
)}
</div>
{/* What happens to completed tasks; the delay of the list owner counts */}
{filter === 'done' && (
<div className="flex flex-wrap items-center gap-x-2 gap-y-1 text-[11px] text-muted px-1">
{currentSelectedList && currentSelectedList.isOwner === false ? (
<span>
Erledigte Aufgaben dieser Liste räumt die Einstellung von {currentSelectedList.ownerName || 'der Eigentümerin bzw. dem Eigentümer'} auf.
</span>
) : (
<>
<span>Erledigte Aufgaben löschen</span>
<select
value={doneRetentionDays}
onChange={(e) => handleChangeDoneRetention(Number(e.target.value))}
className="h-9 sm:h-6 px-1.5 input-field text-xs bg-panel"
aria-label="Erledigte Aufgaben löschen nach"
>
<option value={7}>nach 7 Tagen</option>
<option value={30}>nach 30 Tagen</option>
<option value={90}>nach 90 Tagen</option>
<option value={365}>nach einem Jahr</option>
<option value={0}>nie</option>
</select>
<span className="text-faint">
{doneRetentionDays > 0 ? '(gezählt ab dem Abhaken; gilt für deine eigenen Listen)' : '(bleiben, bis du sie leerst)'}
</span>
</>
)}
</div>
)}
{loading ? (
<div className="p-6 text-center text-faint text-sm">Lade Aufgaben…</div>
) : filter === 'discarded' ? (