[deep dive] Threads: full UI/UX audit + "mark all threads read" #165
Closed
opened 2026-09-17 23:06:35 -04:00 by jared
·
7 comments
No Branch/Tag Specified
lotus
fix/call-theme-before-ready
fix/dfn-worklet-file
test/lotus-feature-e2e
fix/own-presence-display
fix/sso-uia-desktop
seasonal/lunar
seasonal/newyear
seasonal/christmas
chore/build-hygiene
fix/status-save-speed
chore/typing-hygiene
feat/settings-search
feat/settings-pages
chore/security-deps
feat/settings-quick-wins
feat/data-export
feat/stale-sessions
feat/landscape-call
feat/expected-devices
feat/oauth-detect
chore/ec-0.28
chore/sdk-44
feat/key-upload-recovery
feat/keychain-session
feat/vimeo-events
feat/widget-always-on-screen
feat/call-summary-details
feat/widget-popout
fix/ios-long-press
chore/any-cleanup
chore/soundboard-mic-mix
fix/phone-call-controls
fix/a11y-axe-findings
fix/copy-and-reddit-context
feat/about-build-info
fix/schedule-24h
fix/connecting-strip
fix/ui-polish-batch
fix/outbox-multi-tab
fix/widget-share-hardening
fix/side-panel-width
fix/topic-markdown
fix/acl-ip-literals-default
feat/decorations-host
fix/call-disconnected-bar
fix/linux-update-verify
perf/lazy-settings
fix/dnd-busy-presence
fix/threads-stay-unread
fix/private-note-wording
fix/audio-output-frame-ids
fix/pip-minimise
fix/translate-unavailable-copy
fix/status-expiry-account-data
ci/reseed-bundle-budget
fix/close-no-tray
fix/soundboard-not-permitted
fix/denoise-state-notice
docs/features-catchup
fix/desktop-settings-section
fix/dev-denoise-theme
fix/pwa-manifest
fix/translate-feedback
fix/consent-retry-snooze
fix/strip-metadata-packs
fix/preview-deceptive-links
fix/embed-ogurl-provider
fix/settingtile-nesting
fix/shortcuts-dialog
fix/forward-encryption-warning
fix/compress-alpha-anim
fix/screenshare-double-toast
fix/sw-reply-checks
fix/pip-default-position
fix/inapp-toast-permission
fix/mute-timers-account-data
fix/policy-dedupe
fix/nudge-dismissed-strip
fix/kuma-plain-text
fix/rejoin-dm
fix/search-edit-dupes
fix/room-filter-empty
fix/emoji-board-escape
fix/call-failed-retry
fix/share-links-lotus
fix/poll-vote-local-echo
fix/scheduled-from-server
fix/delayed-restart-dedupe
fix/decline-references-notification
fix/stall-watchdog
fix/filtered-timeline-double-count
fix/call-listener-limit
fix/voice-room-no-room-ping
fix/reactions-collapse-loop
fix/group-call-permission
fix/dialog-enter-closes
fix/delivery-status-live
fix/room-upgrade-lotus-state
fix/quiet-hours-tick
fix/logout-wipe-leftovers
fix/inter-font-name
fix/retention-floor-grace
fix/firefox-persist-hang
fix/phantom-ring-after-reload
fix/local-room-names-rest
fix/missed-call-voice-rooms
fix/animated-bg-compositor
fix/link-safety-bypasses
fix/e2e-register-403
fix/local-room-names-everywhere
fix/collapsible-messages
fix/logout-volume-residue
fix/enter-newline-device-local
fix/dnd-restore-status
fix/presence-sticky
fix/location-confirm
fix/reminders-stick
fix/voice-full-distinct-users
fix/presence-pagehide-status
fix/gif-card-aspect
deps/npm-audit-fixes
security-nudge
linux-package-updates
audit-brace-expansion
server-status-banner
desktop-keychain-mirror
desktop-call-origin
clock-skew-lag
offline-outbox
desktop-lotus-links
poll-ui
calls-open-in-browser
screenshare-prompt
call-origin-split
update-packages
sw-fix
read-me-update
image-path-changes
dm-calls
fix-2469
renovate/element-hq-element-call-embedded-0.x
renovate/npm-i18next-http-backend-vulnerability
renovate/npm-vite-vulnerability
dev
docs-update
more-theme
fix-257
imporve-thread-reply
revert-2402-improve-menu-congestion
mxidColor-toggle
update-sw-main-msg
v4.11.1
v4.10.5
v4.10.4
v4.10.3
v4.10.2
v4.10.1
v4.10.0
v4.9.1
v4.9.0
v4.8.1
v4.8.0
v4.7.1
v4.7.0
v4.6.0
v4.5.1
v4.5.0
v4.4.0
v4.3.2
v4.3.0
v4.2.3
v4.2.2
v4.2.1
v4.2.0
v4.1.0
v4.0.3
v4.0.0
v3.2.0
v3.1.0
v3.0.0
v2.2.6
v2.2.5
v2.2.4
v2.2.3
v2.2.2
v2.2.1
v2.2.0
v2.1.3
v2.1.2
v2.1.1
v2.1.0
v2.0.4
v2.0.3
v2.0.2
v2.0.1
v2.0.0
v1.8.2
v1.8.1
v1.8.0
v1.7.0
v1.6.1
v1.6.0
v1.5.1
v1.5.0
v1.4.0
v1.3.2
v1.3.1
v1.3.0
v1.2.1
v1.2.0
v1.1.0
v1.0.0
Labels
Clear labels
a11y
area: appearance
area: auth-session
area: build-ci
area: calls
area: desktop
area: media
area: messaging
area: mobile
area: moderation
area: navigation
area: notifications
area: settings
area: threads
bug
dependencies
docs
duplicate
enhancement
help wanted
invalid
needs-human-review
performance
planning
priority: critical
priority: high
priority: low
priority: medium
qa
question
research
security
tech-debt
ux
wontfix
Accessibility: keyboard, screen reader, contrast, motion
Client area: appearance
Client area: auth-session
Client area: build-ci
Client area: calls
Client area: desktop
Client area: media
Client area: messaging
Client area: mobile
Client area: moderation
Client area: navigation
Client area: notifications
Client area: settings
Client area: threads
Something is not working
Third-party package versions and advisories
README / LOTUS_* docs wrong or missing
This issue or pull request already exists
New feature
Need some help
Something is wrong
Re-render storms, leaks, heavy work on hot paths
Data loss, security hole, or crash on a main path
Broken feature or serious usability problem
Minor issue or polish
Wrong behaviour in an edge case or notable degradation
Manual QA: shipped, needs a human in a real environment
More information is needed
XSS, unsafe URLs, data leaks, auth/session
Code health, dead code, fragile patterns
Usability or visual inconsistency
This won't be fixed
Milestone
No items
No Milestone
Features 2026-Q4
Projects
Clear projects
No projects
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: LotusGuild/cinny#165
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Jared: "we need an extreme deep dive into our implementation of threads as a whole, especially UI/UX."
Audit the whole thread surface: thread panel open/close and navigation, thread composer parity with the main composer (toolbar, drafts, paste, uploads, mentions), "N replies" chips + unread badges, per-thread read tracking, the Threads list panel (sorting, filters, empty states), notifications overrides, jumping between root and thread, replies-in-thread vs. replies-to-thread-root confusion, mobile layout, keyboard/a11y, and edge cases (edited/redacted roots, threads in encrypted rooms, threads across the search/bookmarks/forward features). Plus the small feature: Mark all threads read in the Threads list panel.
Deliverable: findings ranked by severity as a checklist here, then fixes in reviewed batches (a11y and privacy items first — see #140/#141).
From #163: media posted inside threads never appears in the Media Gallery — thread replies are partitioned out of the room timeline by the SDK (
room.partitionThreadedEvents), so neither the old live-timeline scan nor the new detached media timeline sees them. Include in this audit: decide whether the gallery should walk thread timelines (or the server-side filter should include thread relations) and whether the gallery tile should say which thread an item came from.From the #141 verification: each thread read receipt is POSTed twice for the same event (observed 4
/receipt/requests for 2 events, identical bodies).ThreadPanel.tsx:150-176runsmarkReadon bothThreadEvent.NewReplyandRoomEvent.Timeline;lastReadEventIdRefshould dedupe, so either both fire before the ref updates (it is set synchronously — unlikely) or twoThreadPanelinstances are mounted. Low priority; fold into the audit.Threads audit — exercised end to end on the local dev homeserver (Playwright, desktop 1400px + Pixel 7)
Fixed on
lotus(all pushed):58a716c7— opening a thread on a pristine composer crashed the client (shared SlateinitialValue; see #184). This alone explains most "threads feel broken" reports.f2f49842— every reply in the panel showed aThread ↩ <root author> <root text…>quote because the spec's fallback reply relation (is_falling_back→ root) was rendered like a real reply. Root is already pinned at the top, so that was noise on every row. Genuine reply-to-a-reply quotes stay. Before/after: the first screenshot on this issue vs the one below.f2f49842— no way to open the Threads list (or Widgets) on a phone: the header buttons were Desktop-only and the ⋮ menu had no entry. Both added next to Members / Media Gallery. (Closes #211.)Verified working: chips "N replies · time" on roots, replies never inline in the main timeline; panel = root + divider + reply timeline + own composer, opens scrolled to the latest reply; own reply updates the chip live; reply-to-a-reply produces a proper
m.thread+ non-fallbackm.in_reply_to; Escape closes the panel; edit inside a thread keeps the thread relation and re-renders; reactions inside a thread work; deleting a reply drops the chip count; redacting the root while the panel is open leaves a stable "deleted" panel; Threads list with All/Unread/Participating + Recent/Oldest, participant avatars, "N replies · just now"; mobile panel is full-screen with Back.Filed as follow-ups (need a decision or are bigger):
Threads audit (2026-09-23) — driven headlessly, desktop 1300×850 + Pixel 7
Shipped from this audit
fdec3ed7. A chip in the Threads panel header, shown only when something is unread; one threaded receipt per unread thread (never the root, per the P6 rule). Verified: 2 unread threads → click → 0 unread rows, chip gone.<aside aria-label="Thread">(6aa77552, from the a11y gate work) so screen readers can jump to it; the timeline inside isrole="log"named "Thread timeline".Verified working
threadRootId).cinny_threads_filter_v1/_sort_v1); rows carry unread + highlight badges and a participants stack; rows announce "Open thread by X, unread, N replies".isRoot ? null : thread.id).Findings, ranked
markAsRead's per-thread half firing on every room visit. It also makes "Mark all read" mostly invisible unless you scroll up first. Highest-value fix, but it's the product call in #217 — my recommendation there is option (c): a room visit clears threads you have not participated in (they only lit the room dot), and leaves participating threads unread until the thread itself is opened. That matches Slack/Discord and keeps the room dot honest.ThreadPanelpassescompactLayout, so on a 360 px-wide desktop panel every secondary control (attach, formatting, sticker-vs-emoji split, location, poll, voice, schedule) hides behind "More actions" — the main composer shows them inline. Measured: main composer 12 inline controls, thread composer 3. At 360 px the collapse is defensible, but it should follow the panel's real width (the panel is already full-width under 750 px), not be hard-coded. Medium.More from the 2026-09-23 audit
64af736c). For example, "Thread Lab · started by alice, Fri 09:18 PM", using the user's clock and date settings. It fits the 360 px panel without clipping. The e2e aria snapshot was updated to match (3e69c715).a932b199). Each row has a small button that takes the room timeline to the thread's first message. It sits beside the row button rather than inside it, so there are no nested buttons. On a phone it also closes the list, and the button is the larger touch size (34 px). Verified on desktop (the list stays open) and on a Pixel 7.compactLayoutis correct at every width, and I reverted the change.2b1f4ee5). The SDK gives a thread root its own id asthreadRootId, so the!isThreadedMessageguard hid the thread action on the one message that actually has a thread. Roots now show View thread in the hover bar and View Thread (N replies) in the ⋮ menu, with the thread icon; other messages keep "Reply in Thread". Verified on a 14-reply root: both open the panel. A keyboard shortcut is still open.Finishing the audit (2026-09-26)
1 / #217: viewing a room cleared threads you follow. Fixed in
a8db61f7; details in #217.3. Unread state on the chip. This already shipped with the a11y work in
206a3e93: a green dot on the chip, and "unread replies" in its accessible name. With #217 fixed the dot now survives while the room is open (screenshot in #217), and the new e2e asserts it.6. Duplicate receipts. One receipt per reply now, measured on the wire for both followed and unfollowed threads (see #217).
Media posted inside threads was missing from the Media Gallery (noted 2026-09-18).
ba48e959: the SDK moves thread replies out of every room timeline set, including the gallery's, into the room's Thread objects. The gallery now merges in media from loaded threads, newest first, and refreshes when a thread reply arrives (after decryption in E2EE rooms).Related (#164,
f5d3c43b): clicking an image inside the thread panel opened the viewer as1 / 1. It now steps through the thread's own media.7. A dedicated keyboard shortcut: not adding one. The app's global shortcuts are deliberately just
?and Escape, and the thread panel is already fully keyboard-reachable:2b1f4ee5), then Enter to open itIf you want a global hotkey anyway, say which key and I'll add it to the
?dialog.That covers every finding in this audit, so I'm closing it.