perf(dashboard): slim task list + auto-archive stale done tasks

GET /api/tasks was returning ~69 MB of JSON per call (67.9 MB of agent
logs across 1199 tasks), causing the dashboard to hang for 2+ minutes.

- core: extend listTasks() with slim and includeArchived options
- dashboard: GET /api/tasks now uses slim mode and excludes archived
  by default; ?includeArchived=1 opts in
- frontend: lazy-load archived tasks when the archived column is first
  expanded via new useTasks.loadArchivedTasks()
- engine: self-healing maintenance now auto-archives done tasks older
  than 48h (data stays in SQLite, column flips done -> archived)
- tests: slim mode + includeArchived coverage in store.test.ts;
  routes.test.ts assertion updated for new args

Also bundles in-progress test-setup noise filters and pre-existing
QuickEntryBox/routes test work that was already modified locally.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
gsxdsm
2026-04-10 20:16:20 -07:00
parent ffa0edc7b2
commit 39a0f783bf
11 changed files with 333 additions and 82 deletions

View File

@@ -25,6 +25,8 @@ interface BoardProps {
onArchiveTask?: (id: string) => Promise<Task>;
onUnarchiveTask?: (id: string) => Promise<Task>;
onArchiveAllDone?: () => Promise<Task[]>;
/** Lazy-load archived tasks. Called the first time the user expands the archived column. */
onLoadArchivedTasks?: () => Promise<void>;
searchQuery?: string;
availableModels?: ModelInfo[];
/**
@@ -62,8 +64,9 @@ function areTaskArraysEqual(previous: Task[], next: Task[]): boolean {
return previous.every((task, index) => task === next[index]);
}
export function Board({ tasks, projectId, maxConcurrent, onMoveTask, onOpenDetail, addToast, onQuickCreate, onNewTask, autoMerge, onToggleAutoMerge, globalPaused, onUpdateTask, onArchiveTask, onUnarchiveTask, onArchiveAllDone, searchQuery = "", availableModels, onPlanningMode, onSubtaskBreakdown, onOpenDetailWithTab, favoriteProviders, favoriteModels, onToggleFavorite, onToggleModelFavorite, taskStuckTimeoutMs, onOpenMission }: BoardProps) {
export function Board({ tasks, projectId, maxConcurrent, onMoveTask, onOpenDetail, addToast, onQuickCreate, onNewTask, autoMerge, onToggleAutoMerge, globalPaused, onUpdateTask, onArchiveTask, onUnarchiveTask, onArchiveAllDone, onLoadArchivedTasks, searchQuery = "", availableModels, onPlanningMode, onSubtaskBreakdown, onOpenDetailWithTab, favoriteProviders, favoriteModels, onToggleFavorite, onToggleModelFavorite, taskStuckTimeoutMs, onOpenMission }: BoardProps) {
const [archivedCollapsed, setArchivedCollapsed] = useState(true);
const archivedLoadedRef = useRef(false);
const { fetchBatch } = useBatchBadgeFetch(projectId);
const debounceTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
// Normalized search-active signal: trimmed and non-empty
@@ -78,8 +81,15 @@ export function Board({ tasks, projectId, maxConcurrent, onMoveTask, onOpenDetai
});
const handleToggleArchivedCollapse = useCallback(() => {
setArchivedCollapsed((current) => !current);
}, []);
setArchivedCollapsed((current) => {
const next = !current;
if (!next && !archivedLoadedRef.current && onLoadArchivedTasks) {
archivedLoadedRef.current = true;
void onLoadArchivedTasks();
}
return next;
});
}, [onLoadArchivedTasks]);
// Tasks are already server-filtered when searchQuery is active (via useTasks hook).
// Client-side filtering is removed - tasks prop is used directly.