From cd6b0aef7e651e43b44c5448ceceb9534d76c34e Mon Sep 17 00:00:00 2001 From: brennanb2025 Date: Tue, 9 Jun 2026 15:43:21 -0700 Subject: [PATCH] Fix add project action selection accessibility Co-authored-by: Orca --- .../sidebar/AddRepoStartSteps.test.tsx | 96 ++++++++++++++++--- .../components/sidebar/AddRepoStartSteps.tsx | 34 +++++-- 2 files changed, 109 insertions(+), 21 deletions(-) diff --git a/src/renderer/src/components/sidebar/AddRepoStartSteps.test.tsx b/src/renderer/src/components/sidebar/AddRepoStartSteps.test.tsx index 9eadd3a5dad..ace7b22bc85 100644 --- a/src/renderer/src/components/sidebar/AddRepoStartSteps.test.tsx +++ b/src/renderer/src/components/sidebar/AddRepoStartSteps.test.tsx @@ -51,7 +51,17 @@ function renderServerPathStartStep(runtimeEnvironmentId: string | null): string ) } -async function renderLocalStartStepDom(isSshLikely: boolean): Promise<{ +type LocalStartStepDomOptions = { + isAdding?: boolean + addProjectBusyLabel?: string | null + nestedScanInProgress?: boolean + nestedScanId?: string | null +} + +async function renderLocalStartStepDom( + isSshLikely: boolean, + options: LocalStartStepDomOptions = {} +): Promise<{ container: HTMLDivElement root: Root }> { @@ -61,19 +71,21 @@ async function renderLocalStartStepDom(isSshLikely: boolean): Promise<{ await act(async () => { root.render( - + + + ) }) @@ -210,6 +222,62 @@ describe('AddRepoLocalStartStep', () => { }) }) + it('clears the ⏎ selection when focus leaves the action list', async () => { + const { container, root } = await renderLocalStartStepDom(false) + const outsideButton = document.createElement('button') + document.body.appendChild(outsideButton) + + await act(async () => { + outsideButton.focus() + }) + + expect(document.activeElement).toBe(outsideButton) + expect(findButton(container, 'Browse folder').textContent).not.toContain('⏎') + expect(findButton(container, 'Clone from URL').textContent).not.toContain('⏎') + + await act(async () => { + root.unmount() + }) + }) + + it('does not show an ⏎ selection while add actions are busy', async () => { + const { container, root } = await renderLocalStartStepDom(false, { + isAdding: true, + addProjectBusyLabel: 'Scanning repositories', + nestedScanInProgress: true, + nestedScanId: 'scan-1' + }) + const stopScanButton = container.querySelector( + 'button[aria-label="Stop scan"]' + ) + + await act(async () => { + stopScanButton?.focus() + }) + + expect(document.activeElement).toBe(stopScanButton) + expect(findButton(container, 'Browse folder').textContent).not.toContain('⏎') + expect(findButton(container, 'Clone from URL').textContent).not.toContain('⏎') + + await act(async () => { + root.unmount() + }) + }) + + it('hides the visual ⏎ chip from assistive technology', async () => { + const { container, root } = await renderLocalStartStepDom(false) + const browseButton = findButton(container, 'Browse folder') + const enterChip = Array.from(browseButton.querySelectorAll('[aria-hidden="true"]')).find( + (entry) => entry.textContent?.includes('⏎') + ) + + expect(enterChip).toBeTruthy() + + await act(async () => { + root.unmount() + }) + }) + it('roves selection down the action list with the ArrowDown key', async () => { const { container, root } = await renderLocalStartStepDom(false) diff --git a/src/renderer/src/components/sidebar/AddRepoStartSteps.tsx b/src/renderer/src/components/sidebar/AddRepoStartSteps.tsx index ea41778251e..8fc9d45953d 100644 --- a/src/renderer/src/components/sidebar/AddRepoStartSteps.tsx +++ b/src/renderer/src/components/sidebar/AddRepoStartSteps.tsx @@ -99,9 +99,13 @@ export function AddRepoLocalStartStep({ // The white fill + ⏎ chip is a roving selection indicator, not a fixed "primary" badge: // it follows keyboard focus so Enter always activates the highlighted action. Browse is // autofocused on open, so it starts selected; Tab and ↑/↓ move the highlight. - const [selectedKind, setSelectedKind] = useState(primaryAction.kind) + const [selectedKind, setSelectedKind] = useState(primaryAction.kind) useEffect(() => { + if (isAdding) { + setSelectedKind(null) + return + } if (!isAdding) { browseActionRef.current?.focus() } @@ -125,6 +129,16 @@ export function AddRepoLocalStartStep({ buttons[nextIndex]?.focus() } + const handleActionsBlur = (event: React.FocusEvent): void => { + if (!(event.relatedTarget instanceof HTMLButtonElement)) { + setSelectedKind(null) + return + } + if (!event.relatedTarget.matches('button[data-add-repo-action]')) { + setSelectedKind(null) + } + } + return ( <> @@ -141,7 +155,12 @@ export function AddRepoLocalStartStep({ ) : null} -
+
( - + ) const AddRepoPrimaryStartAction = ({