Skip to content

Commit d7cb1e3

Browse files
committed
Button Focus States, Event Handler Error Handling
1. Button Focus States - Added focus-visible:outline styles to all buttons for proper keyboard navigation accessibility 2. Event Handler Error Handling - Added try-catch blocks around all vscode.postMessage calls to prevent UI crashes from communication errors
1 parent da9a4bb commit d7cb1e3

File tree

2 files changed

+45
-16
lines changed

2 files changed

+45
-16
lines changed

webview-ui/src/components/file-changes/FilesChangedOverview.tsx

Lines changed: 35 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -100,28 +100,48 @@ const FilesChangedOverview: React.FC = () => {
100100

101101
// Action handlers
102102
const handleViewDiff = React.useCallback((uri: string) => {
103-
vscode.postMessage({ type: "viewDiff", uri })
103+
try {
104+
vscode.postMessage({ type: "viewDiff", uri })
105+
} catch (error) {
106+
console.error("Failed to view diff for file:", uri, error)
107+
}
104108
}, [])
105109

106110
const handleAcceptFile = React.useCallback((uri: string) => {
107-
vscode.postMessage({ type: "acceptFileChange", uri })
108-
// Backend will send updated filesChanged message with filtered results
111+
try {
112+
vscode.postMessage({ type: "acceptFileChange", uri })
113+
// Backend will send updated filesChanged message with filtered results
114+
} catch (error) {
115+
console.error("Failed to accept file change:", uri, error)
116+
}
109117
}, [])
110118

111119
const handleRejectFile = React.useCallback((uri: string) => {
112-
vscode.postMessage({ type: "rejectFileChange", uri })
113-
// Backend will send updated filesChanged message with filtered results
120+
try {
121+
vscode.postMessage({ type: "rejectFileChange", uri })
122+
// Backend will send updated filesChanged message with filtered results
123+
} catch (error) {
124+
console.error("Failed to reject file change:", uri, error)
125+
}
114126
}, [])
115127

116128
const handleAcceptAll = React.useCallback(() => {
117-
vscode.postMessage({ type: "acceptAllFileChanges" })
118-
// Backend will send updated filesChanged message with filtered results
129+
try {
130+
vscode.postMessage({ type: "acceptAllFileChanges" })
131+
// Backend will send updated filesChanged message with filtered results
132+
} catch (error) {
133+
console.error("Failed to accept all file changes:", error)
134+
}
119135
}, [])
120136

121137
const handleRejectAll = React.useCallback(() => {
122-
const visibleUris = files.map((file) => file.uri)
123-
vscode.postMessage({ type: "rejectAllFileChanges", uris: visibleUris })
124-
// Backend will send updated filesChanged message with filtered results
138+
try {
139+
const visibleUris = files.map((file) => file.uri)
140+
vscode.postMessage({ type: "rejectAllFileChanges", uris: visibleUris })
141+
// Backend will send updated filesChanged message with filtered results
142+
} catch (error) {
143+
console.error("Failed to reject all file changes:", error)
144+
}
125145
}, [files])
126146

127147
/**
@@ -255,7 +275,7 @@ const FilesChangedOverview: React.FC = () => {
255275
disabled={isProcessing}
256276
tabIndex={0}
257277
data-testid="reject-all-button"
258-
className="bg-[var(--vscode-button-secondaryBackground)] text-[var(--vscode-button-secondaryForeground)] border-none rounded px-2 py-1 text-xs disabled:opacity-60 disabled:cursor-not-allowed cursor-pointer"
278+
className="bg-[var(--vscode-button-secondaryBackground)] text-[var(--vscode-button-secondaryForeground)] border-none rounded px-2 py-1 text-xs disabled:opacity-60 disabled:cursor-not-allowed cursor-pointer focus-visible:outline focus-visible:outline-2 focus-visible:outline-[var(--vscode-focusBorder)]"
259279
title={t("file-changes:actions.reject_all")}
260280
type="button"
261281
aria-disabled={isProcessing}
@@ -267,7 +287,7 @@ const FilesChangedOverview: React.FC = () => {
267287
disabled={isProcessing}
268288
tabIndex={0}
269289
data-testid="accept-all-button"
270-
className="bg-[var(--vscode-button-background)] text-[var(--vscode-button-foreground)] border-none rounded px-2 py-1 text-xs disabled:opacity-60 disabled:cursor-not-allowed cursor-pointer"
290+
className="bg-[var(--vscode-button-background)] text-[var(--vscode-button-foreground)] border-none rounded px-2 py-1 text-xs disabled:opacity-60 disabled:cursor-not-allowed cursor-pointer focus-visible:outline focus-visible:outline-2 focus-visible:outline-[var(--vscode-focusBorder)]"
271291
title={t("file-changes:actions.accept_all")}
272292
type="button"
273293
aria-disabled={isProcessing}
@@ -379,7 +399,7 @@ const FileItem: React.FC<FileItemProps> = React.memo(
379399
disabled={isProcessing}
380400
title={t("file-changes:actions.view_diff")}
381401
data-testid={`diff-${file.uri}`}
382-
className="bg-[var(--vscode-button-secondaryBackground)] text-[var(--vscode-button-secondaryForeground)] border border-[var(--vscode-button-border)] rounded px-1.5 py-0.5 text-[11px] min-w-[35px] disabled:opacity-60 disabled:cursor-not-allowed cursor-pointer"
402+
className="bg-[var(--vscode-button-secondaryBackground)] text-[var(--vscode-button-secondaryForeground)] border border-[var(--vscode-button-border)] rounded px-1.5 py-0.5 text-[11px] min-w-[35px] disabled:opacity-60 disabled:cursor-not-allowed cursor-pointer focus-visible:outline focus-visible:outline-2 focus-visible:outline-[var(--vscode-focusBorder)]"
383403
type="button"
384404
aria-disabled={isProcessing}>
385405
Diff
@@ -390,7 +410,7 @@ const FileItem: React.FC<FileItemProps> = React.memo(
390410
title={t("file-changes:actions.reject_file")}
391411
aria-label={t("file-changes:actions.reject_file")}
392412
data-testid={`reject-${file.uri}`}
393-
className="bg-[var(--vscode-button-secondaryBackground)] text-[var(--vscode-button-secondaryForeground)] border border-[var(--vscode-button-border)] rounded px-1.5 py-0.5 text-[11px] min-w-[20px] disabled:opacity-60 disabled:cursor-not-allowed cursor-pointer"
413+
className="bg-[var(--vscode-button-secondaryBackground)] text-[var(--vscode-button-secondaryForeground)] border border-[var(--vscode-button-border)] rounded px-1.5 py-0.5 text-[11px] min-w-[20px] disabled:opacity-60 disabled:cursor-not-allowed cursor-pointer focus-visible:outline focus-visible:outline-2 focus-visible:outline-[var(--vscode-focusBorder)]"
394414
type="button"
395415
aria-disabled={isProcessing}>
396416
@@ -401,7 +421,7 @@ const FileItem: React.FC<FileItemProps> = React.memo(
401421
title={t("file-changes:actions.accept_file")}
402422
aria-label={t("file-changes:actions.accept_file")}
403423
data-testid={`accept-${file.uri}`}
404-
className="bg-[var(--vscode-button-background)] text-[var(--vscode-button-foreground)] border border-[var(--vscode-button-border)] rounded px-1.5 py-0.5 text-[11px] min-w-[20px] disabled:opacity-60 disabled:cursor-not-allowed cursor-pointer"
424+
className="bg-[var(--vscode-button-background)] text-[var(--vscode-button-foreground)] border border-[var(--vscode-button-border)] rounded px-1.5 py-0.5 text-[11px] min-w-[20px] disabled:opacity-60 disabled:cursor-not-allowed cursor-pointer focus-visible:outline focus-visible:outline-2 focus-visible:outline-[var(--vscode-focusBorder)]"
405425
type="button"
406426
aria-disabled={isProcessing}>
407427

webview-ui/src/hooks/useDebouncedAction.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useCallback, useRef, useState } from "react"
1+
import { useCallback, useEffect, useRef, useState } from "react"
22

33
export function useDebouncedAction(delay = 300) {
44
const [isProcessing, setIsProcessing] = useState(false)
@@ -26,6 +26,15 @@ export function useDebouncedAction(delay = 300) {
2626
[isProcessing, delay],
2727
)
2828

29+
// Cleanup effect to prevent memory leaks
30+
useEffect(() => {
31+
return () => {
32+
if (timeoutRef.current) {
33+
clearTimeout(timeoutRef.current)
34+
}
35+
}
36+
}, [])
37+
2938
return { isProcessing, handleWithDebounce }
3039
}
3140

0 commit comments

Comments
 (0)