Skip to content

fix: reset message stream when conversation clears#268

Open
adliebe wants to merge 2 commits into
profullstack:masterfrom
adliebe:fix/message-stream-null-disconnect
Open

fix: reset message stream when conversation clears#268
adliebe wants to merge 2 commits into
profullstack:masterfrom
adliebe:fix/message-stream-null-disconnect

Conversation

@adliebe
Copy link
Copy Markdown

@adliebe adliebe commented May 27, 2026

Summary

  • closes Message stream stays connected after clearing conversation #267
  • derives visible connection, error, and typing state from the active conversation id so stale state from a cleared or changed conversation does not leak into the UI
  • clears the stored EventSource ref during cleanup and ignores late typing-poll completions after effect cleanup
  • adds a regression test for clearing conversationId after an SSE connection has opened

Validation

  • PNPM_CONFIG_IGNORE_SCRIPTS=true pnpm exec vitest run src/hooks/useMessageStream.test.ts -> 11 passed
  • PNPM_CONFIG_IGNORE_SCRIPTS=true pnpm exec eslint src/hooks/useMessageStream.ts src/hooks/useMessageStream.test.ts -> 0 errors, 0 warnings
  • PNPM_CONFIG_IGNORE_SCRIPTS=true pnpm exec tsc --noEmit -> passed
  • git diff --check -> passed

@adliebe
Copy link
Copy Markdown
Author

adliebe commented May 27, 2026

Submitted for the active Ugig testing/fix gig: https://ugig.net/gigs/abd6b2a0-e728-48cf-a46f-f99e419ed94e

Scope: real message-stream state bug, issue #267, focused fix plus regression test and validation listed in the PR body.

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented May 27, 2026

Greptile Summary

This PR fixes stale UI state when a conversation is cleared or switched by replacing per-state boolean/string tracking with an integer stream-ID scheme. Each effect run gets a unique streamId; derived values (isConnected, error, isOtherTyping) only surface if their stored stream ID matches the current one, so cleared or switched conversations can never expose state from a prior session.

  • Replaces isConnected/error state with connectedStreamId and connectionError (keyed by streamId + conversationId), and wraps isOtherTyping in an equivalent keyed object; all three derived values are now computed synchronously from streamIdRef.current on every render.
  • Adds a cancelled flag and streamIdRef guard to the typing-poll effect so in-flight fetches after cleanup are silently dropped rather than updating state.
  • Adds three regression tests covering: clearing the conversation ID resets connection state, reconnecting to the same ID waits for onopen, and no stale errors appear before reconnect opens.

Confidence Score: 5/5

Safe to merge — the stream-ID scheme correctly isolates all connection, error, and typing state per effect run, and the three new regression tests directly exercise the cleared-conversation and same-ID-reconnect paths.

The staleness fix is logically sound: every handler (onopen, onmessage, onerror) guards against stale streams by comparing the captured streamId to streamIdRef.current, and the polling effect captures both a cancelled flag and the stream ID to discard in-flight fetches after cleanup. All three derived values resolve correctly across the null to conv-123 and conv-123 to null to conv-123 transitions that were the original bug, and the new tests verify each scenario end-to-end.

No files require special attention.

Important Files Changed

Filename Overview
src/hooks/useMessageStream.ts Core hook refactored to use a monotonically-increasing stream ID for staleness detection; derived state logic is correct and all handler guards (onopen, onmessage, onerror) properly check the captured streamId against streamIdRef.current.
src/hooks/useMessageStream.test.ts Three new regression tests added; all cover the null-clear and same-ID-reconnect paths that were the original bug, with appropriate use of act, waitFor, and synchronous assertions where effects have already been flushed by rerender.

Sequence Diagram

sequenceDiagram
    participant C as Component
    participant H as useMessageStream
    participant ES as EventSource
    participant API as /typing API

    C->>H: "conversationId=conv-123"
    H->>H: "streamIdRef++ streamId=1"
    H->>ES: new EventSource
    ES-->>H: onopen
    H->>H: "setConnectedStreamId(1), isConnected=true"
    H->>API: "start polling streamId=1"

    C->>H: "conversationId=null"
    H->>ES: close
    H->>H: "streamIdRef 1->2->3, setConnectedStreamId(null)"
    H->>H: "isConnected=false, cancel poll"

    C->>H: "conversationId=conv-123"
    H->>H: "streamIdRef 3->4, isConnected=false"
    H->>ES: new EventSource
    ES-->>H: "onopen streamIdRef===4"
    H->>H: "setConnectedStreamId(4), isConnected=true"
    H->>API: "start polling streamId=4"
Loading

Reviews (2): Last reviewed commit: "fix: reset stale stream state before rec..." | Re-trigger Greptile

Comment thread src/hooks/useMessageStream.ts
@adliebe
Copy link
Copy Markdown
Author

adliebe commented May 27, 2026

Pushed a follow-up fix for the Greptile P1: reset stale connection/error/typing state whenever a new stream attempt starts, and key late SSE/poll updates by stream id so a null -> same conversation reconnect cannot report connected or show stale errors before onopen.\n\nValidation:\n- pnpm vitest run src/hooks/useMessageStream.test.ts -> 13 passed\n- pnpm type-check -> passed

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.

Message stream stays connected after clearing conversation

1 participant