Skip to content

feat(desktop): pulse thread ingress rows while an agent works in the thread#1569

Closed
tellaho wants to merge 4 commits into
mainfrom
tho/threads-activity-indicator
Closed

feat(desktop): pulse thread ingress rows while an agent works in the thread#1569
tellaho wants to merge 4 commits into
mainfrom
tho/threads-activity-indicator

Conversation

@tellaho

@tellaho tellaho commented Jul 7, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds a subtle pulsing badge to thread ingress rows (MessageThreadSummaryRow) when an agent is actively working (bot typing) in that thread.

Approach

  • New threadScopedBotTypingHeadIdKey helper in useChannelActivityTyping.ts — deduped, sorted key of thread head ids with active bot typing (channel-scoped entries excluded).
  • ChannelPane derives one memoized ReadonlySet<string> from that key. Identity only changes when the set of working threads changes — not on each 1s typing prune tick.
  • Set flows MessageTimelineTimelineMessageListMessageRowItem; each MessageThreadSummaryRow receives only isAgentWorking: boolean. No row ever sees raw typing entries.
  • Badge: h-1.5 w-1.5 rounded-full bg-primary motion-safe:animate-pulse dot next to participant avatars, with an aria-label suffix for screen readers.
  • Channel-level working surfaces untouched — thread-only typing stays out of agentWorkingSignal.

Performance

  • Stable-key trick means zero row re-renders on prune ticks while the working set is unchanged.
  • When the set does change, only TimelineMessageList re-renders its callback path — MessageRow is memoized and its props don't include the set, so heavy markdown rows stay put.
  • No per-row timers or animated layout.

Testing

  • 4 new unit tests on the key helper (null exclusion, mixed entries, dedup, order stability).
  • All existing tests pass: typecheck ✅, pnpm test 1926/1926 ✅, pnpm check (biome + file sizes + px-text) ✅.

Files changed

  • desktop/src/features/channels/ui/useChannelActivityTyping.ts
  • desktop/src/features/channels/ui/useChannelActivityTyping.test.mjs
  • desktop/src/features/channels/ui/ChannelPane.tsx
  • desktop/src/features/messages/ui/MessageTimeline.tsx
  • desktop/src/features/messages/ui/TimelineMessageList.tsx
  • desktop/src/features/messages/ui/MessageThreadSummaryRow.tsx

npub1223z34hd7vtwc6qj4s7flsxkj644nlre2nthu7lrrmkumhu3xddsrx9r6w and others added 2 commits July 6, 2026 21:32
…thread

Derive a stable, sorted set of working thread head ids from thread-scoped
bot typing entries in ChannelPane and pass it down the timeline as a
ReadonlySet, so each thread summary row gets a plain boolean instead of
scanning raw typing entries. The set identity only changes when the set of
working threads changes — not on each 1s typing prune tick — preserving the
existing timeline memo boundaries. Channel-level working surfaces are
untouched; thread-only typing still stays out of agentWorkingSignal.

The badge is a small motion-safe pulsing dot on the matching
MessageThreadSummaryRow, with an aria-label suffix for screen readers.

Co-authored-by: Taylor Ho <taylorkmho@gmail.com>
Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
Co-authored-by: Taylor Ho <taylorkmho@gmail.com>
Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
@tellaho tellaho marked this pull request as draft July 7, 2026 04:37
npub1223z34hd7vtwc6qj4s7flsxkj644nlre2nthu7lrrmkumhu3xddsrx9r6w and others added 2 commits July 6, 2026 21:41
getTypingScopeId keyed typing entries on the reply parent tag, so an agent
working deep in a thread produced a threadHeadId that matched no ingress row
(and missed the open-thread composer filter). Typing and completion events
already carry a root tag for nested replies — prefer it, falling back to the
parent for direct replies where root === parent and no root tag is emitted.

Both the registration and post-message suppression paths flow through the
same helper, so suppression keys stay consistent. Exported for tests.

Co-authored-by: Taylor Ho <taylorkmho@gmail.com>
Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
…moothly

Closed thread ingress rows render inside .timeline-row-cv, which applies
content-visibility: auto. The browser may skip or throttle rendering work
for contained rows, so the badge's opacity pulse stuttered on the closed
timeline while the same activity animated smoothly in the open thread panel
(which has no containment) — exactly the reported repro signal.

Opt only rows with an active working badge out of containment via a
data-agent-working attribute on the row wrapper, mirroring the existing
:hover/:focus-within un-pause rule. Scope stays tight: at most the one or
two rows with live agent activity lose containment, and only while the
badge is showing.

Co-authored-by: Taylor Ho <taylorkmho@gmail.com>
Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
@tellaho

tellaho commented Jul 7, 2026

Copy link
Copy Markdown
Collaborator Author

Parking this draft direction in favor of a new header-level threads activity indicator exploration. Context preserved in the PR/thread; closing so it does not move forward accidentally.

@tellaho tellaho closed this Jul 7, 2026
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