diff --git a/server/appRuntime.test.ts b/server/appRuntime.test.ts index f8425ec..c269b3e 100644 --- a/server/appRuntime.test.ts +++ b/server/appRuntime.test.ts @@ -1084,6 +1084,49 @@ test("listIssues stays cached-only when issue automation is off", async () => { assert.equal(buildOctokitCalls, 0, "disabled issue automation must not enrich list rows through GitHub"); }); +test("listIssues excludes closed worked issues from the default open issue count", async () => { + const storage = new MemStorage(); + await storage.updateConfig({ autoIssues: false, watchedRepos: ["owner/repo"] }); + const makeIssue = (number: number, title: string) => ({ + number, + title, + body: null, + bodyHtml: null, + url: `https://github.com/owner/repo/issues/${number}`, + repoFullName: "owner/repo", + repoCloneUrl: "https://github.com/owner/repo.git", + author: "alice", + labels: ["needs-triage"], + assignees: [], + comments: 0, + createdAt: "2026-05-03T17:00:00.000Z", + updatedAt: "2026-05-03T18:00:00.000Z", + }); + await storage.upsertSyncedIssues("owner/repo", [ + makeIssue(1, "Open issue"), + makeIssue(2, "Open worked issue"), + makeIssue(3, "Closed worked issue"), + ], "2026-05-03T18:00:00.000Z"); + await storage.markSyncedIssueWorked("owner/repo", 2); + await storage.markSyncedIssueWorked("owner/repo", 3); + await storage.markRepoIssuesStale("owner/repo"); + await storage.upsertSyncedIssues("owner/repo", [ + makeIssue(1, "Open issue"), + makeIssue(2, "Open worked issue"), + ], "2026-05-03T19:00:00.000Z"); + + const runtime = createAppRuntime({ + storage, + startBackgroundServices: false, + startWatcher: false, + }); + + const page = await runtime.listIssues({ limit: 50, offset: 0 }); + + assert.equal(page.totalCount, 2); + assert.deepEqual(page.items.map((issue) => issue.number), [2, 1]); +}); + test("getIssue stays cached-only when issue automation is off", async () => { const storage = new MemStorage(); await storage.updateConfig({ autoIssues: false, watchedRepos: ["owner/repo"] }); diff --git a/server/appRuntime.ts b/server/appRuntime.ts index 118dcce..d668b20 100644 --- a/server/appRuntime.ts +++ b/server/appRuntime.ts @@ -1528,8 +1528,8 @@ export function createAppRuntime(dependencies: AppRuntimeDependencies = {}): App }; } - const synced = await storage.listSyncedIssues({ repos: config.watchedRepos, limit, offset, includeWorked: true }); - const counts = await storage.listSyncedIssueCounts({ repos: config.watchedRepos, includeWorked: true }); + const synced = await storage.listSyncedIssues({ repos: config.watchedRepos, limit, offset }); + const counts = await storage.listSyncedIssueCounts({ repos: config.watchedRepos }); const issueAutomationEnabled = config.autoIssues !== false; const octokit = issueAutomationEnabled ? await buildOctokit(config) : null; const workJobs = await storage.listBackgroundJobs({ kind: "work_issue" }); diff --git a/server/memoryStorage.ts b/server/memoryStorage.ts index 0298caf..6b7c7be 100644 --- a/server/memoryStorage.ts +++ b/server/memoryStorage.ts @@ -367,7 +367,7 @@ export class MemStorage implements IStorage { const repos = new Set(input.repos.map((repo) => repo.toLowerCase())); const includeWorked = input.includeWorked ?? false; const all = Array.from(this.syncedIssues.values()) - .filter((record) => (record.isOpen || (includeWorked && record.isWorked)) && repos.has(record.repo.toLowerCase()) && (includeWorked || !record.isWorked)) + .filter((record) => (record.isOpen || (includeWorked && record.isWorked)) && repos.has(record.repo.toLowerCase())) .sort((a, b) => b.number - a.number); const items = all.slice(input.offset, input.offset + input.limit).map((record) => structuredClone(record)); return { items, hasMore: input.offset + items.length < all.length }; @@ -381,7 +381,6 @@ export class MemStorage implements IStorage { for (const record of Array.from(this.syncedIssues.values())) { if (!record.isOpen && !(includeWorked && record.isWorked)) continue; if (!repos.has(record.repo.toLowerCase())) continue; - if (!includeWorked && record.isWorked) continue; repoTotals[record.repo] = (repoTotals[record.repo] ?? 0) + 1; totalCount += 1; } diff --git a/server/sqliteStorage.ts b/server/sqliteStorage.ts index 46af77c..11f0c2b 100644 --- a/server/sqliteStorage.ts +++ b/server/sqliteStorage.ts @@ -2267,13 +2267,12 @@ export class SqliteStorage implements IStorage { ` SELECT repo, issue_number, payload_json, is_open, is_worked, first_seen_at, last_seen_at FROM synced_issues - WHERE (is_open = 1 OR (? = 1 AND is_worked = 1)) AND repo IN (${placeholders}) AND (? = 1 OR is_worked = 0) + WHERE (is_open = 1 OR (? = 1 AND is_worked = 1)) AND repo IN (${placeholders}) ORDER BY issue_number DESC LIMIT ? OFFSET ? `, includeWorked ? 1 : 0, ...input.repos, - includeWorked ? 1 : 0, input.limit + 1, input.offset, ); @@ -2292,12 +2291,11 @@ export class SqliteStorage implements IStorage { ` SELECT repo, COUNT(*) as count FROM synced_issues - WHERE (is_open = 1 OR (? = 1 AND is_worked = 1)) AND repo IN (${placeholders}) AND (? = 1 OR is_worked = 0) + WHERE (is_open = 1 OR (? = 1 AND is_worked = 1)) AND repo IN (${placeholders}) GROUP BY repo `, includeWorked ? 1 : 0, ...input.repos, - includeWorked ? 1 : 0, ); const repoTotals: Record = {}; let totalCount = 0;