From 3327309e8b119364d3eaccbb37942cd88b983c75 Mon Sep 17 00:00:00 2001 From: amosblomqvist Date: Thu, 24 Sep 2026 15:29:01 +0300 Subject: [PATCH] The all-sessions coverage GET dies: the per-session block is the coverage surface MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extraction-UX close-out interlude (master dispatch 2026-09-24, off UI's ff1ec7a confirmation): nothing client-side consumes GET /api/graph/coverage — the sidebar block and the chat-bar ring both read GET /api/chats/[id]'s coverage block. The dead route (app/api/graph/coverage/route.ts) is deleted; the manual-extract front door (POST /api/graph/coverage/extract) stays by design. No server helper was orphaned — the route was composition-only over shared seams (chatStore, extractionStatuses, stores, coverageOf). The sessionCoverage doc + the chats/[id] route comment now point at the per-session block as THE coverage surface. Pin (tests/coverage.test.ts): the route file's absence (prompt-overrides' structural-pin precedent) + the Extract POST's survival — a merge cannot resurrect the dead surface silently. --- app/api/chats/[id]/route.ts | 5 +++-- app/api/graph/coverage/route.ts | 40 --------------------------------- src/server/chats.ts | 9 ++++---- tests/coverage.test.ts | 15 ++++++++++++- 4 files changed, 22 insertions(+), 47 deletions(-) delete mode 100644 app/api/graph/coverage/route.ts diff --git a/app/api/chats/[id]/route.ts b/app/api/chats/[id]/route.ts index 2ff52d0..b885575 100644 --- a/app/api/chats/[id]/route.ts +++ b/app/api/chats/[id]/route.ts @@ -7,8 +7,9 @@ export async function GET(_req: Request, { params }: { params: Promise<{ id: str // turnInFlight lets a returning client re-attach to a running turn // (chat-handler write-ahead + registry — see src/server/turn-liveness.ts). // coverage (extraction-UX redesign, 2026-09-24): the chat view's ring - // meter — one session's read of the same derivation /api/graph/coverage - // serves for all sessions. getChat already 404'd, so the block exists. + // meter + sidebar block — THE coverage surface (the all-sessions GET + // route died with the redesign; the Extract POST stays). getChat + // already 404'd, so the block exists. return Response.json({ ...chat, turnInFlight: isTurnRunning(id), coverage: sessionCoverage(id) }); } diff --git a/app/api/graph/coverage/route.ts b/app/api/graph/coverage/route.ts deleted file mode 100644 index 8ebecfe..0000000 --- a/app/api/graph/coverage/route.ts +++ /dev/null @@ -1,40 +0,0 @@ -/** - * GET /api/graph/coverage — the exact extraction coverage of every session - * (vision-extraction-coverage.md, settled 2026-09-15): which sessions have - * unextracted content, how much (tokens + message counts), and which runs - * are in flight. "Uncovered" is DERIVED here per request — fingerprint the - * current transcript, diff against the coverage ledger — never tracked as - * a second ledger. This is the data behind the graph view's coverage panel - * and its per-session "extract what's missing" buttons. - */ -import type { UIMessage } from "ai"; -import { chatStore, extractionStatuses, stores } from "@/src/server"; -import { coverageOf } from "@/src/core"; - -export const dynamic = "force-dynamic"; - -export async function GET() { - const running = new Set( - extractionStatuses().filter((run) => run.kind === "chat").map((run) => run.chatId), - ); - const entries = stores.graphStore.readCoverage(); - const chats = chatStore.listChats().map((summary) => { - const record = chatStore.getChat(summary.id); - const messages = (record?.messages ?? []) as UIMessage[]; - const cov = coverageOf(messages, entries, summary.id); - return { - chatId: summary.id, - title: summary.title, - updatedAt: summary.updatedAt, - totalMessages: cov.slots.length, - coveredMessages: cov.coveredCount, - uncoveredMessages: cov.uncoveredCount, - coveredTokens: cov.coveredTokens, - uncoveredTokens: cov.uncoveredTokens, - running: running.has(summary.id), - }; - }); - // Most uncovered first — the actionable sessions lead the panel. - chats.sort((a, b) => b.uncoveredTokens - a.uncoveredTokens || (a.updatedAt < b.updatedAt ? 1 : -1)); - return Response.json({ chats }); -} \ No newline at end of file diff --git a/src/server/chats.ts b/src/server/chats.ts index 62a2d91..c4f2070 100644 --- a/src/server/chats.ts +++ b/src/server/chats.ts @@ -114,10 +114,11 @@ export async function deleteChatSession(chatId: string, mode: ChatDeleteMode): P } /** One session's coverage read (extraction-UX redesign, owner dispatch - * 2026-09-24): the chat view's ring meter + popover data — the same - * derivation the all-sessions list serves (/api/graph/coverage), scoped - * to one chat. "Uncovered" stays DERIVED per request (fingerprint the - * transcript, diff the coverage ledger — never dual-tracked). + * 2026-09-24): the chat view's ring meter + popover data, scoped to one + * chat (the all-sessions GET died with this redesign — nothing consumed + * it once the sidebar and ring read here; the Extract POST stays). + * Uncovered stays DERIVED per request (fingerprint the transcript, + * diff the coverage ledger — never dual-tracked). * * lastFoldedAt: the newest coverage row's fold timestamp — a cheap single * pass over the chat's ledger rows (the ring popover's "last extraction" diff --git a/tests/coverage.test.ts b/tests/coverage.test.ts index 3b7fdb5..2bb5605 100644 --- a/tests/coverage.test.ts +++ b/tests/coverage.test.ts @@ -1,5 +1,6 @@ import assert from "node:assert/strict"; import { describe, it } from "node:test"; +import * as fs from "node:fs"; import type { UIMessage } from "ai"; import { @@ -156,4 +157,16 @@ describe("batchTag — deterministic, ref-stable, mutation-sensitive", () => { const mutated = coverageSlots([user("m1", "aaaa!"), user("m2", "bbbb")]); assert.notEqual(batchTag(slots), batchTag(mutated)); }); -}); \ No newline at end of file +}); +describe("the all-sessions coverage GET is dead (extraction-UX close-out, 2026-09-24)", () => { + it("the route file is gone — the only coverage endpoint is the Extract POST", () => { + // UI confirmation (ff1ec7a): nothing client-side consumes the + // all-sessions list anymore — the sidebar block and the ring both + // read GET /api/chats/[id]'s coverage block. This pin guards against + // a merge resurrecting the dead surface. + assert.equal(fs.existsSync("app/api/graph/coverage/route.ts"), false, "the all-sessions GET route is deleted"); + // stays by design: the manual-extract front door (sidebar block's + // Extract affordance) lives on. + assert.equal(fs.existsSync("app/api/graph/coverage/extract/route.ts"), true, "the Extract POST survives"); + }); +});