Skip to content

Improve Claude status indicators and unread notifications#1325

Merged
arnestrickmann merged 5 commits intomainfrom
emdash/feat-t12c10rgbf9f9fafafbfb11rgb00000000000010rgbf9f9fafafbfb11rg-4go
Mar 6, 2026
Merged

Improve Claude status indicators and unread notifications#1325
arnestrickmann merged 5 commits intomainfrom
emdash/feat-t12c10rgbf9f9fafafbfb11rgb00000000000010rgbf9f9fafafbfb11rg-4go

Conversation

@rabanspiegel
Copy link
Contributor

@rabanspiegel rabanspiegel commented Mar 6, 2026

Summary

  • add Claude-backed semantic status tracking for task rows and chat tabs
  • show unread-style blue dots for non-working updates and align indicators with the sidebar icon column
  • keep legacy PTY busy as a fallback, and align sidebar spinner behavior with the active chat/tab

Testing

  • pnpm run format
  • pnpm run lint
  • pnpm run type-check
  • pnpm exec vitest run

Note

Medium Risk
Adds a new cross-app status/unread store wired into terminal input/output and agent events, which can affect activity indicators and notification behavior across tasks. Risk is mostly in state synchronization (active view/unread clearing) and PTY lifecycle handling, not data/security.

Overview
Adds semantic agent status + unread tracking. Introduces agentStatusStore with new shared AgentStatusKind types and adapters to map Claude agent events (and confirmed PTY activity after user submit) into statuses, plus unread state when non-active conversations reach waiting/complete/error.

Updates UI indicators across the app. Replaces legacy busy spinners in ChatInterface tabs, TaskItem, and ProjectMainView rows with a unified TaskStatusIndicator (spinner for working, blue dot for unread terminal states), with useTaskBusy retained as a fallback when semantic status is unknown.

Wires status lifecycle into runtime events. Workspace forwards agent events to the new store; TerminalSessionManager and usePendingInjection mark submits and feed PTY output into the store; ChatInterface tracks the active conversation to set the visible status id and clear unread when viewing a tab. Includes new hooks (useConversationStatus, useTaskStatus, useTaskUnread, useStatusUnread) and tests for status/unread behavior.

Written by Cursor Bugbot for commit 69cdff8. This will update automatically on new commits. Configure here.

@vercel
Copy link

vercel bot commented Mar 6, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
docs Ready Ready Preview, Comment Mar 6, 2026 7:06am

Request Review

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

const isBusy = useTaskBusy(ws.id);
const taskStatus = useTaskStatus(ws.id);
const taskUnread = useTaskUnread(ws.id);
const displayStatus = taskStatus === 'unknown' && isBusy ? 'working' : taskStatus;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dropped task.status === 'running' database fallback indicator

Medium Severity

The old code checked (isRunning || ws.status === 'running') to show a spinner, using the database status field as a secondary fallback. The new displayStatus computation only falls back to isBusy (PTY-based detection) when taskStatus === 'unknown', completely dropping the ws.status === 'running' / task.status === 'running' database check. During app startup or before PTY activity classification kicks in, tasks that are actually running (per the database) won't show any status indicator.

Additional Locations (1)

Fix in Cursor Fix in Web

window.removeEventListener(CONVERSATIONS_CHANGED_EVENT, onChanged);
for (const off of chatUnsubs.values()) off?.();
};
}, [taskId, reloadChats]);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Substantial duplicated logic across status and unread hooks

Low Severity

useTaskStatus and useTaskUnread contain nearly identical scaffolding: the reloadChats callback, CONVERSATIONS_CHANGED_EVENT constant, syncChatIds/load/onChanged pattern, and conversation-change event wiring are all duplicated. Each independently fetches conversations from rpc.db.getConversations, manages its own subscription maps, and handles cleanup. A shared helper or combined hook would reduce the maintenance surface and risk of the two diverging.

Additional Locations (1)

Fix in Cursor Fix in Web

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Mar 6, 2026

Greptile Summary

This PR introduces a Claude-backed semantic status tracking system on top of the existing PTY-busy heuristic, replacing bare Spinner components with a shared TaskStatusIndicator that shows either a spinner (working) or a blue unread dot (waiting/complete/error). The changes fit cleanly into the existing event-driven architecture — Workspace.tsx fans out AgentEvents to both the existing activityStore and the new agentStatusStore, and the terminal session wires markUserInputSubmitted/handlePtyData hooks at the keypress level.

Key observations:

  • mapUserInputToStatus return value is discarded — the function is called only as a capability guard in markUserInputSubmitted; its returned AgentStatusKind is unused, making the adapter API misleading.
  • CONVERSATIONS_CHANGED_EVENT is duplicated across useTaskStatus.ts and useTaskUnread.ts — a rename or typo in one file would silently break the other.

The core state-machine logic is sound and well-tested, with no correctness-breaking bugs identified.

Confidence Score: 4/5

  • Safe to merge with minor follow-up on API design clarity.
  • The core state-machine logic is sound and well-tested with no correctness-breaking bugs. The two flagged issues (mapUserInputToStatus API design and CONVERSATIONS_CHANGED_EVENT duplication) are maintainability concerns rather than functional bugs and can be addressed in follow-up work without affecting current behavior.
  • No files require special attention. The implementation is solid across all components.

Last reviewed commit: 69cdff8

Comment on lines +23 to +26
export function mapUserInputToStatus(providerId: string): AgentStatusKind | null {
if (providerId === 'claude') return 'working';
return null;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mapUserInputToStatus returns an AgentStatusKind value that is never used. In AgentStatusStore.markUserInputSubmitted, the result is assigned to nextKind but only serves as a capability check (if (!nextKind) return); the actual 'working' string is discarded. This makes the function signature misleading — it appears to compute a status that will be applied, but it doesn't.

Consider renaming to reflect its actual role as a capability check:

Suggested change
export function mapUserInputToStatus(providerId: string): AgentStatusKind | null {
if (providerId === 'claude') return 'working';
return null;
}
export function supportsUserInputTracking(providerId: string): boolean {
return providerId === 'claude';
}

Then update markUserInputSubmitted to call supportsUserInputTracking directly.

import { rpc } from '../lib/rpc';

const EMPTY_STATUS = 'unknown' as AgentStatusKind;
const CONVERSATIONS_CHANGED_EVENT = 'emdash:conversations-changed';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The string constant 'emdash:conversations-changed' is independently defined here and also in useTaskUnread.ts (line 5). Duplicate constants like this are a maintenance risk — a rename or typo in one file silently breaks the other without a compile-time error.

Extract this constant to a shared location (e.g., src/renderer/lib/events.ts or src/shared/events.ts) and import it in both hooks.

Suggested change
const CONVERSATIONS_CHANGED_EVENT = 'emdash:conversations-changed';
import { CONVERSATIONS_CHANGED_EVENT } from '../lib/events';

@arnestrickmann
Copy link
Contributor

Nice!

@arnestrickmann arnestrickmann merged commit 2b32410 into main Mar 6, 2026
6 checks 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.

2 participants