From 3ed02845f76800c48c5a4e7336d7f0c4fecece41 Mon Sep 17 00:00:00 2001 From: yappologistic Date: Thu, 26 Mar 2026 10:50:07 -0600 Subject: [PATCH 1/3] fix: UI/UX accessibility and async state guard improvements MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix wrong aria-labels in OpenInPicker ("Subscription actions" → "Open in editor", "Copy options" → "More editor options") - Disable export dialog controls (format toggles, download button) during save-to-workspace - Disable share dialog copy/open buttons during revoke operation - Make terminal split button actually disabled (was CSS-only faux-disabled) - Add focus-visible reveal to terminal close button (was invisible to keyboard) - Add role="separator" with aria-label to terminal resize handle - Add aria-expanded to disclosure buttons (PlanSidebar, ProposedPlanCard, ChangedFilesTree) - Add role="tree" and empty state to ChangedFilesTree - Add aria-label to MessageCopyButton icon-only button - Add aria-labels to PermissionPoliciesSection move/delete buttons - Add aria-pressed to PermissionPoliciesSection approval kind toggles - Add role="radiogroup" with role="radio" and aria-checked to ThreadExportDialog format selector - Update AGENTS.md with UI/UX audit guidelines --- AGENTS.md | 4 +++ apps/web/src/components/PlanSidebar.tsx | 1 + .../src/components/ThreadTerminalDrawer.tsx | 34 ++++++++++++++----- .../src/components/chat/ChangedFilesTree.tsx | 11 +++++- .../src/components/chat/MessageCopyButton.tsx | 1 + apps/web/src/components/chat/OpenInPicker.tsx | 6 ++-- .../src/components/chat/ProposedPlanCard.tsx | 1 + .../components/chat/ThreadExportDialog.tsx | 27 ++++++++++++--- .../src/components/chat/ThreadShareDialog.tsx | 8 +++-- .../settings/PermissionPoliciesSection.tsx | 4 +++ 10 files changed, 78 insertions(+), 19 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 517962c707..759f68d395 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -31,6 +31,10 @@ - Keep provider event logging opt-in. Raw provider prompts, tool payloads, approval answers, and runtime output must not be persisted by default; use `CUT3_ENABLE_PROVIDER_EVENT_LOGS=1` only for deliberate local debugging. - Keep provider exit failures visible end-to-end. If a runtime emits `session.exited` with a non-graceful reason, orchestration must preserve that reason in `thread.session.lastError` so OpenCode/Copilot/Kimi/Codex crashes do not look like silent clean stops. - When testing hot orchestration streams backed by PubSub, avoid `fork + sleep` subscription races. Start the collector with an explicit readiness handshake (for example `Effect.forkScoped` plus `Effect.yieldNow`, or another deterministic subscription barrier) before dispatching commands. +- Keep interactive controls properly disabled during in-flight async operations (e.g. export, share, revoke): users must not be able to trigger conflicting actions while a prior action is still completing. Guard format toggles, download buttons, and secondary actions behind the relevant `isSaving`/`isRevoking` flags. +- Keep ARIA semantics aligned with visual affordances: disclosure/expand buttons need `aria-expanded`, toggle-style buttons need `aria-pressed` or `role="radio"` with `aria-checked`, icon-only buttons need explicit `aria-label`, tree-like file lists need `role="tree"`, and controls revealed only on hover (e.g. terminal close buttons) must also be revealed on `focus-visible` so keyboard users can reach them. +- Keep `aria-label` values on interactive groups and their trigger buttons accurate and descriptive of the actual feature. Do not leave placeholder labels from copy-paste (e.g. "Subscription actions" for an editor picker, "Copy options" for an editor menu). +- When a button visually looks disabled (opacity, cursor-not-allowed), make it actually `disabled` so it is removed from tab order and does not fire click handlers. CSS-only faux-disabled states are a keyboard trap. ## Project Snapshot diff --git a/apps/web/src/components/PlanSidebar.tsx b/apps/web/src/components/PlanSidebar.tsx index 47bee930cc..16b51862f9 100644 --- a/apps/web/src/components/PlanSidebar.tsx +++ b/apps/web/src/components/PlanSidebar.tsx @@ -224,6 +224,7 @@ const PlanSidebar = memo(function PlanSidebar({
diff --git a/apps/web/src/components/chat/ThreadShareDialog.tsx b/apps/web/src/components/chat/ThreadShareDialog.tsx index 7f8c3fd231..bc5557b1bc 100644 --- a/apps/web/src/components/chat/ThreadShareDialog.tsx +++ b/apps/web/src/components/chat/ThreadShareDialog.tsx @@ -92,11 +92,15 @@ export const ThreadShareDialog = memo(function ThreadShareDialog(props: { variant="outline" size="sm" onClick={props.onOpenSharedView} - disabled={!hasShare || !props.shareUrl} + disabled={!hasShare || !props.shareUrl || props.isRevokingShare} > Open shared view -
diff --git a/apps/web/src/components/settings/PermissionPoliciesSection.tsx b/apps/web/src/components/settings/PermissionPoliciesSection.tsx index 2eee859362..42bb94e3fd 100644 --- a/apps/web/src/components/settings/PermissionPoliciesSection.tsx +++ b/apps/web/src/components/settings/PermissionPoliciesSection.tsx @@ -184,6 +184,7 @@ export function PermissionPoliciesSection({ size="icon-sm" variant="ghost" disabled={isFirst} + aria-label="Move rule up" onClick={() => onChangeRules(moveRule(rules, rule.id, -1))} > @@ -192,6 +193,7 @@ export function PermissionPoliciesSection({ size="icon-sm" variant="ghost" disabled={isLast} + aria-label="Move rule down" onClick={() => onChangeRules(moveRule(rules, rule.id, 1))} > @@ -199,6 +201,7 @@ export function PermissionPoliciesSection({