From 1f17bfe60899179b718d62ba940ecacf4ac2020e Mon Sep 17 00:00:00 2001 From: Trevor Walker Date: Thu, 24 Sep 2026 05:14:48 -0600 Subject: [PATCH] fix(web): expose selected menu choices to assistive tech --- apps/web/src/components/DiffPanel.tsx | 108 +++++++++--------- .../src/components/files/FileBreadcrumbs.tsx | 77 ++++++++----- .../pullRequest/PullRequestCodeTab.tsx | 50 ++++---- 3 files changed, 128 insertions(+), 107 deletions(-) diff --git a/apps/web/src/components/DiffPanel.tsx b/apps/web/src/components/DiffPanel.tsx index d954a7ff69..547856a426 100644 --- a/apps/web/src/components/DiffPanel.tsx +++ b/apps/web/src/components/DiffPanel.tsx @@ -72,7 +72,8 @@ import { import { DropdownMenu, DropdownMenuContent, - DropdownMenuItem, + DropdownMenuRadioGroup, + DropdownMenuRadioItem, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, @@ -644,6 +645,26 @@ export default function DiffPanel({ if (!routeThreadRef) return; useDiffPanelStore.getState().selectBranchBaseRef(routeThreadRef, baseRef); }; + // The scope menu has two radio groups: the top-level one treats the latest + // turn as "latest", while the turn sub-menu keys every turn by id so the + // latest turn is also marked there. + const selectedTurnValue = selectedTurn ? `turn:${selectedTurn.turnId}` : ""; + const selectedScopeValue = + selectedTurnId === null + ? selectedGitScope + : selectedTurn?.turnId === latestTurn?.turnId + ? "latest" + : selectedTurnValue; + const selectScopeValue = (value: string) => { + if (value === "unstaged" || value === "branch") { + selectGitScope(value); + } else if (value === "latest") { + if (latestTurn) selectTurn(latestTurn.turnId); + } else { + const turn = orderedTurnDiffSummaries.find((summary) => `turn:${summary.turnId}` === value); + if (turn) selectTurn(turn.turnId); + } + }; const headerRow = ( <> @@ -657,61 +678,42 @@ export default function DiffPanel({ - selectGitScope("unstaged")} - > - Working tree - - selectGitScope("branch")} - > - Branch changes - - { - if (latestTurn) selectTurn(latestTurn.turnId); - }} - > - Latest turn - + + + Working tree + + + Branch changes + + + Latest turn + + Turn - {orderedTurnDiffSummaries.map((summary) => { - const turnCount = - summary.checkpointTurnCount ?? - inferredCheckpointTurnCountByTurnId[summary.turnId] ?? - "?"; - return ( - selectTurn(summary.turnId)} - > - Turn {turnCount} - - {formatShortTimestamp(summary.completedAt, settings.timestampFormat)} - - - ); - })} + + {orderedTurnDiffSummaries.map((summary) => { + const turnCount = + summary.checkpointTurnCount ?? + inferredCheckpointTurnCountByTurnId[summary.turnId] ?? + "?"; + return ( + + + Turn {turnCount} + + {formatShortTimestamp(summary.completedAt, settings.timestampFormat)} + + + + ); + })} + diff --git a/apps/web/src/components/files/FileBreadcrumbs.tsx b/apps/web/src/components/files/FileBreadcrumbs.tsx index 4896c9ce2d..6f69aba45e 100644 --- a/apps/web/src/components/files/FileBreadcrumbs.tsx +++ b/apps/web/src/components/files/FileBreadcrumbs.tsx @@ -10,6 +10,8 @@ import { MenuGroup, MenuItem, MenuPopup, + MenuRadioGroup, + MenuRadioItem, MenuSeparator, MenuTrigger, } from "~/components/ui/menu"; @@ -143,36 +145,51 @@ function BreadcrumbMenuContent(props: { : "This folder is empty."} ) : ( - children.map((entry) => { - const isCurrentFile = entry.kind === "file" && entry.path === props.currentFilePath; - return ( - { - if (entry.kind === "directory") { - props.onDirectoryChange(entry.path); - return; - } - props.onOpenChange(false); - props.onOpenFile(entry.path); - }} - > - - - }> - {entry.label} - - - {entry.path} - - - {entry.kind === "directory" ? : null} - - ); - }) + // Files form a radio group keyed by path so the open file is marked as checked; + // directories only navigate the menu, so they stay plain items. + { + props.onOpenChange(false); + props.onOpenFile(path); + }} + > + {children.map((entry) => { + const isCurrentFile = entry.kind === "file" && entry.path === props.currentFilePath; + const row = ( + <> + + + }> + {entry.label} + + + {entry.path} + + + + ); + return entry.kind === "directory" ? ( + props.onDirectoryChange(entry.path)} + > + {row} + + + ) : ( + + {row} + + ); + })} + )} {entriesQuery.error && entriesQuery.data !== null ? ( diff --git a/apps/web/src/components/pullRequest/PullRequestCodeTab.tsx b/apps/web/src/components/pullRequest/PullRequestCodeTab.tsx index af1d40cd00..fe8052afa8 100644 --- a/apps/web/src/components/pullRequest/PullRequestCodeTab.tsx +++ b/apps/web/src/components/pullRequest/PullRequestCodeTab.tsx @@ -69,6 +69,8 @@ import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, + DropdownMenuRadioGroup, + DropdownMenuRadioItem, DropdownMenuTrigger, } from "../ui/menu"; import { toastManager } from "../ui/toast"; @@ -1048,31 +1050,31 @@ function PullRequestCodeTab({ - onSelectedCommitChange(null)} + onSelectedCommitChange(value === "all" ? null : value)} > - All commits - - {orderedCommits.slice(0, visibleCommitCount).map((entry) => ( - onSelectedCommitChange(entry.oid)} - > - {/* Headlines run long, and the abbreviated oid after one is what a reader - matches against the commit list on the host. */} - - {entry.messageHeadline}} - /> - {entry.messageHeadline} - - - {entry.oid.slice(0, 7)} - - - ))} + + All commits + + {orderedCommits.slice(0, visibleCommitCount).map((entry) => ( + + {/* Headlines run long, and the abbreviated oid after one is what a reader + matches against the commit list on the host. */} + + + {entry.messageHeadline}} + /> + {entry.messageHeadline} + + + {entry.oid.slice(0, 7)} + + + + ))} + {orderedCommits.length > visibleCommitCount ? ( // Kept out of the radio group: it changes how much of the list is on screen // rather than what the diff is scoped to.