feat(KB-129): dashboard performance optimizations

- Fix SSE hook cleanup to prevent memory leaks and stale connections
- Cap agent log memory and optimize batch log processing
- Memoize Board, Column, and TaskCard with custom comparator to reduce re-renders
- Stabilize column task arrays and preserve pagination across live updates
- Add TaskCardBadge component for PR/issue state display
- Remove deprecated GitHub polling code and archive functionality from core store
This commit is contained in:
gsxdsm
2026-03-30 11:26:55 -07:00
parent 753bec19df
commit 50cd478f2e
21 changed files with 1269 additions and 276 deletions

View File

@@ -562,6 +562,17 @@ describe("TaskStore", () => {
expect(tasks).toHaveLength(2);
expect(tasks.map((t) => t.id).sort()).toEqual(["KB-001", "PROJ-002"]);
});
it("supports pagination with limit and offset", async () => {
await store.createTask({ description: "Task 1" });
await store.createTask({ description: "Task 2" });
await store.createTask({ description: "Task 3" });
const paged = await store.listTasks({ limit: 1, offset: 1 });
expect(paged).toHaveLength(1);
expect(paged[0].id).toBe("KB-002");
});
});
describe("pauseTask", () => {

View File

@@ -367,7 +367,7 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
return { ...task, prompt };
}
async listTasks(): Promise<Task[]> {
async listTasks(options?: { limit?: number; offset?: number }): Promise<Task[]> {
if (!existsSync(this.tasksDir)) return [];
const entries = await readdir(this.tasksDir, { withFileTypes: true });
@@ -383,13 +383,22 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
}
}
return tasks.sort((a, b) => {
const sorted = tasks.sort((a, b) => {
const cmp = a.createdAt.localeCompare(b.createdAt);
if (cmp !== 0) return cmp;
const aNum = parseInt(a.id.slice(a.id.lastIndexOf("-") + 1), 10) || 0;
const bNum = parseInt(b.id.slice(b.id.lastIndexOf("-") + 1), 10) || 0;
return aNum - bNum;
});
const offset = Math.max(0, options?.offset ?? 0);
const limit = options?.limit;
if (limit === undefined) {
return sorted.slice(offset);
}
return sorted.slice(offset, offset + Math.max(0, limit));
}
async moveTask(id: string, toColumn: Column): Promise<Task> {