// Accessibility enhancements for keyboard + screen-reader users. // // Several primary controls in Odysseus are authored as click-only
s // (most notably the whole sidebar navigation: New Chat, Search, Brain, // Calendar, Compare, Cookbook, Deep Research, Gallery, Library, Notes, // Tasks, Theme, plus the account row).
s are not in the tab order and // are not announced as buttons, so keyboard and screen-reader users cannot // reach or operate them. // // This module enhances those rows in place — making them focusable // (tabindex=0), announcing them as buttons when it's safe to do so, and // activating them with Enter / Space — without changing how they look or // how they behave for mouse users. The visible focus ring already exists in // style.css (`.list-item:focus-visible`); it simply never fired because the // rows were never focusable. (function () { 'use strict'; // Click-as-button rows we want reachable by keyboard. var ROW_SELECTOR = ['#sidebar .list-item', '#user-bar-profile'].join(','); // Native interactive descendants. If a row contains one of these we must // NOT give the row role="button" — a button inside a button is invalid // (axe "nested-interactive") and confuses screen readers. Such rows still // become focusable + Enter/Space-activatable, just without the role. var NESTED_INTERACTIVE = 'a[href],button,input,select,textarea,[contenteditable="true"],[tabindex]:not([tabindex="-1"])'; function enhanceRow(el) { if (!el || el.nodeType !== 1 || el.dataset.a11yEnhanced === '1') return; var tag = el.tagName; // Leave genuine native controls alone. if (tag === 'BUTTON' || tag === 'A' || tag === 'INPUT' || tag === 'SELECT' || tag === 'TEXTAREA') return; el.dataset.a11yEnhanced = '1'; if (!el.hasAttribute('tabindex')) el.setAttribute('tabindex', '0'); el.setAttribute('data-a11y-activatable', '1'); if (!el.querySelector(NESTED_INTERACTIVE) && !el.hasAttribute('role')) { el.setAttribute('role', 'button'); } // Guarantee an accessible name. Visible text normally supplies it; fall // back to the title attribute for icon-only rows. if (!el.getAttribute('aria-label') && !(el.textContent || '').trim() && el.getAttribute('title')) { el.setAttribute('aria-label', el.getAttribute('title')); } } function enhanceAll(root) { (root || document).querySelectorAll(ROW_SELECTOR).forEach(enhanceRow); } // ---- Modal dialogs ----------------------------------------------------- // Odysseus modals are plain