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

@@ -1322,6 +1322,42 @@ describe("TaskStore", () => {
expect(paged).toHaveLength(1);
expect(paged[0].id).toBe("FN-002");
});
it("slim mode returns metadata but drops heavy fields (log/comments/steps)", async () => {
const task = await store.createTask({ description: "Slim test" });
await store.logEntry(task.id, "heavy log entry that should not appear in slim list");
const fullList = await store.listTasks();
const slimList = await store.listTasks({ slim: true });
const full = fullList.find((t) => t.id === task.id)!;
const slim = slimList.find((t) => t.id === task.id)!;
expect(full.log.length).toBeGreaterThan(0);
expect(slim.id).toBe(task.id);
expect(slim.description).toBe("Slim test");
expect(slim.column).toBe(full.column);
expect(slim.log).toEqual([]);
expect(slim.steps).toEqual([]);
expect(slim.comments).toBeUndefined();
});
it("includeArchived=false excludes archived tasks; default includes them", async () => {
const keep = await store.createTask({ description: "Stays visible" });
const toArchive = await store.createTask({ description: "Will be archived" });
await store.moveTask(toArchive.id, "todo");
await store.moveTask(toArchive.id, "in-progress");
await store.moveTask(toArchive.id, "in-review");
await store.moveTask(toArchive.id, "done");
await store.archiveTask(toArchive.id);
const withArchived = await store.listTasks();
const withoutArchived = await store.listTasks({ includeArchived: false });
expect(withArchived.map((t) => t.id)).toEqual(expect.arrayContaining([keep.id, toArchive.id]));
expect(withoutArchived.map((t) => t.id)).toContain(keep.id);
expect(withoutArchived.map((t) => t.id)).not.toContain(toArchive.id);
});
});
describe("SQLite-first reads when task blobs are missing", () => {