Skip to content

Fix chat unread: don't mark unread if user viewed the streaming response#134

Open
mcintyre94 wants to merge 1 commit into
mainfrom
update-chat-unread-logic-f4ff93f7
Open

Fix chat unread: don't mark unread if user viewed the streaming response#134
mcintyre94 wants to merge 1 commit into
mainfrom
update-chat-unread-logic-f4ff93f7

Conversation

@mcintyre94

Copy link
Copy Markdown
Owner

Summary

  • Adds hasSeenCurrentTurnResponse flag to ChatViewModel to track whether the user was actively viewing a turn while it streamed
  • Strengthens markChatUnread to guard on both !isActive and !hasSeenCurrentTurnResponse, fixing a race where isActive briefly drops to false at the exact moment the result event fires (e.g. during a NavigationStack view lifecycle transition)
  • Resets the flag at the start of each new user turn (sendMessage) and each reconnect (reconnectIfNeeded)

Test plan

  • Run unit tests — 6 new tests added covering: not-active/not-seen marks unread, active prevents unread, seen-during-streaming prevents unread, activated-mid-stream prevents unread, .connecting alone doesn't set the seen flag, .connecting.streaming transition does set it
  • Manual: send a message, watch the full response stream — no unread indicator should appear on the active chat
  • Manual: send a message, switch to another chat before the response finishes — unread indicator should appear when the response completes

🤖 Generated with Claude Code

Add hasSeenCurrentTurnResponse tracking to ChatViewModel so that a
false-unread is not triggered when isActive briefly drops to false at
the exact moment the result event fires (e.g. view lifecycle timing
during a NavigationStack transition).

The flag is set via didSet observers on both `status` (when transitioning
to .streaming while isActive) and `isActive` (when the user opens a chat
already in .streaming). It is reset at the start of each new user turn
(sendMessage) and each reconnect (reconnectIfNeeded).

markChatUnread now guards on !isActive && !hasSeenCurrentTurnResponse,
satisfying both "chat is not open" and "user genuinely hasn't seen this
response" before setting the unread indicator.

Adds 6 unit tests covering the key scenarios.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
@claude

claude Bot commented May 1, 2026

Copy link
Copy Markdown

Code review

One issue found: hasSeenCurrentTurnResponse not reset in the queued-prompt path

Wisp/ViewModels/ChatViewModel.swift L838–L842

The reset on line 840 is not reached when sendMessage is called while the chat is already streaming. In that path, sendMessage returns early (after only setting queuedPrompt) and never hits this reset line. The queued prompt is later drained by calling executeClaudeCommand directly — which also has no reset — so hasSeenCurrentTurnResponse carries over from the previous turn.

Concrete scenario where chat is incorrectly not marked unread:

  1. User watches turn N stream → hasSeenCurrentTurnResponse = true
  2. While still streaming, user queues turn N+1 → sendMessage takes the early-return branch, flag is not reset
  3. User navigates away (isActive = false)
  4. Turn N completes → markChatUnread correctly skips (user did see it) ✓
  5. Queued turn N+1 is executed via executeClaudeCommand directly (no reset)
  6. Turn N+1 completes while user is away → markChatUnread is incorrectly suppressed because the stale flag from turn N is still true

The same issue applies to the interrupt path, which also calls executeClaudeCommand directly with a saved queued prompt.

Suggested fix: Reset hasSeenCurrentTurnResponse = false at the top of executeClaudeCommand — this covers all execution paths (queued-prompt drains, interrupt drain, and retries) with a single change.

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