Skip to content

fix: tabular review streams auto-resume after network drops - #33

Open
amal66 wants to merge 1 commit into
upstream-mainfrom
upstream-pr/stream-reliability
Open

fix: tabular review streams auto-resume after network drops#33
amal66 wants to merge 1 commit into
upstream-mainfrom
upstream-pr/stream-reliability

Conversation

@amal66

@amal66 amal66 commented Jul 17, 2026

Copy link
Copy Markdown
Owner

Summary

When a tabular review is generating and the network blips (laptop sleeps, Wi-Fi drops, a proxy kills the idle connection), the SSE stream dies and the grid freezes with cells stuck on "generating" — even though the server keeps extracting and writing results to the database. With this change the client notices the drop, reconnects (up to 5 times with linear backoff), and catches up on the remaining cells from the database, so the table keeps filling in instead of requiring a manual page refresh.

Changes

  • frontend/src/app/components/tabular/TabularReviewView.tsx — the generate stream reader is factored into an idempotent applyFrame + consume(response) helper that reports whether the terminal [DONE] was seen; if the stream drops first, a bounded resume loop (5 attempts, linear backoff) reconnects and keeps consuming.
  • frontend/src/app/lib/mikeApi.ts — new resumeTabularGeneration(reviewId) client call: GET /tabular-review/:reviewId/generate/stream.
  • backend/src/routes/tabular.ts — new GET /:reviewId/generate/stream resume endpoint: a pure observer that never re-triggers extraction. It computes the still-pending cells (targetPendingCells, exported for tests), then polls tabular_cells (the source of truth the generate run keeps writing to) every 3s, forwarding each newly terminal cell as the same cell_update SSE frame, until all targeted cells are terminal, the client disconnects, or a 15-minute cap elapses. Replayed frames are safe: the client matches cells by (document_id, column_index).
  • backend/tests/tabularGenerateStream.test.ts — unit tests for targetPendingCells (placed under backend/tests/ so they are outside the tsc build's src include; they run under the vitest harness).

Why

Long generate runs are exactly the streams most likely to hit an idle-timeout or transient network failure. The extraction work already survives the disconnect server-side (each finished cell is persisted before the frame is written), so the only missing piece was a way for the client to re-attach and observe the remaining progress. This ports the auto-resume behaviour from amal66/mike main, translated into this codebase's inline SSE consumption style.

Left out deliberately:

  • Server-side SSE heartbeats/keepalive: deferred to fix: add SSE heartbeats to prevent stream timeout on long tool calls Open-Legal-Products/mike#170, which already covers 15s : keepalive comments across the SSE endpoints (the fork's heartbeat matches what that PR does; no point duplicating it).
  • The fork's Redis/BullMQ async extraction queue (durable jobs + pub/sub progress channel). The resume endpoint here is the fork's DB-poll backstop only, which is self-sufficient: the DB is the source of truth in both designs and this repo has no Redis infrastructure.
  • The fork's shared @mike/api-client readSSE transport — out of scope per the port rules; the delta is expressed in the existing inline reader style instead.

Testing

  • cd backend && npm install && npm run build — green as committed.
  • cd frontend && npm install && npm run build — green as committed (with .env.local copied from .env.local.example).
  • With the vitest harness branch (upstream-pr/test-harness) merged locally: cd backend && npm test → 2 files, 15 tests passed (3 of them from this PR); cd frontend && npm test → 1 file, 8 tests passed (none added here).
  • Not verified end-to-end against a live Supabase/LLM stack in this environment.

Provenance

All changes are mechanical ports of code in amal66/mike@origin/main (commit b3166dd) — apps/web/src/app/components/tabular/TabularReviewView.tsx, packages/api-client/src/index.ts (resumeTabularGeneration), apps/api/src/modules/tabular/tabular.generateStream.ts (targetPendingCells, tailTabularRun, streamTabularRunView), apps/api/src/modules/tabular/tabular.routes.ts (the GET route), and apps/api/src/modules/tabular/__tests__/tabular.generateStream.test.ts. The resume endpoint's request-prep block is copied from this repo's own POST /:reviewId/generate prep. Exceptions (adaptations forced by upstream structure): (1) Redis pub/sub subscription, startSseHeartbeat, and the afterSubscribe enqueue hook are omitted from the tail loop — no Redis/queue infra exists here and heartbeats are deferred to olp#170 — leaving the fork's DB-poll reconcile as the only frame source; (2) attachActiveVersionPaths is dropped from the copied prep (the observer never reads storage paths); (3) client call rewritten from the fork's clientConfig.fetchImpl/apiUrl to this repo's fetch/API_BASE idiom; (4) fork comments referencing the async queue mode were reworded to describe this repo's synchronous run; (5) the test file drops the fork's vi.mock("lib/env") (only needed for the fork's queue import chain) and imports from routes/tabular; targetPendingCells' column parameter type is inlined as { index: number }[].

Credits & prior art

🤖 Generated with Claude Code

https://claude.ai/code/session_01CEguyEgXa9JjCciXCcVemC

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant