Skip to content

Commit 4bc13b2

Browse files
committed
fix(webui): let the tab strip and the diff stats pass the accessibility scan
Two findings the scan reports, both real, both fixed in code rather than recorded as debt - the scan refuses contrast exceptions, and a reviewed fix is the house rule. The workspace tab strip claimed `role="tablist"` and gave each tab a `role="tab"`, but every tab also carries a close button, and a tablist owns only tabs: axe reported "children which are not allowed" for the close button in every state, including the empty one. The app has no `role="tabpanel"` for the strip to point at either, so the tabs pattern was already incomplete. The strip is a group of toggle buttons now - `role="group"` on the row, `aria-pressed` on the tab that is open - and the close button stays where it is. The rendered vocabulary changed, so the presentation gate's reviewed signature is recorded for the two modules. The diff stats are 12px text drawn in the status colours, and the light theme's green measures 2.7:1 on the grouped surface; the scan reported the addition and the deletion badge in both themes. The stylesheet already asked for `--code-theme-addition-foreground` and `--code-theme-deletion-foreground`, and those tokens did not exist, so it fell back to colours that never passed. They are defined now: green 700 and red 600 on light (6.3:1 and 5.7:1), green 400 and red 300 on dark (6.3:1 and 5.9:1).
1 parent 096c2ec commit 4bc13b2

5 files changed

Lines changed: 21 additions & 8 deletions

File tree

‎packages/webui/src/client/features/workspace/ui/WebuiWorkspaceTabStrip.public.tsx‎

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -286,7 +286,7 @@ export function WebuiWorkspaceTabStrip({
286286
const tabListRef = useRef<HTMLDivElement>(null);
287287
useEffect(() => {
288288
tabListRef.current
289-
?.querySelector<HTMLElement>('[aria-selected="true"]')
289+
?.querySelector<HTMLElement>('[aria-pressed="true"]')
290290
?.scrollIntoView({ block: "nearest", inline: "nearest" });
291291
}, [state.activeTabId]);
292292

@@ -335,8 +335,7 @@ export function WebuiWorkspaceTabStrip({
335335
variant="plain"
336336
size="regular"
337337
className="webui-workspace-tab-control"
338-
role="tab"
339-
aria-selected={state.activeTabId === item.id}
338+
aria-pressed={state.activeTabId === item.id}
340339
data-testid={`workspace-tab-${item.id}`}
341340
onClick={() => dispatch({ type: "select-tab", tabId: item.id })}
342341
>

‎packages/webui/src/client/features/workspace/ui/WorkspacePanels.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -752,7 +752,7 @@ export function WebuiWorkspacePanel({ state, dispatch, sessionId, workspaceDir,
752752
return <aside ref={panelRef} className={`webui-workspace-panel ${state.expanded ? "is-expanded" : ""} ${panelClosing ? "is-closing" : ""} ${expansionMotion ? `is-${expansionMotion}` : ""}`} onAnimationEnd={handlePanelAnimationEnd} style={panelWidth !== undefined && !state.expanded && surfaceTier === "split" ? { width: `${panelWidth}px`, flex: `0 0 ${panelWidth}px` } : undefined} data-testid="workspace-panel" data-active-tab={tab} data-webui-surface-tier={surfaceTier}>
753753
<div className="webui-workspace-panel-resize-handle" role="separator" aria-label="调整工作区面板宽度" aria-orientation="vertical" aria-valuenow={Math.round(panelWidth ?? 50)} tabIndex={state.expanded ? -1 : 0} onPointerDown={startPanelResize} onPointerMove={movePanelResize} onPointerUp={stopPanelResize} onPointerCancel={stopPanelResize} onKeyDown={nudgePanelResize} />
754754
<div className="webui-workspace-panel-header">
755-
<div className="webui-workspace-tabs" role="tablist" aria-label="工作区面板标签">
755+
<div className="webui-workspace-tabs" role="group" aria-label="工作区面板标签">
756756
<WebuiWorkspaceTabStrip state={state} dispatch={panelDispatch} sessionId={sessionId} />
757757
</div>
758758
<div className="webui-workspace-panel-actions">

‎packages/webui/src/client/styles/tokens.css‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,11 @@
280280
--border_tertiary_hover: var(--opacity_black_1_8);
281281
--border_tertiary_inactive: var(--opacity_black_1_8);
282282
--border_tertiary_press: var(--opacity_black_1_15);
283+
/* The diff stats are 12px text, and a code colour has to clear WCAG AA
284+
* (4.5:1) against the surface it is drawn on. The status colours do not:
285+
* the light theme's green measures 2.7:1 on the grouped surface. */
286+
--code-theme-addition-foreground: var(--green_700);
287+
--code-theme-deletion-foreground: var(--red_600);
283288
--icon_default_accent: var(--blue_400);
284289
--icon_default_inverted: var(--gray_0);
285290
--icon_default_inverted_static: var(--opacity_white_0_95);
@@ -495,6 +500,11 @@
495500
--border_tertiary_hover: var(--opacity_black_1_8);
496501
--border_tertiary_inactive: var(--opacity_black_1_8);
497502
--border_tertiary_press: var(--opacity_black_1_15);
503+
/* The diff stats are 12px text, and a code colour has to clear WCAG AA
504+
* (4.5:1) against the surface it is drawn on. The status colours do not:
505+
* the light theme's green measures 2.7:1 on the grouped surface. */
506+
--code-theme-addition-foreground: var(--green_700);
507+
--code-theme-deletion-foreground: var(--red_600);
498508
--icon_default_accent: var(--blue_400);
499509
--icon_default_inverted: var(--gray_0);
500510
--icon_default_inverted_static: var(--opacity_white_0_95);
@@ -713,6 +723,10 @@
713723
--border_tertiary_hover: var(--opacity_white_0_15);
714724
--border_tertiary_inactive: var(--opacity_white_0_15);
715725
--border_tertiary_press: var(--opacity_white_0_8);
726+
/* The dark theme draws the diff stats on a darker surface, so the code
727+
* colours are the lighter steps: they measure 6.3:1 and 5.9:1 there. */
728+
--code-theme-addition-foreground: var(--green_400);
729+
--code-theme-deletion-foreground: var(--red_300);
716730
--icon_default_accent: var(--blue_500);
717731
--icon_default_inverted: var(--gray_1000);
718732
--icon_default_inverted_static: var(--opacity_white_0_95);

‎packages/webui/test/browser/workspace-panel-actions.spec.mjs‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,8 +111,8 @@ test("an open preview tab survives leaving the session and coming back", async (
111111
// so the panel re-renders from a store that never asked the runtime again.
112112
await expect(preview).toBeVisible();
113113
await expect(preview).toContainText("export const answer = 42;");
114-
await expect(page.getByRole("tab", { name: /a\.ts/u })).toHaveAttribute(
115-
"aria-selected",
114+
await expect(page.getByRole("button", { name: "a.ts", exact: true })).toHaveAttribute(
115+
"aria-pressed",
116116
"true",
117117
);
118118
expect(

‎scripts/lib/webui-presentation-inventory.json‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -228,14 +228,14 @@
228228
"features/transcript/ui/markdown.public.tsx": "125a314c717770f5",
229229
"features/transcript/ui/webui-mermaid.tsx": "fc1aab96c48f3a3d",
230230
"features/workspace/ui/WebuiWorkspaceDirectoryBrowser.public.tsx": "9feb6e3d2b274a52",
231-
"features/workspace/ui/WebuiWorkspaceTabStrip.public.tsx": "b267d52d32598fdd",
231+
"features/workspace/ui/WebuiWorkspaceTabStrip.public.tsx": "4d45a3b6e622571c",
232232
"features/workspace/ui/WorkspaceArchiveView.tsx": "b4ac1e613b7eff7c",
233233
"features/workspace/ui/WorkspaceCanvas.tsx": "cb7f6f22caa93fd0",
234234
"features/workspace/ui/WorkspaceFileTree.tsx": "606b6b0696e488b7",
235235
"features/workspace/ui/WorkspaceHtmlPreview.tsx": "f19ad21c5fe816f1",
236236
"features/workspace/ui/WorkspaceMediaPreview.tsx": "cbeadd98697a96c9",
237237
"features/workspace/ui/WorkspacePanelControls.tsx": "36d3cfa00459a60a",
238-
"features/workspace/ui/WorkspacePanels.tsx": "9c2df5d705fbb2fb",
238+
"features/workspace/ui/WorkspacePanels.tsx": "8137d1d6980c6917",
239239
"features/workspace/ui/WorkspaceProgressPanel.tsx": "e05e76aca83cf877",
240240
"shell/ArchonPage.tsx": "ce4b452ba9bb68ac",
241241
"shell/ArchonShell.tsx": "3dfc406588433f84",

0 commit comments

Comments
 (0)