From 0f2e7a38652319d01144d3ca6bcc13692ec59618 Mon Sep 17 00:00:00 2001 From: Guilhem Lemouel Date: Tue, 15 Sep 2026 18:04:20 +0200 Subject: [PATCH] docs(chat): say what a partial read actually costs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The comment offered the next turn's poll as a recovery route. It is not one: the cursor comes from the last persisted row, and applying none of them is exactly what leaves it where it was, so the next poll reads the same window against a conversation that has only grown. A reload is what recovers, and the comment says so — it changes how the trade-off reads. The warning blamed the page cap for both exits, so a cursor that stalled on the second request reported twenty pages. It names which one stopped it and where it got to. Co-Authored-By: Claude Opus 5 (1M context) --- .../conversations/FlowChatManager.svelte.ts | 23 ++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/frontend/src/lib/components/flows/conversations/FlowChatManager.svelte.ts b/frontend/src/lib/components/flows/conversations/FlowChatManager.svelte.ts index 95421beac6..04b973b8c7 100644 --- a/frontend/src/lib/components/flows/conversations/FlowChatManager.svelte.ts +++ b/frontend/src/lib/components/flows/conversations/FlowChatManager.svelte.ts @@ -935,7 +935,9 @@ export class FlowChatManager { const response: ChatMessage[] = [] let afterSeq = this.getLastPersistedMessageSeq(conversationId) let readWhole = false - for (let page = 0; page < POLL_MAX_PAGES; page++) { + let stalled = false + let pages = 0 + for (; pages < POLL_MAX_PAGES; pages++) { const batch = await FlowConversationsService.listConversationMessages({ workspace: this.#workspace()!, conversationId: conversationId, @@ -954,7 +956,10 @@ export class FlowChatManager { const furthest = Math.max(...batch.map((m) => m.created_seq)) // A page that moved nothing would ask for the same rows forever, and is no more a // finished read than the cap above. - if (afterSeq !== undefined && furthest <= afterSeq) break + if (afterSeq !== undefined && furthest <= afterSeq) { + stalled = true + break + } afterSeq = furthest } @@ -962,11 +967,17 @@ export class FlowChatManager { // Reading stopped before the conversation did, so none of this is a picture of // it. Appending would stand these rows beside the temp ones already showing the // same answer, and sweeping would drop the only thing showing what was never - // read. The transcript keeps what it has: the next turn's poll resumes from it, - // and a reload refetches. + // read, so the transcript keeps what it has. + // + // Nothing recovers it in this session: the cursor is taken from the last + // persisted row, and applying none of them is what leaves it where it was, so + // the next poll reads the same window against a conversation that has only + // grown. A reload refetches. That is the price of not showing a transcript + // that is part duplicate and part missing. console.warn( - `Stopped reading conversation ${conversationId} after ${POLL_MAX_PAGES} pages ` + - `(${response.length} rows, up to seq ${afterSeq}); leaving the transcript as it is` + `Stopped reading conversation ${conversationId} after ${pages + 1} page(s) ` + + `(${stalled ? 'the cursor stopped advancing' : `cap of ${POLL_MAX_PAGES}`}, ` + + `${response.length} rows, up to seq ${afterSeq}); leaving the transcript as it is` ) return }