From c9f352a39f2312ea02d86f9eff0453764076ff19 Mon Sep 17 00:00:00 2001 From: Matias Palma Date: Tue, 21 Apr 2026 00:32:20 -0400 Subject: [PATCH 1/2] =?UTF-8?q?improvements:=20a11y=20=E2=80=94=20aria-liv?= =?UTF-8?q?e=20regions=20for=20chat,=20updater=20and=20git=20feedback?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Screen readers were silent on four dynamic UI surfaces that announce completion or error state: - `AiChatComponent`: the chat transcript had no live region, so streaming assistant replies and tool-run status never reached AT. Marked the scroll container as `role=log` / `aria-live=polite` and toggle `aria-busy` while a response is in flight. - `UpdaterDialog`: the toast is `role=status` / `aria-live=polite` normally and escalates to `role=alert` / `aria-live=assertive` when the updater reports an error. - `GitExplorerComponent`: - `gitFeedback` flash banner is now a persistent `role=status` region (toggled via `sr-only`) instead of being conditionally mounted, so transient commit/push/stash messages are actually announced when they appear. - Merge-conflict banner is `role=alert` so the conflict is surfaced immediately rather than waiting for focus. Closes #105 --- .../builtin.ai-assistant/AiChatComponent.tsx | 10 +++++++- .../GitExplorerComponent.tsx | 25 ++++++++++++++----- apps/desktop/src/api/builtin.l10n.ts | 2 ++ apps/desktop/src/components/UpdaterDialog.tsx | 7 +++++- 4 files changed, 36 insertions(+), 8 deletions(-) diff --git a/apps/desktop/src/addons/builtin.ai-assistant/AiChatComponent.tsx b/apps/desktop/src/addons/builtin.ai-assistant/AiChatComponent.tsx index 19952c6f..ef88b5ba 100644 --- a/apps/desktop/src/addons/builtin.ai-assistant/AiChatComponent.tsx +++ b/apps/desktop/src/addons/builtin.ai-assistant/AiChatComponent.tsx @@ -713,7 +713,15 @@ const AiChatComponent: React.FC = () => { )} {/* Chat messages */} -
+
{activeSession?.messages.map((msg, i) => (
{
) : (
- {gitFeedback && ( -
- {gitFeedback} -
- )} + {/* Live regions must stay mounted so screen readers can observe + text changes; conditional mounting would hide the update. */} +
+ {gitFeedback} +
{hasConflicts && ( -
+
{t('git.conflicts.banner')}
diff --git a/apps/desktop/src/api/builtin.l10n.ts b/apps/desktop/src/api/builtin.l10n.ts index faa1b10a..c5f2d3a6 100644 --- a/apps/desktop/src/api/builtin.l10n.ts +++ b/apps/desktop/src/api/builtin.l10n.ts @@ -258,6 +258,7 @@ export function registerBuiltinTranslations() { 'ai.status.badge.ready': 'Ready', 'ai.status.badge.busy': 'Busy', 'ai.disclaimer': 'Trixty AI can make mistakes. Check important info.', + 'ai.chat_log_label': 'AI chat transcript', 'terminal.error_connect': 'Error connecting to terminal: ', 'editor.empty_desc': 'Select a file from the explorer to begin', @@ -635,6 +636,7 @@ export function registerBuiltinTranslations() { 'ai.status.badge.ready': 'Listo', 'ai.status.badge.busy': 'Ocupado', 'ai.disclaimer': 'Trixty AI puede cometer errores. Verifica info importante.', + 'ai.chat_log_label': 'Transcripción del chat de IA', 'terminal.error_connect': 'Error al conectar con la terminal: ', 'editor.empty_desc': 'Selecciona un archivo del explorador para comenzar', diff --git a/apps/desktop/src/components/UpdaterDialog.tsx b/apps/desktop/src/components/UpdaterDialog.tsx index 06061f63..918c0bed 100644 --- a/apps/desktop/src/components/UpdaterDialog.tsx +++ b/apps/desktop/src/components/UpdaterDialog.tsx @@ -113,7 +113,12 @@ const UpdaterDialog: React.FC = () => { }; return ( -
+
{/* Header */} From 0d8219e7f29b828728e30474d941507ce5d0a6af Mon Sep 17 00:00:00 2001 From: Matias Palma Date: Tue, 21 Apr 2026 01:14:28 -0400 Subject: [PATCH 2/2] a11y: scope updater live region to a dedicated sr-only announcement MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Copilot review on #172 pointed out that wrapping the whole updater toast in role=status + aria-atomic=true means screen readers re-announce the entire toast (title, buttons, body) every time `progress` ticks — and `updater-progress` fires on every downloaded chunk during install, which can be dozens of events per second. Replaced the toast-wide live region with a single `sr-only` span that carries a pre-formatted phase string (title + status, without the raw progress percentage). Sighted users see the same toast; screen readers now get one announcement per phase change instead of a flood during download, and the role still escalates to `alert` on error. --- apps/desktop/src/components/UpdaterDialog.tsx | 33 +++++++++++++++---- 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/apps/desktop/src/components/UpdaterDialog.tsx b/apps/desktop/src/components/UpdaterDialog.tsx index 918c0bed..88030dfe 100644 --- a/apps/desktop/src/components/UpdaterDialog.tsx +++ b/apps/desktop/src/components/UpdaterDialog.tsx @@ -112,13 +112,34 @@ const UpdaterDialog: React.FC = () => { } }; + // Build the string AT should announce for the current phase. Progress + // percentage is intentionally omitted because `updater-progress` fires + // on every chunk and would flood screen readers if it lived inside a + // live region. + const getAnnouncement = (): string => { + switch (state.phase) { + case "checking": return t('updater.checking'); + case "up-to-date": return `${getDialogTitle()}. ${t('updater.uptodate')}`; + case "available": return `${getDialogTitle()}. ${t('updater.new_version')} ${state.version}`; + case "downloading": return t('updater.downloading'); + case "ready": return `${getDialogTitle()}. ${t('updater.relaunching')}`; + case "error": return `${getDialogTitle()}. ${state.message}`; + default: return getDialogTitle(); + } + }; + return ( -
+
+ {/* Dedicated live region so only the phase text is announced — not + the whole toast with buttons, and not every frame of the + `updater-progress` stream. Role escalates to `alert` on error. */} + + {getAnnouncement()} +
{/* Header */}