fix(a11y): thread chip says it opens the thread, and whether it's unread/muted (#179)
CI / Build & Quality Checks (push) Successful in 2m15s
CI / Docker image build & smoke test (push) Skipped
CI / Secret scan (gitleaks) (push) Successful in 14s
CI / Trigger Desktop Build (push) Canceled after 0s
CI / Playwright smoke (e2e) (push) Canceled after 0s

The "N replies · <time>" chip under a thread root was named only by its
visible text, so a screen reader never said it opens the thread, and the
unread dot / muted bell were visual-only. Its accessible name is now the
visible text first (WCAG 2.5.3) plus "view thread", "unread replies" and
"muted" as applicable, e.g. "1 reply · 12:16 PM, view thread".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
This commit is contained in:
Lotus CI
2026-09-24 12:18:35 -04:00
co-authored by Claude Opus 5.5
parent d5aa18e3ab
commit 206a3e933a
+14 -4
View File
@@ -19,6 +19,18 @@ export function ThreadSummary({ rootEvent, room, onOpen }: ThreadSummaryProps) {
const { count, latestTs } = summary;
const latestStr = latestTs !== undefined ? format(latestTs) : undefined;
const visibleText = `${count === 1 ? '1 reply' : `${count} replies`}${latestStr ? ` · ${latestStr}` : ''}`;
// #179: the chip was named only by its visible text, so a screen reader never
// heard that it opens the thread, nor about the unread dot / muted bell. Keep
// the visible text first (WCAG 2.5.3, label in name) and add the rest.
const ariaLabel = [
visibleText,
'view thread',
unread > 0 ? 'unread replies' : undefined,
mode === ThreadNotificationMode.Mute ? 'muted' : undefined,
]
.filter(Boolean)
.join(', ');
return (
<Box style={{ marginTop: config.space.S200 }}>
@@ -26,6 +38,7 @@ export function ThreadSummary({ rootEvent, room, onOpen }: ThreadSummaryProps) {
variant="SurfaceVariant"
radii="300"
className={MobileTouchTarget}
aria-label={ariaLabel}
before={<Icon size="50" src={Icons.Thread} />}
after={
unread > 0 ? <Badge variant="Success" fill="Solid" radii="Pill" size="200" /> : undefined
@@ -35,10 +48,7 @@ export function ThreadSummary({ rootEvent, room, onOpen }: ThreadSummaryProps) {
if (threadId) onOpen(threadId);
}}
>
<Text size="T200">
{count === 1 ? '1 reply' : `${count} replies`}
{latestStr ? ` · ${latestStr}` : ''}
</Text>
<Text size="T200">{visibleText}</Text>
{mode === ThreadNotificationMode.Mute && <Icon size="50" src={Icons.BellMute} />}
</Chip>
</Box>