Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -713,7 +713,15 @@ const AiChatComponent: React.FC = () => {
)}

{/* Chat messages */}
<div ref={scrollRef} className="flex-1 overflow-y-auto p-4 space-y-4 scrollbar-thin bg-[#0e0e0e]">
<div
ref={scrollRef}
role="log"
aria-live="polite"
aria-relevant="additions text"
aria-busy={isTyping}
aria-label={t('ai.chat_log_label')}
className="flex-1 overflow-y-auto p-4 space-y-4 scrollbar-thin bg-[#0e0e0e]"
>
{activeSession?.messages.map((msg, i) => (
<div key={i} className={`flex ${msg.role === "user" ? "justify-end" : "justify-start"}`}>
<div
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -768,13 +768,26 @@ const GitExplorerComponent: React.FC = () => {
</div>
) : (
<div className="flex flex-col h-full">
{gitFeedback && (
<div className="px-3 py-2 bg-white/[0.03] border-b border-[#1a1a1a] border-l-2 border-l-white/40 text-[11px] text-white/80 flex items-center gap-2">
<span className="truncate">{gitFeedback}</span>
</div>
)}
{/* Live regions must stay mounted so screen readers can observe
text changes; conditional mounting would hide the update. */}
<div
role="status"
aria-live="polite"
aria-atomic="true"
className={
gitFeedback
? "px-3 py-2 bg-white/[0.03] border-b border-[#1a1a1a] border-l-2 border-l-white/40 text-[11px] text-white/80 flex items-center gap-2"
: "sr-only"
}
>
<span className="truncate">{gitFeedback}</span>
</div>
{hasConflicts && (
<div className="px-3 py-2 bg-red-500/10 border-b border-red-500/20 border-l-2 border-l-red-400 text-[11px] text-red-200 flex items-center gap-2">
<div
role="alert"
aria-live="assertive"
className="px-3 py-2 bg-red-500/10 border-b border-red-500/20 border-l-2 border-l-red-400 text-[11px] text-red-200 flex items-center gap-2"
>
<AlertTriangle size={12} className="shrink-0 text-red-400" />
<span>{t('git.conflicts.banner')}</span>
</div>
Expand Down
2 changes: 2 additions & 0 deletions apps/desktop/src/api/builtin.l10n.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -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',
Expand Down
26 changes: 26 additions & 0 deletions apps/desktop/src/components/UpdaterDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -112,8 +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 (
<div className="fixed bottom-6 right-6 z-[9999] animate-in slide-in-from-bottom-4 fade-in duration-300">
{/* 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. */}
<span
role={state.phase === "error" ? "alert" : "status"}
aria-live={state.phase === "error" ? "assertive" : "polite"}
className="sr-only"
>
{getAnnouncement()}
</span>
<div className="w-[340px] bg-[#111] border border-[#262626] rounded-2xl shadow-[0_8px_40px_rgba(0,0,0,0.6)] overflow-hidden">

{/* Header */}
Expand Down
Loading