Commit Graph

5 Commits

Author SHA1 Message Date
jared 8c0e2b4250 DP18: add getMemberName helper and dedup name-fallback sites
Add a pure `getMemberName(room, userId): string` helper in utils/room.ts
(= getMemberDisplayName ?? getMxIdLocalPart ?? userId) and replace the
inline `getMemberDisplayName(room, id) ?? getMxIdLocalPart(id) ?? id`
fallback across the codebase. No behavior change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 23:01:32 -04:00
jared 0bbdd7ce94 fix(notifications/threads): Wave-1 audit fixes (🔴 + web 🟠)
- T1 (🔴): markThreadAsRead no longer receipts the thread ROOT (a 2nd instance
  of the read-marker-corruption regression — opening a thread whose root is old
  re-lit the whole room). Extracted to a pure threadReceipt.ts + 5 regression
  tests.
- N1 (🔴): favicon/tab-title unread count now sums only leaf rooms (was double-
  counting every ancestor-space aggregate in roomToUnread).
- N2 (🔴): notifications/sounds dedupe on the event id, not the unread count —
  fixes "read a DM, next message never notifies again".
- T4 (🟠): the thread notification path no longer re-gates on the room count, so
  an explicit per-thread "All replies" override in a Mentions-only room fires.
- N3 (🟠): getUnreadInfos skips phantom {0,0} entries (muted-thread-only rooms no
  longer light the nav row / pollute unread filters).
- N4 (🟠): the Receipt handler recomputes unread instead of blanket-DELETE, so a
  threaded receipt can't wipe a room's valid main-timeline badge.
- T2 (🟠): thread "Jump to Latest" re-anchors the virtual window (was landing on
  a stale mid/old event).

Gates: tsc/eslint/prettier clean, build OK, 678 tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 20:10:32 -04:00
jared 664dcd4cd8 fix(audit): correctness wave — ghost sends, Escape coordination, panel exclusion
- ScheduledMessagesTray: cancel prunes local state ONLY on confirmed server
  cancel; failures keep the item + show an inline error (was: a failed cancel
  looked cancelled but still sent at the scheduled time).
- Escape semantics: the composer consumes Escape (preventDefault+stopPropagation)
  iff autocomplete is open or a reply draft is set; the thread panel and Room's
  markAsRead act only on unconsumed Escape, and markAsRead defers entirely while
  a thread panel is open (listener order made it fire before the panel closed).
- Room: thread panel / media gallery are mutually exclusive (most-recently-
  opened wins); on mobile at most one right panel renders (thread > gallery >
  members) instead of stacked fullscreen overlays.
- RemindMeDialog: busy-disabled presets (no more double-click duplicates),
  try/catch with inline error, close only on success.
- ThreadTimeline: "Jump to Latest" floating chip when scrolled up (RoomTimeline
  idiom).

From the 4-auditor deep-audit wave; reviewer-verified.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 00:18:51 -04:00
jared 440c1fe948 fix(threads): review-wave fixes — decryption re-render, receipt dedupe, chip perf
Two-reviewer audit of the thread stack; confirmed findings fixed:
- ThreadTimeline: wrap encrypted events in EncryptedContent so a live-arriving
  E2EE reply re-renders when its key decrypts (decryption emits neither
  RoomEvent.Timeline nor ThreadEvent.Update — previously stuck at "Unable to
  decrypt").
- ThreadPanel: mark-read deduped on the latest event id (RoomEvent.Timeline
  re-emits per backfilled event/edit/reaction; previously up to N receipt POSTs
  per panel open) + rejection handled with retry.
- RoomTimeline: ThreadSummary chips now mount only for events carrying thread
  data (each chip holds a room-level listener; one per rendered message would
  blow the SDK's 100-listener emitter cap) with a single room-level
  ThreadEvent.New tick for new-thread liveness.
- useThreadPendingEvents: keep a sent reply visible through the /send-response→
  /sync window (was flashing out of the pending strip before landing).
- ThreadTimeline: reseed the window on RoomEvent.TimelineReset (gappy sync left
  a detached timeline).

Documented-acceptable (reviewer-noted): thread typing shows as room typing (no
per-thread typing in the spec; Element matches), thread panel + members drawer
can be open together, scheduled-send is thread-unaware but unreachable there.

Gates: tsc clean, eslint 0 errors, build OK, 616/617 tests (1 IDB skip).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 21:58:42 -04:00
jared aa62df9c75 feat(threads): Thread Panel — full side drawer (P3-8)
Right-side thread drawer (MembersDrawer pattern; mobile fullscreen):
- ThreadPanel: header + close/Escape, ThreadTimeline, its own RoomInput
  (threadRootId prop; drafts/replies/uploads isolated per roomId::threadId;
  schedule + slash-commands off in threads v1) and threaded mark-as-read.
- ThreadTimeline: lean reimplementation over thread.liveTimeline — copied
  useTimelinePagination pattern (/relations back-pagination + decryption),
  virtualized, root event emphasized + "N replies" divider, reactions/edits/
  redactions, and a pending strip (chronological local echo never enters the
  thread timelineSet — rendered from LocalEchoUpdated instead).
- ThreadSummary chips on root messages (server-aggregated bundle or live
  Thread; unread badge via getThreadUnreadNotificationCount) keep threads
  discoverable now that replies leave the main timeline.
- Reply-in-Thread menu + thread indicators open the panel; deep links to
  thread events redirect into it.
- State: roomIdToActiveThreadIdAtomFamily + getThreadDraftKey (+18 tests).

Gates: tsc clean, eslint 0 errors, build OK, 616/617 tests (1 IDB skip).
Awaiting live QA; release note: threaded replies no longer render inline.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 21:45:20 -04:00