fix(connect) [BRNS-DESK-030]: expose the model choice and name the glyph-only home controls - #83
fix(connect) [BRNS-DESK-030]: expose the model choice and name the glyph-only home controls#83sebastian-ssvlabs wants to merge 1 commit into
Conversation
…yph-only home controls The four onboarding model options were plain buttons that changed nothing in the accessibility tree when picked, on a screen whose only job is picking one. They are now a single radiogroup: each option is a role="radio" with aria-checked bound to the existing selection state, arrow keys move the selection inside one Tab stop, and the vendor glyphs are aria-hidden so the accessible name is the vendor and its plan rather than punctuation. On Home, the controls whose whole content is a decorative glyph (tab close, session-colour clear, the two dismiss crosses) now carry an aria-label naming the action, and the recent-session tiles hide their placeholder glyph and announce what opening them does.
Chris-ssvlabs
left a comment
There was a problem hiding this comment.
- 🟡 src/routes/+page.svelte:13204 — BRNS-DESK-030: the side-ask close button (
onclick={closeSideAsk},title="Close (Esc)") still exposes bare×as its accessible name — name-from-content wins overtitle, the same defect class this PR fixes five other instances of, so the "no Home control exposes bare glyph as name" acceptance criterion is not met. Addaria-label={t("home_dismissAnswer")}-style key (e.g.home_closeAside) to both locale files and the button.
nir-ssvlabs
left a comment
There was a problem hiding this comment.
This is a correct radiogroup rather than an approximation of one: role="radiogroup" with an aria-label, role="radio" + aria-checked per option, a genuine roving tabindex (sel || (!model && i === 0) ? 0 : -1) so the four vendors are one Tab stop with a defined entry point before anything is picked, and arrows that move focus and selection — which is the ARIA-specified behaviour for a radiogroup, not a shortcut. aria-hidden="true" on the glyphs is the fix for "✕ xAI" announcing as a cancel marker, and the Home controls now name their action instead of exposing a bare glyph.
Checked: the roles, aria-checked, tabindex management and wraparound in modelPickerKeys, the glyph hiding, and that the new labels exist in both locales. Not read: a screen-reader pass — the tree is right on inspection, but that is the check that would actually prove the announcement.
Merge: ✅ into main.
Summary
A ○ ✕ ◆) are hidden from the accessibility tree, so names no longer read as punctuation — notably "✕ xAI", which announced as a cancel/unavailable marker on an enabled control.Mechanism
Radio-group semantics rather than
aria-pressed: the screen is a single choice among four, and one role change conveys both the selection and the "1 of 4" grouping.aria-checkedbinds to the samemodel === p.idexpression the visual selected state already uses, so there is no second source of truth. Rovingtabindex(0on the checked option,-1on the rest, falling back to the first option when nothing is stored yet) plus arrow-key handling is the standard pattern; arrows both move focus and select.Connect screen — accessible names and states
AXButton "A Claude RECOMMENDED via your Claude subscription"— no stateAXRadioButton "Claude RECOMMENDED via your Claude subscription",AXValue=1when chosenAXButton "○ OpenAI GPT via your ChatGPT Plus/Pro"AXRadioButton "OpenAI GPT via your ChatGPT Plus/Pro",AXValue=0/1AXButton "✕ xAI GROK via your X Premium+"AXRadioButton "xAI GROK via your X Premium+",AXValue=0/1AXButton "◆ Gemini GOOGLE via your Google One AI"AXRadioButton "Gemini GOOGLE via your Google One AI",AXValue=0/1AXRadioGroup "Choose a model provider"wrapping all fourSelecting one option now flips exactly one
aria-checkedtotrueand the rest tofalse, which is the diff the ticket could not find.Home — glyph-only controls
✧placeholders)openSession(card.sessionId)"Resume session: <title>", placeholder glypharia-hiddencloseWorkspaceTab(tab.id)"×""Close tab"setSessionColor(id, null)"×""Clear session color"withoutSideAsk(sideAsks, ask.id)"×""Dismiss answer"scratchpadError = null"×""Dismiss error"The
titletooltips are untouched;aria-labelwas added because name-from-content beats thetitleattribute, so the glyph was winning. Six new strings, all added to bothmessages/en.jsonandmessages/zh-CN.json(this route had not()usage before — the import is new here, and the existing hardcoded visible strings on the screen were deliberately left alone as out of scope).Semantics only: no visual, layout, or mouse behaviour changed. The check-circle, the border/shadow selected treatment, and
pickModel()itself are all as they were.Verification
npx prettier --check src/routes/+page.svelte messages/en.json messages/zh-CN.json— cleannpx eslint src/routes/+page.svelte— 0 errors, 34 warnings, byte-identical to the same command onorigin/main(all pre-existingno-unused-vars). eslint's svelte a11y rules are the meaningful local signal here and the change adds nothing.node scripts/i18n-check.mjs— 0 errors, 18 warnings, same counts asorigin/main; the six new keys are present and translated in both locales with matching{title}placeholder.npm run check/npm run build/cargo— this environment has no.svelte-kitor cargo target, sosvelte-checkfails onCannot read file '.svelte-kit/tsconfig.json'before it looks at any source. That is also the sole reason the commit used--no-verify(the hook's prettier and eslint steps both passed).AXRadioGroup/AXValuestrings in the table above. They are the expected WebKit mapping of the roles and attributes in the diff, not observed output — the repro needs a built app and the macOS accessibility tree.The repo's ~127 pre-existing
svelte-checka11y warnings are out of scope and untouched — no attempt was made to clear that baseline. The one point where it moves is incidental: the picker options previously hadonclickwith no key handler, so adding the radio keydown removes that particular warning rather than adding one.