@@ -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 {
@@ -139,6 +139,15 @@ export const ClaudeCodeSession: React.FC<ClaudeCodeSessionProps> = ({
139139 // const aiTracking = useAIInteractionTracking('sonnet'); // Default model
140140 const workflowTracking = useWorkflowTracking ( 'claude_session' ) ;
141141
142+ // Screen reader announcements
143+ const {
144+ announceClaudeStarted,
145+ announceClaudeFinished,
146+ announceAssistantMessage,
147+ announceToolExecution,
148+ announceToolCompleted
149+ } = useScreenReaderAnnouncements ( ) ;
150+
142151 // Call onProjectPathChange when component mounts with initial path
143152 useEffect ( ( ) => {
144153 if ( onProjectPathChange && projectPath ) {
@@ -454,6 +463,9 @@ export const ClaudeCodeSession: React.FC<ClaudeCodeSessionProps> = ({
454463 setError ( null ) ;
455464 hasActiveSessionRef . current = true ;
456465
466+ // Announce that Claude is starting to process
467+ announceClaudeStarted ( ) ;
468+
457469 // For resuming sessions, ensure we have the session ID
458470 if ( effectiveSession && ! claudeSessionId ) {
459471 setClaudeSessionId ( effectiveSession . id ) ;
@@ -544,6 +556,60 @@ export const ClaudeCodeSession: React.FC<ClaudeCodeSessionProps> = ({
544556 }
545557 } ) ;
546558
559+ // Helper to find tool name by tool use ID from previous messages
560+ function findToolNameById ( toolUseId : string ) : string | null {
561+ // Search backwards through messages for the tool use
562+ for ( let i = messages . length - 1 ; i >= 0 ; i -- ) {
563+ const msg = messages [ i ] ;
564+ if ( msg . type === 'assistant' && msg . message ?. content ) {
565+ const toolUse = msg . message . content . find ( ( c : any ) =>
566+ c . type === 'tool_use' && c . id === toolUseId
567+ ) ;
568+ if ( toolUse ?. name ) {
569+ return toolUse . name ;
570+ }
571+ }
572+ }
573+ return null ;
574+ }
575+
576+ // Helper to announce incoming messages to screen readers
577+ function announceIncomingMessage ( message : ClaudeStreamMessage ) {
578+ if ( message . type === 'assistant' && message . message ?. content ) {
579+ // Announce tool execution
580+ const toolUses = message . message . content . filter ( ( c : any ) => c . type === 'tool_use' ) ;
581+ toolUses . forEach ( ( toolUse : any ) => {
582+ const toolName = toolUse . name || 'unknown tool' ;
583+ const description = toolUse . input ?. description ||
584+ toolUse . input ?. command ||
585+ toolUse . input ?. file_path ||
586+ toolUse . input ?. pattern ||
587+ toolUse . input ?. prompt ?. substring ( 0 , 50 ) ;
588+ announceToolExecution ( toolName , description ) ;
589+ } ) ;
590+
591+ // Announce text content
592+ const textContent = message . message . content
593+ . filter ( ( c : any ) => c . type === 'text' )
594+ . map ( ( c : any ) => typeof c . text === 'string' ? c . text : ( c . text ?. text || '' ) )
595+ . join ( ' ' )
596+ . trim ( ) ;
597+
598+ if ( textContent ) {
599+ announceAssistantMessage ( textContent ) ;
600+ }
601+ } else if ( message . type === 'system' ) {
602+ // Announce system messages if they have meaningful content
603+ if ( message . subtype === 'init' ) {
604+ // Don't announce init messages as they're just setup
605+ return ;
606+ } else if ( message . result || message . error ) {
607+ const content = message . result || message . error || 'System message received' ;
608+ announceAssistantMessage ( content ) ;
609+ }
610+ }
611+ }
612+
547613 // Helper to process any JSONL stream message string
548614 function handleStreamMessage ( payload : string ) {
549615 try {
@@ -555,6 +621,9 @@ export const ClaudeCodeSession: React.FC<ClaudeCodeSessionProps> = ({
555621
556622 const message = JSON . parse ( payload ) as ClaudeStreamMessage ;
557623
624+ // Announce incoming messages to screen readers
625+ announceIncomingMessage ( message ) ;
626+
558627 // Track enhanced tool execution
559628 if ( message . type === 'assistant' && message . message ?. content ) {
560629 const toolUses = message . message . content . filter ( ( c : any ) => c . type === 'tool_use' ) ;
@@ -583,6 +652,14 @@ export const ClaudeCodeSession: React.FC<ClaudeCodeSessionProps> = ({
583652 const toolResults = message . message . content . filter ( ( c : any ) => c . type === 'tool_result' ) ;
584653 toolResults . forEach ( ( result : any ) => {
585654 const isError = result . is_error || false ;
655+
656+ // Announce tool completion
657+ if ( result . tool_use_id ) {
658+ // Try to find the tool name from previous messages
659+ const toolName = findToolNameById ( result . tool_use_id ) || 'Tool' ;
660+ // announceToolCompleted(toolName, !isError); // Disabled to prevent interrupting other announcements
661+ }
662+
586663 // Note: We don't have execution time here, but we can track success/failure
587664 if ( isError ) {
588665 sessionMetrics . current . toolsFailed += 1 ;
@@ -634,6 +711,9 @@ export const ClaudeCodeSession: React.FC<ClaudeCodeSessionProps> = ({
634711 hasActiveSessionRef . current = false ;
635712 isListeningRef . current = false ; // Reset listening state
636713
714+ // Announce that Claude has finished
715+ announceClaudeFinished ( ) ;
716+
637717 // Track enhanced session stopped metrics when session completes
638718 if ( effectiveSession && claudeSessionId ) {
639719 const sessionStartTimeValue = messages . length > 0 ? messages [ 0 ] . timestamp || Date . now ( ) : Date . now ( ) ;
0 commit comments