@@ -28,7 +28,7 @@ import { SplitPane } from "@/components/ui/split-pane";
2828import { WebviewPreview } from "./WebviewPreview" ;
2929import type { ClaudeStreamMessage } from "./AgentExecution" ;
3030import { useVirtualizer } from "@tanstack/react-virtual" ;
31- import { useTrackEvent , useComponentMetrics , useWorkflowTracking } from "@/hooks" ;
31+ import { useTrackEvent , useComponentMetrics , useWorkflowTracking , useScreenReaderAnnouncements } from "@/hooks" ;
3232import { SessionPersistenceService } from "@/services/sessionPersistence" ;
3333
3434interface ClaudeCodeSessionProps {
@@ -140,6 +140,15 @@ export const ClaudeCodeSession: React.FC<ClaudeCodeSessionProps> = ({
140140 // const aiTracking = useAIInteractionTracking('sonnet'); // Default model
141141 const workflowTracking = useWorkflowTracking ( 'claude_session' ) ;
142142
143+ // Screen reader announcements
144+ const {
145+ announceClaudeStarted,
146+ announceClaudeFinished,
147+ announceAssistantMessage,
148+ announceToolExecution,
149+ announceToolCompleted
150+ } = useScreenReaderAnnouncements ( ) ;
151+
143152 // Call onProjectPathChange when component mounts with initial path
144153 useEffect ( ( ) => {
145154 if ( onProjectPathChange && projectPath ) {
@@ -480,6 +489,9 @@ export const ClaudeCodeSession: React.FC<ClaudeCodeSessionProps> = ({
480489 setError ( null ) ;
481490 hasActiveSessionRef . current = true ;
482491
492+ // Announce that Claude is starting to process
493+ announceClaudeStarted ( ) ;
494+
483495 // For resuming sessions, ensure we have the session ID
484496 if ( effectiveSession && ! claudeSessionId ) {
485497 setClaudeSessionId ( effectiveSession . id ) ;
@@ -570,6 +582,60 @@ export const ClaudeCodeSession: React.FC<ClaudeCodeSessionProps> = ({
570582 }
571583 } ) ;
572584
585+ // Helper to find tool name by tool use ID from previous messages
586+ function findToolNameById ( toolUseId : string ) : string | null {
587+ // Search backwards through messages for the tool use
588+ for ( let i = messages . length - 1 ; i >= 0 ; i -- ) {
589+ const msg = messages [ i ] ;
590+ if ( msg . type === 'assistant' && msg . message ?. content ) {
591+ const toolUse = msg . message . content . find ( ( c : any ) =>
592+ c . type === 'tool_use' && c . id === toolUseId
593+ ) ;
594+ if ( toolUse ?. name ) {
595+ return toolUse . name ;
596+ }
597+ }
598+ }
599+ return null ;
600+ }
601+
602+ // Helper to announce incoming messages to screen readers
603+ function announceIncomingMessage ( message : ClaudeStreamMessage ) {
604+ if ( message . type === 'assistant' && message . message ?. content ) {
605+ // Announce tool execution
606+ const toolUses = message . message . content . filter ( ( c : any ) => c . type === 'tool_use' ) ;
607+ toolUses . forEach ( ( toolUse : any ) => {
608+ const toolName = toolUse . name || 'unknown tool' ;
609+ const description = toolUse . input ?. description ||
610+ toolUse . input ?. command ||
611+ toolUse . input ?. file_path ||
612+ toolUse . input ?. pattern ||
613+ toolUse . input ?. prompt ?. substring ( 0 , 50 ) ;
614+ announceToolExecution ( toolName , description ) ;
615+ } ) ;
616+
617+ // Announce text content
618+ const textContent = message . message . content
619+ . filter ( ( c : any ) => c . type === 'text' )
620+ . map ( ( c : any ) => typeof c . text === 'string' ? c . text : ( c . text ?. text || '' ) )
621+ . join ( ' ' )
622+ . trim ( ) ;
623+
624+ if ( textContent ) {
625+ announceAssistantMessage ( textContent ) ;
626+ }
627+ } else if ( message . type === 'system' ) {
628+ // Announce system messages if they have meaningful content
629+ if ( message . subtype === 'init' ) {
630+ // Don't announce init messages as they're just setup
631+ return ;
632+ } else if ( message . result || message . error ) {
633+ const content = message . result || message . error || 'System message received' ;
634+ announceAssistantMessage ( content ) ;
635+ }
636+ }
637+ }
638+
573639 // Helper to process any JSONL stream message string
574640 function handleStreamMessage ( payload : string ) {
575641 try {
@@ -581,6 +647,9 @@ export const ClaudeCodeSession: React.FC<ClaudeCodeSessionProps> = ({
581647
582648 const message = JSON . parse ( payload ) as ClaudeStreamMessage ;
583649
650+ // Announce incoming messages to screen readers
651+ announceIncomingMessage ( message ) ;
652+
584653 // Track enhanced tool execution
585654 if ( message . type === 'assistant' && message . message ?. content ) {
586655 const toolUses = message . message . content . filter ( ( c : any ) => c . type === 'tool_use' ) ;
@@ -609,6 +678,14 @@ export const ClaudeCodeSession: React.FC<ClaudeCodeSessionProps> = ({
609678 const toolResults = message . message . content . filter ( ( c : any ) => c . type === 'tool_result' ) ;
610679 toolResults . forEach ( ( result : any ) => {
611680 const isError = result . is_error || false ;
681+
682+ // Announce tool completion
683+ if ( result . tool_use_id ) {
684+ // Try to find the tool name from previous messages
685+ const toolName = findToolNameById ( result . tool_use_id ) || 'Tool' ;
686+ // announceToolCompleted(toolName, !isError); // Disabled to prevent interrupting other announcements
687+ }
688+
612689 // Note: We don't have execution time here, but we can track success/failure
613690 if ( isError ) {
614691 sessionMetrics . current . toolsFailed += 1 ;
@@ -660,6 +737,9 @@ export const ClaudeCodeSession: React.FC<ClaudeCodeSessionProps> = ({
660737 hasActiveSessionRef . current = false ;
661738 isListeningRef . current = false ; // Reset listening state
662739
740+ // Announce that Claude has finished
741+ announceClaudeFinished ( ) ;
742+
663743 // Track enhanced session stopped metrics when session completes
664744 if ( effectiveSession && claudeSessionId ) {
665745 const sessionStartTimeValue = messages . length > 0 ? messages [ 0 ] . timestamp || Date . now ( ) : Date . now ( ) ;
0 commit comments