From 6ae68013af6949c7334bfde784456998b372e90d Mon Sep 17 00:00:00 2001 From: lebaudantoine Date: Tue, 6 Jan 2026 23:12:40 +0100 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8(frontend)=20add=20SR=20announcements?= =?UTF-8?q?=20for=20transcript=20and=20recording?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit announce transcript and record events to sr to provide clear feedback Signed-off-by: Cyril --- .../components/RecordingStateToast.tsx | 145 +++++++++++------- 1 file changed, 89 insertions(+), 56 deletions(-) diff --git a/src/frontend/src/features/recording/components/RecordingStateToast.tsx b/src/frontend/src/features/recording/components/RecordingStateToast.tsx index 7bded694..4699e0d5 100644 --- a/src/frontend/src/features/recording/components/RecordingStateToast.tsx +++ b/src/frontend/src/features/recording/components/RecordingStateToast.tsx @@ -1,6 +1,6 @@ import { css } from '@/styled-system/css' import { useTranslation } from 'react-i18next' -import { useMemo } from 'react' +import { useMemo, useRef, useState, useEffect } from 'react' import { Text } from '@/primitives' import { RecordingMode, @@ -21,6 +21,9 @@ export const RecordingStateToast = () => { const { openTranscript, openScreenRecording } = useSidePanel() + const [srMessage, setSrMessage] = useState('') + const lastKeyRef = useRef('') + const hasTranscriptAccess = useHasRecordingAccess( RecordingMode.Transcript, FeatureFlags.Transcript @@ -67,6 +70,23 @@ export const RecordingStateToast = () => { return `${metadata.recording_mode}.${status}` }, [metadata, isStarted, isStarting, isRecording]) + // Update screen reader message only when the key actually changes + // This prevents duplicate announcements caused by re-renders + useEffect(() => { + if (key && key !== lastKeyRef.current) { + lastKeyRef.current = key + const message = t(key) + setSrMessage(message) + + // Clear message after 3 seconds to prevent it from being announced again + const timer = setTimeout(() => { + setSrMessage('') + }, 3000) + + return () => clearTimeout(timer) + } + }, [key, t]) + if (!key) return null const hasScreenRecordingAccessAndActive = @@ -74,61 +94,74 @@ export const RecordingStateToast = () => { const hasTranscriptAccessAndActive = isTranscriptActive && hasTranscriptAccess return ( -
- + <> + {/* Screen reader only message to announce state changes once */} +
+ {srMessage} +
+ {/* Visual banner (without aria-live to avoid duplicate announcements) */} +
+ - {!hasScreenRecordingAccessAndActive && !hasTranscriptAccessAndActive && ( - - {t(key)} - - )} - {hasScreenRecordingAccessAndActive && ( - - {t(key)} - - )} - {hasTranscriptAccessAndActive && ( - - {t(key)} - - )} -
+ {!hasScreenRecordingAccessAndActive && + !hasTranscriptAccessAndActive && ( + + {t(key)} + + )} + {hasScreenRecordingAccessAndActive && ( + + {t(key)} + + )} + {hasTranscriptAccessAndActive && ( + + {t(key)} + + )} +
+ ) }