feat: task trash, triage trash and retention settings for every area; admin can forbid keeping things for good
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 2m39s

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
sandrodachandClaude Opus 5.5 committed 2026-11-01 15:44:34 +01:00
1 parent a927a94a95
commit 5a9c5bac37
36 files changed
+1356 -175

No files matched your search

+21
View File
@@ -4,6 +4,8 @@ import {
DashboardStats,
Document,
TrashedDocument,
TrashedTask,
Retention,
JournalDay,
PushDevice,
BackupFile,
@@ -302,6 +304,7 @@ export const api = {
notificationSettings?: NotificationSettings;
doneRetentionDays?: number;
voiceInput?: '' | 'browser';
retention?: Retention;
}): Promise<User> {
return request<User>('/api/v1/auth/settings', {
method: 'PATCH',
@@ -479,6 +482,24 @@ export const api = {
return request<{ deleted: number }>('/api/v1/tasks/clear-done', { method: 'POST', body: JSON.stringify({ taskListId }) });
},
// Task trash: deleted tasks, cleared and automatically cleaned-up completed ones
async getTaskTrash(taskListId = ''): Promise<TrashedTask[]> {
const q = taskListId ? `?taskListId=${encodeURIComponent(taskListId)}` : '';
return request<TrashedTask[]>(`/api/v1/tasks/trash${q}`);
},
async restoreTrashedTask(id: string): Promise<Thought> {
return request<Thought>(`/api/v1/tasks/trash/${id}/restore`, { method: 'POST' });
},
async purgeTrashedTask(id: string): Promise<{ deleted: boolean }> {
return request<{ deleted: boolean }>(`/api/v1/tasks/trash/${id}`, { method: 'DELETE' });
},
async emptyTaskTrash(taskListId: string): Promise<{ deleted: number }> {
return request<{ deleted: number }>('/api/v1/tasks/trash/empty', { method: 'POST', body: JSON.stringify({ taskListId }) });
},
async purgeDiscarded(): Promise<{ purged: number }> {
return request<{ purged: number }>('/api/v1/thoughts/discarded/purge', { method: 'DELETE' });
},
+7
View File
@@ -48,3 +48,10 @@ export const formatDue = (due: string | undefined, now: Date = new Date()): stri
else label = `${pad(d)}.${pad(m)}.${y}`;
return time ? `${label}, ${time}` : label;
};
/** When something went to a trash, e.g. "01.11.2026, 14:05". */
export const formatDeletedAt = (iso: string) => {
const d = new Date(iso);
if (Number.isNaN(d.getTime())) return '';
return d.toLocaleString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit' });
};
+26
View File
@@ -115,6 +115,9 @@ export interface User {
doneRetentionDays?: number;
// Voice input: '' = the server's Whisper (default), 'browser' = the browser's recognition
voiceInput?: '' | 'browser';
// How long finished and deleted things are kept, in days (0 = for good; only when allowed)
retention?: Retention;
retentionNeverAllowed?: boolean;
organizationId?: string;
createdAt: string;
}
@@ -293,6 +296,9 @@ export interface SystemSettings {
backupEnabled?: boolean;
backupTime?: string;
backupRetentionDays?: number;
// People may keep finished and deleted things for good ("nie"); fixed when set via RETENTION_ALLOW_NEVER
retentionAllowNever?: boolean;
retentionNeverViaEnv?: boolean;
}
/** One file of a server backup listing. */
@@ -382,6 +388,26 @@ export interface JournalDay {
empty: boolean;
}
/** Delays in days per area (Kontoeinstellungen → Aufbewahrung); 0 = keep for good. */
export interface Retention {
doneTasks: number;
taskTrash: number;
triageTrash: number;
noteTrash: number;
listDone: number;
}
/** A deleted task waiting in the task trash. */
export interface TrashedTask {
id: string;
content: string;
taskListId: string;
isCompleted: boolean;
trashedAt: string;
trashedBy: string;
canEdit: boolean;
}
export interface TrashedDocument {
id: string;
title: string;