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

@@ -118,6 +118,38 @@ function buildMultipart(fieldName: string, filename: string, contentType: string
return { body, boundary };
}
describe("GET /tasks", () => {
let store: TaskStore;
beforeEach(() => {
store = createMockStore();
});
function buildApp() {
const app = express();
app.use(express.json());
app.use("/api", createApiRoutes(store));
return app;
}
it("returns tasks with optional pagination params", async () => {
(store.listTasks as ReturnType<typeof vi.fn>).mockResolvedValueOnce([FAKE_TASK_DETAIL]);
const res = await GET(buildApp(), "/api/tasks?limit=10&offset=5");
expect(res.status).toBe(200);
expect(res.body).toHaveLength(1);
expect(store.listTasks).toHaveBeenCalledWith({ limit: 10, offset: 5 });
});
it("returns 400 for invalid pagination params", async () => {
const res = await GET(buildApp(), "/api/tasks?limit=-1");
expect(res.status).toBe(400);
expect(res.body.error).toContain("limit");
});
});
describe("GET /tasks/:id", () => {
let store: TaskStore;

View File

@@ -582,9 +582,22 @@ export function createApiRoutes(store: TaskStore, options?: ServerOptions): Rout
registerModelsRoute(router, options?.modelRegistry);
// List all tasks
router.get("/tasks", async (_req, res) => {
router.get("/tasks", async (req, res) => {
try {
const tasks = await store.listTasks();
const limit = typeof req.query.limit === "string" ? Number.parseInt(req.query.limit, 10) : undefined;
const offset = typeof req.query.offset === "string" ? Number.parseInt(req.query.offset, 10) : undefined;
if (limit !== undefined && (!Number.isFinite(limit) || limit < 0)) {
res.status(400).json({ error: "limit must be a non-negative integer" });
return;
}
if (offset !== undefined && (!Number.isFinite(offset) || offset < 0)) {
res.status(400).json({ error: "offset must be a non-negative integer" });
return;
}
const tasks = await store.listTasks({ limit, offset });
res.json(tasks);
} catch (err: any) {
res.status(500).json({ error: err.message });