fix(threads): harden threads list after review
CI / Build & Quality Checks (push) Successful in 11m23s
CI / Trigger Desktop Build (push) Successful in 9s

Address findings from 3 review agents on the Threads list panel:

- Last-activity accuracy (SDK): sort key and the "last reply <time>"
  label now use thread.replyToEvent.getTs() (server bundle latest_event)
  instead of lastReply(), which returns the ROOT time until each thread's
  replies lazily paginate (or permanently on fetch error). Applied to the
  hook signature too.

- Live-refresh completeness (correctness): the useRoomThreads signature
  now includes thread.length and the root event's replacingEventId, so a
  mid-thread redaction (reply count) and a root-message edit (row snippet)
  refresh the row live instead of going stale.

- a11y: the row's aria-label was the button's whole accessible name,
  hiding the snippet/count/unread from screen readers. It now describes
  the thread ("Open thread by <name>, unread, N replies, last reply ..").

- Unread badge: replaced the bare green dot (Success = the mention color)
  with the app-wide UnreadBadge, using the Highlight count so mentions
  render red and ordinary unread renders secondary, matching room-nav.

- Hover/focus affordance: the clickable row moved its inline styles to a
  css class with token-based :hover / :active backgrounds.

- Participant pile now also includes the last replier from the bundle.

- Stabilized the panel's onClose/onOpenThread with useCallback so its
  Escape listener isn't re-subscribed every Room render. Added
  filter->sort pipeline + all/participating immutability tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-10 01:21:01 -04:00
co-authored by Claude Opus 4.8
parent d6d1f5a233
commit b6413d763d
6 changed files with 86 additions and 36 deletions
+13 -5
View File
@@ -70,6 +70,17 @@ export function Room() {
),
);
// Stable handlers for the threads-list panel so its document-level Escape
// listener isn't torn down and re-added on every Room re-render.
const closeThreadsList = useCallback(() => setThreadsListOpen(false), [setThreadsListOpen]);
const openThreadFromList = useCallback(
(threadId: string) => {
setActiveThreadId(threadId);
setThreadsListOpen(false);
},
[setActiveThreadId, setThreadsListOpen],
);
const callView = callEmbed?.roomId === room.roomId || room.isCallRoom() || callMembers.length > 0;
// The content panels (thread / media gallery / widgets) are mutually exclusive
@@ -190,11 +201,8 @@ export function Room() {
<ThreadsListPanel
key={room.roomId}
room={room}
onClose={() => setThreadsListOpen(false)}
onOpenThread={(threadId) => {
setActiveThreadId(threadId);
setThreadsListOpen(false);
}}
onClose={closeThreadsList}
onOpenThread={openThreadFromList}
/>
</>
)}