From 7fde4bb3ad34d7f3d87b79e89b67398dd79e402f Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Thu, 30 Jul 2026 22:46:08 -0700 Subject: [PATCH] fix(a11y): my #2965 gave six dialogs two elements with the same accessible name (#2977) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **This fixes a regression I introduced in #2965, found by re-running the full dashboard lane on `main` rather than trusting the targeted runs I did at the time.** `AddNodeModal` and `ConnectNodeModal` are red on main: ``` → Found multiple elements with the text of: Add Node → Found multiple elements with the text of: Connect to Node ``` ### Cause #2965 dropped the redundant `" dialog"` suffix from each `FloatingWindow`'s `ariaLabel`. That was correct — `role="dialog"` already conveys it. What I missed is that six of those modals **also** put an `aria-label` with the *same* text on their own inner `
`: ```jsx
``` Before #2965 the two differed (`"Add Node dialog"` vs `"Add Node"`), so `getByLabelText("Add Node")` matched exactly one element. Now both match. ### Why the inner one goes, not the dialog's Those inner labels sit on **role-less `
`s**, where assistive technology ignores `aria-label` entirely — it was never conveying anything to anyone. Removing it restores a single accessible name per dialog and needs no test changes. ### Surface enumeration — four of the six were latent Only two surfaced as failures; the other four have no test querying by that name, so they would have shipped a duplicate accessible name silently. Found by scanning every component for an inner `aria-label` whose expression matches its own `ariaLabel` prop: | modal | was it red? | |---|---| | `AddNodeModal` | red on main | | `ConnectNodeModal` | red on main | | `GroupTaskModal` | latent | | `NodeDetailModal` | latent | | `ScriptsModal` | latent | | `WorkflowAddStepModal` | latent | ### Five more, deliberately untouched `AgentDetailView`, `PlanningModeModal`, `SettingsModal` (`role="region"`), `ScheduledTasksModal` (`role="listbox"`) and `NewTaskModal` (`role="dialog"`) also carry their dialog's name on an inner element — but those elements **have a role**, so the label is meaningful rather than dead markup. A listbox named "Automations" inside a dialog named "Automations" is redundant, not broken, and renaming it is a UX decision rather than a cleanup. Left alone and recorded here. **Verified:** 93/93 across `AddNodeModal`, `ConnectNodeModal`, `NodesView`, `GroupTaskModal`, `ScriptsModal` and the #2965 aria guard; `tsc -p tsconfig.app.json` 0 errors; lint clean; FNXC gate exit 0. Product-code change to a11y markup, so this is user-visible but needs no operator-facing note — say the word if you want a changeset. **Measured dashboard-lane state on main before this PR:** `3 failed | 11173 passed`. Two are these; the third is `MainContent.planning-project-remount`, which belongs to #2420 and is detailed there. Co-authored-by: Claude Opus 5 (1M context) --- packages/dashboard/app/components/AddNodeModal.tsx | 2 +- packages/dashboard/app/components/ConnectNodeModal.tsx | 5 +---- packages/dashboard/app/components/GroupTaskModal.tsx | 2 +- packages/dashboard/app/components/NodeDetailModal.tsx | 1 - packages/dashboard/app/components/ScriptsModal.tsx | 2 +- packages/dashboard/app/components/WorkflowAddStepModal.tsx | 2 +- 6 files changed, 5 insertions(+), 9 deletions(-) diff --git a/packages/dashboard/app/components/AddNodeModal.tsx b/packages/dashboard/app/components/AddNodeModal.tsx index 28f6864927..474b2e4697 100644 --- a/packages/dashboard/app/components/AddNodeModal.tsx +++ b/packages/dashboard/app/components/AddNodeModal.tsx @@ -258,7 +258,7 @@ export function AddNodeModal({ isOpen, onClose, onSubmit, onDiscoverRemoteProjec return ( /* FNXC:ModalTouchGeometry 2026-07-26-13:15: Shared FloatingWindow owns this modal's touch drag, resize, clamping, and persistence while phone and short viewports retain their sheet behavior. */ -
+

{t("nodes.addNode", "Add Node")}