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

@@ -1307,8 +1307,39 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
return { ...task, prompt };
}
async listTasks(options?: { limit?: number; offset?: number }): Promise<Task[]> {
const rows = this.db.prepare('SELECT * FROM tasks ORDER BY createdAt ASC').all();
async listTasks(options?: {
limit?: number;
offset?: number;
/** When false, exclude tasks in the `archived` column. Default: true (backward compatible). */
includeArchived?: boolean;
/** When true, omit heavy fields (log, comments, steps, workflowStepResults, steeringComments)
* from each row to make list responses cheap for board-style consumers. Detail fields default
* to empty arrays in the returned Task objects; use `getTask(id)` to load full data. */
slim?: boolean;
}): Promise<Task[]> {
const includeArchived = options?.includeArchived ?? true;
const slim = options?.slim ?? false;
const slimColumns = `
id, title, description, "column", status, size, reviewLevel, currentStep,
worktree, blockedBy, paused, baseBranch, branch, baseCommitSha,
modelPresetId, modelProvider, modelId,
validatorModelProvider, validatorModelId,
planningModelProvider, planningModelId,
mergeRetries, stuckKillCount, recoveryRetryCount, nextRecoveryAt,
error, summary, thinkingLevel,
createdAt, updatedAt, columnMovedAt,
dependencies,
attachments, prInfo, issueInfo, mergeDetails,
breakIntoSubtasks, enabledWorkflowSteps, modifiedFiles,
missionId, sliceId, assignedAgentId, assigneeUserId,
checkedOutBy, checkedOutAt
`;
const selectClause = slim ? slimColumns : '*';
const whereClause = includeArchived ? '' : ` WHERE "column" != 'archived'`;
const sql = `SELECT ${selectClause} FROM tasks${whereClause} ORDER BY createdAt ASC`;
const rows = this.db.prepare(sql).all();
const tasks = (rows as any[]).map((row) => this.rowToTask(row));
// Sort by createdAt, then by numeric ID suffix for tie-breaking