diff options
| author | Adam Malczewski <[email protected]> | 2026-06-12 19:00:29 +0900 |
|---|---|---|
| committer | Adam Malczewski <[email protected]> | 2026-06-12 19:00:29 +0900 |
| commit | 1776912f39830060f47aeca2cf452674786da36d (patch) | |
| tree | b1e6f01704296260ae06157fa941a2d31dcf9b47 /src/features/chat/test-helpers.ts | |
| parent | 4d2e6d817f65a13631f00786c45b403d7d7d58ec (diff) | |
| download | dispatch-web-1776912f39830060f47aeca2cf452674786da36d.tar.gz dispatch-web-1776912f39830060f47aeca2cf452674786da36d.zip | |
feat(chat): consume CR-5 history windowing — server-windowed cold loads + show-earlier backfill
Re-pinned [email protected]>0.10.0 + [email protected]>0.6.1 (reply
frontend-history-windowing-handoff.md); re-mirrored both .dispatch references.
- HistorySync port gains optional { limit?, beforeSeq? } (CR-5 params); the
app's createHistorySync appends them to GET /conversations/:id.
- COLD-cache fresh load now fetches ?sinceSeq=0&limit=<floor(0.75xL)> — a huge
conversation no longer ships whole to show 192 chunks. A warm-cache tail sync
stays unwindowed (windowing a tail that outgrew the limit would leave a
silent seq gap behind the cache).
- hasEarlier now derives from the [email protected] CONTRACT (1-based gap-free seqs):
loaded window starting above seq 1 => older history exists — covering both
locally-trimmed AND server-windowed transcripts (the watermark stays as the
merge floor only).
- showEarlier(): local cache first; when the cache doesn't reach far enough
back, backfills the missing older run via ?beforeSeq=<oldestKnown>&limit=
and persists it (next page-in is local). latestSeq windowed-read caveat is
satisfied structurally (tail cursor derives from the cache's max seq).
- live-probe: +6 CR-5 checks (seq origin, newest-k ascending, short-chat
exactness, beforeSeq paging, 400 validation x2). NOT yet run live — backend
was down at commit time; run pending.
- backend-handoff.md: CR-5 RESOLVED, pins/mirrors current. 602 tests green x2.
Diffstat (limited to 'src/features/chat/test-helpers.ts')
| -rw-r--r-- | src/features/chat/test-helpers.ts | 23 |
1 files changed, 17 insertions, 6 deletions
diff --git a/src/features/chat/test-helpers.ts b/src/features/chat/test-helpers.ts index 07dad26..6bb98a1 100644 --- a/src/features/chat/test-helpers.ts +++ b/src/features/chat/test-helpers.ts @@ -1,6 +1,6 @@ import type { StoredChunk } from "@dispatch/wire"; import type { ConversationCache } from "../conversation-cache"; -import type { ChatTransport, HistorySync, MetricsSync } from "./ports"; +import type { ChatTransport, HistorySync, HistoryWindow, MetricsSync } from "./ports"; export interface FakeTransport { readonly sent: import("@dispatch/transport-contract").ChatSendMessage[]; @@ -20,14 +20,14 @@ export function createFakeTransport(): FakeTransport { } export interface FakeHistorySync { - readonly calls: Array<{ conversationId: string; sinceSeq: number }>; + readonly calls: Array<{ conversationId: string; sinceSeq: number; window?: HistoryWindow }>; /** Set the chunks to return on the next call. */ returnChunks: readonly StoredChunk[]; readonly impl: HistorySync; } export function createFakeHistorySync(): FakeHistorySync { - const calls: Array<{ conversationId: string; sinceSeq: number }> = []; + const calls: Array<{ conversationId: string; sinceSeq: number; window?: HistoryWindow }> = []; let returnChunks: readonly StoredChunk[] = []; return { calls, @@ -37,9 +37,20 @@ export function createFakeHistorySync(): FakeHistorySync { set returnChunks(v: readonly StoredChunk[]) { returnChunks = v; }, - impl: async (conversationId, sinceSeq) => { - calls.push({ conversationId, sinceSeq }); - const chunks = returnChunks; + impl: async (conversationId, sinceSeq, window) => { + calls.push({ conversationId, sinceSeq, ...(window !== undefined ? { window } : {}) }); + // Apply the CR-5 WINDOW semantics (`beforeSeq` bound, then newest-`limit`) + // so store tests exercise the real windowed flows. `sinceSeq` filtering is + // deliberately NOT applied — tests set `returnChunks` to the slice they + // mean the server to hold past the cursor. + let chunks = returnChunks; + const before = window?.beforeSeq; + if (before !== undefined) { + chunks = chunks.filter((c) => c.seq < before); + } + if (window?.limit !== undefined && chunks.length > window.limit) { + chunks = chunks.slice(-window.limit); + } const latestSeq = chunks.length > 0 ? Math.max(...chunks.map((c) => c.seq)) : sinceSeq; return { chunks, latestSeq }; }, |
