Accessibility: add labels and toggle states
* Accessibility: ARIA labels and toggle states Screen readers couldn't name several icon-only controls or tell whether the tool toggles were on. This adds accessible names and exposes toggle state, with no behavior or layout change. - Icon-only buttons get aria-label: web/shell tool toggles, the "more tools" overflow button (+ aria-haspopup), and the color-reset buttons. - Unlabeled inputs/selects get aria-label: memory + skills search, model-picker search, memory sort, theme font/density selects, and the new-memory / skill (title, when-to-use, how, tags) fields, which only had a visual floating label. - Toggle state via aria-pressed, kept in sync at the existing .active write sites: web/shell toggles (setupToggle) and the Agent/Chat mode buttons (initModeToggle). Static aria-pressed added in the markup so the attribute exists before JS runs. Scope: first slice of the ROADMAP accessibility pass. Focus-visible/contrast, reduced-motion, and modal dialog roles/focus-trap are left for follow-ups. Checks: node --check static/app.js. No Python touched. * Accessibility: mark chat log busy while streaming The chat log is an aria-live="polite" region, so streaming a response token-by-token made screen readers announce every partial update — noisy and unreadable. Set aria-busy="true" on #chat-history while a response streams and back to "false" in the stream's finally block. Assistive tech then waits for the settled message and announces it once. Checks: node --check static/js/chat.js.
This commit is contained in:
committed by
GitHub
parent
aa0a9e8b5a
commit
cfb7ec1c71
@@ -1564,6 +1564,8 @@ function initializeEventListeners() {
|
||||
saveToggleState(st);
|
||||
agentBtn.classList.toggle('active', mode === 'agent');
|
||||
chatBtn.classList.toggle('active', mode === 'chat');
|
||||
agentBtn.setAttribute('aria-pressed', String(mode === 'agent'));
|
||||
chatBtn.setAttribute('aria-pressed', String(mode === 'chat'));
|
||||
// Slide the pill to the active button
|
||||
const toggle = agentBtn.closest('.mode-toggle');
|
||||
if (toggle) toggle.classList.toggle('mode-chat', mode === 'chat');
|
||||
@@ -1621,11 +1623,13 @@ function initializeEventListeners() {
|
||||
const chk = el(checkboxId);
|
||||
if (chk) chk.checked = saved;
|
||||
btn.classList.toggle('active', saved);
|
||||
btn.setAttribute('aria-pressed', String(saved));
|
||||
btn.addEventListener('click', () => {
|
||||
const curMode = (loadToggleState().mode) || 'chat';
|
||||
const chk = el(checkboxId);
|
||||
chk.checked = !chk.checked;
|
||||
btn.classList.toggle('active', chk.checked);
|
||||
btn.setAttribute('aria-pressed', String(chk.checked));
|
||||
saveToolPref(stateKey, curMode, chk.checked);
|
||||
showToolToggleToast(stateKey, chk.checked);
|
||||
if (chk.checked) _showToolSplash(stateKey);
|
||||
|
||||
@@ -265,7 +265,7 @@
|
||||
<p class="memory-desc doclib-desc" style="margin-top:6px;">Long-term facts the AI remembers across chats — recall, edit, or curate.</p>
|
||||
<div class="memory-toolbar">
|
||||
<div class="memory-toolbar-row">
|
||||
<select id="memory-sort" class="memory-sort-select">
|
||||
<select id="memory-sort" class="memory-sort-select" aria-label="Sort memories">
|
||||
<option value="newest">Newest</option>
|
||||
<option value="oldest">Oldest</option>
|
||||
<option value="alpha">A-Z</option>
|
||||
@@ -274,7 +274,7 @@
|
||||
<button id="memory-select-btn" class="memory-toolbar-btn" title="Select multiple memories">Select</button>
|
||||
<button id="memory-tidy-btn" class="memory-toolbar-btn" title="AI tidy: deduplicate and clean up memories"><svg width="11" height="11" viewBox="0 0 24 24" fill="currentColor" style="vertical-align:-1px;margin-right:2px;"><path d="M12 0L14.59 8.41L23 12L14.59 15.59L12 24L9.41 15.59L1 12L9.41 8.41Z"/></svg> Tidy</button>
|
||||
</div>
|
||||
<input type="text" id="memory-search" placeholder="Search memories…" class="memory-search-input" />
|
||||
<input type="text" id="memory-search" placeholder="Search memories…" class="memory-search-input" aria-label="Search memories" />
|
||||
<div id="memory-category-filters" class="memory-category-filters">
|
||||
<button class="memory-cat-chip active" data-cat="all">all</button>
|
||||
</div>
|
||||
@@ -304,7 +304,7 @@
|
||||
</p>
|
||||
<div class="memory-add-row" style="margin-top:8px;">
|
||||
<div class="skill-ph-wrap" style="flex:1;min-width:0;">
|
||||
<input type="text" id="new-memory-input" placeholder=" " class="memory-add-input skill-hint-input" />
|
||||
<input type="text" id="new-memory-input" placeholder=" " class="memory-add-input skill-hint-input" aria-label="New memory text" />
|
||||
<span class="skill-rich-ph"><span class="k">Add a memory</span> — e.g. 'I prefer concise replies' <svg class="k" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-left:4px;" aria-hidden="true"><polyline points="9 10 4 15 9 20"/><path d="M20 4v7a4 4 0 0 1-4 4H4"/></svg></span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -315,19 +315,19 @@
|
||||
</div>
|
||||
<p class="memory-desc doclib-desc" style="margin-top:6px;">Create a skill by hand — title, what it solves, and an approach.</p>
|
||||
<div class="skill-ph-wrap" style="margin-top:4px;margin-bottom:6px;">
|
||||
<input type="text" id="new-skill-title" placeholder=" " class="memory-add-input skill-hint-input" />
|
||||
<input type="text" id="new-skill-title" placeholder=" " class="memory-add-input skill-hint-input" aria-label="Skill title" />
|
||||
<span class="skill-rich-ph"><span class="k">Title</span> — short name, e.g. “build-vllm-wheel”</span>
|
||||
</div>
|
||||
<div class="skill-ph-wrap" style="margin-bottom:6px;">
|
||||
<input type="text" id="new-skill-problem" placeholder=" " class="memory-add-input skill-hint-input" />
|
||||
<input type="text" id="new-skill-problem" placeholder=" " class="memory-add-input skill-hint-input" aria-label="When to use this skill" />
|
||||
<span class="skill-rich-ph"><span class="k">When to use</span> — what problem does this skill solve?</span>
|
||||
</div>
|
||||
<div class="skill-ph-wrap" style="margin-bottom:6px;">
|
||||
<textarea id="new-skill-solution" placeholder=" " class="memory-add-input skill-hint-input" rows="2" style="resize:vertical;"></textarea>
|
||||
<textarea id="new-skill-solution" placeholder=" " class="memory-add-input skill-hint-input" rows="2" style="resize:vertical;" aria-label="How — the approach or steps"></textarea>
|
||||
<span class="skill-rich-ph skill-rich-ph-top"><span class="k">How</span> — the approach, steps, commands, or rules to follow</span>
|
||||
</div>
|
||||
<div class="skill-ph-wrap" style="margin-bottom:8px;">
|
||||
<input type="text" id="new-skill-tags" placeholder=" " class="memory-add-input skill-hint-input" />
|
||||
<input type="text" id="new-skill-tags" placeholder=" " class="memory-add-input skill-hint-input" aria-label="Tags" />
|
||||
<span class="skill-rich-ph"><span class="k">Tags</span> — comma-separated, e.g. python, build, vllm</span>
|
||||
</div>
|
||||
<div style="display:flex;justify-content:flex-end;">
|
||||
@@ -368,7 +368,7 @@
|
||||
<button id="skills-select-btn" class="memory-toolbar-btn" title="Select multiple skills">Select</button>
|
||||
<button id="skills-audit-btn" class="memory-toolbar-btn" title="Test every skill, auto-fix the weak ones, flag what still fails"><svg width="11" height="11" viewBox="0 0 24 24" fill="currentColor" style="vertical-align:-1px;margin-right:3px;"><path d="M12 0L14.59 8.41L23 12L14.59 15.59L12 24L9.41 15.59L1 12L9.41 8.41Z"/></svg>Audit all</button>
|
||||
</div>
|
||||
<input type="text" id="skills-search" placeholder="Search skills…" class="memory-search-input" />
|
||||
<input type="text" id="skills-search" placeholder="Search skills…" class="memory-search-input" aria-label="Search skills" />
|
||||
</div>
|
||||
<div id="skills-audit-panel" class="skills-audit-panel hidden"></div>
|
||||
<div id="skills-bulk-bar" class="memory-bulk-bar hidden">
|
||||
@@ -407,7 +407,7 @@
|
||||
<span class="admin-toggle-sub" style="display:block;margin-top:6px;opacity:0.6">Controls how many relevant published or approved skills are added to each agent request.</span>
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;gap:12px;margin-top:8px">
|
||||
<span class="admin-toggle-sub" style="margin:0">Max skills per request</span>
|
||||
<input type="number" id="skill-max-input" min="0" max="12" step="1" value="3" style="flex-shrink:0;width:72px;background:var(--input-bg,var(--panel));color:var(--fg);border:1px solid var(--border);border-radius:6px;padding:4px 6px;font-size:12px;text-align:right;font-variant-numeric:tabular-nums" />
|
||||
<input type="number" id="skill-max-input" min="0" max="12" step="1" value="3" aria-label="Max skills to inject" style="flex-shrink:0;width:72px;background:var(--input-bg,var(--panel));color:var(--fg);border:1px solid var(--border);border-radius:6px;padding:4px 6px;font-size:12px;text-align:right;font-variant-numeric:tabular-nums" />
|
||||
</div>
|
||||
<span class="admin-toggle-sub" style="display:block;margin-top:6px;opacity:0.5">Set to 0 to disable skill injection.</span>
|
||||
</div>
|
||||
@@ -464,12 +464,12 @@
|
||||
<div class="admin-card">
|
||||
<h2>Colors</h2>
|
||||
<div class="theme-custom" id="themeCustom">
|
||||
<div class="color-row"><label>Background</label><input type="color" id="clr-bg"><button class="color-reset-btn" data-reset="bg" title="Reset this color">↺</button></div>
|
||||
<div class="color-row"><label>Text</label><input type="color" id="clr-fg"><button class="color-reset-btn" data-reset="fg" title="Reset this color">↺</button></div>
|
||||
<div class="color-row"><label>Panel</label><input type="color" id="clr-panel"><button class="color-reset-btn" data-reset="panel" title="Reset this color">↺</button></div>
|
||||
<div class="color-row"><label>Sidebar</label><input type="color" id="adv-sidebarBg"><button class="color-reset-btn" data-reset-adv="sidebarBg" title="Reset this color">↺</button></div>
|
||||
<div class="color-row"><label>Border</label><input type="color" id="clr-border"><button class="color-reset-btn" data-reset="border" title="Reset this color">↺</button></div>
|
||||
<div class="color-row"><label>Accent</label><input type="color" id="clr-red"><button class="color-reset-btn" data-reset="red" title="Reset this color">↺</button></div>
|
||||
<div class="color-row"><label>Background</label><input type="color" id="clr-bg"><button class="color-reset-btn" data-reset="bg" title="Reset this color" aria-label="Reset color">↺</button></div>
|
||||
<div class="color-row"><label>Text</label><input type="color" id="clr-fg"><button class="color-reset-btn" data-reset="fg" title="Reset this color" aria-label="Reset color">↺</button></div>
|
||||
<div class="color-row"><label>Panel</label><input type="color" id="clr-panel"><button class="color-reset-btn" data-reset="panel" title="Reset this color" aria-label="Reset color">↺</button></div>
|
||||
<div class="color-row"><label>Sidebar</label><input type="color" id="adv-sidebarBg"><button class="color-reset-btn" data-reset-adv="sidebarBg" title="Reset this color" aria-label="Reset color">↺</button></div>
|
||||
<div class="color-row"><label>Border</label><input type="color" id="clr-border"><button class="color-reset-btn" data-reset="border" title="Reset this color" aria-label="Reset color">↺</button></div>
|
||||
<div class="color-row"><label>Accent</label><input type="color" id="clr-red"><button class="color-reset-btn" data-reset="red" title="Reset this color" aria-label="Reset color">↺</button></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="theme-adv-toggle" id="theme-adv-toggle">
|
||||
@@ -479,38 +479,38 @@
|
||||
<div class="theme-adv-group">
|
||||
<div class="theme-adv-group-label">Chat Bubbles</div>
|
||||
<div class="theme-custom">
|
||||
<div class="color-row"><label>User Chat Bubble</label><input type="color" id="adv-userBubbleBg"><button class="color-reset-btn" data-reset-adv="userBubbleBg" title="Reset this color">↺</button></div>
|
||||
<div class="color-row"><label>AI Chat Bubble</label><input type="color" id="adv-aiBubbleBg"><button class="color-reset-btn" data-reset-adv="aiBubbleBg" title="Reset this color">↺</button></div>
|
||||
<div class="color-row"><label>Border Chat Bubble</label><input type="color" id="adv-bubbleBorder"><button class="color-reset-btn" data-reset-adv="bubbleBorder" title="Reset this color">↺</button></div>
|
||||
<div class="color-row"><label>User Chat Bubble</label><input type="color" id="adv-userBubbleBg"><button class="color-reset-btn" data-reset-adv="userBubbleBg" title="Reset this color" aria-label="Reset color">↺</button></div>
|
||||
<div class="color-row"><label>AI Chat Bubble</label><input type="color" id="adv-aiBubbleBg"><button class="color-reset-btn" data-reset-adv="aiBubbleBg" title="Reset this color" aria-label="Reset color">↺</button></div>
|
||||
<div class="color-row"><label>Border Chat Bubble</label><input type="color" id="adv-bubbleBorder"><button class="color-reset-btn" data-reset-adv="bubbleBorder" title="Reset this color" aria-label="Reset color">↺</button></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="theme-adv-group">
|
||||
<div class="theme-adv-group-label">Sidebar</div>
|
||||
<div class="theme-custom">
|
||||
<div class="color-row"><label>Odysseus Logo</label><input type="color" id="adv-brandColor"><button class="color-reset-btn" data-reset-adv="brandColor" title="Reset this color">↺</button></div>
|
||||
<div class="color-row"><label title="Hamburger menu"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" style="vertical-align:-2px;"><line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="12" x2="21" y2="12"/><line x1="3" y1="18" x2="21" y2="18"/></svg></label><input type="color" id="adv-hamburgerColor"><button class="color-reset-btn" data-reset-adv="hamburgerColor" title="Reset this color">↺</button></div>
|
||||
<div class="color-row"><label>Odysseus Logo</label><input type="color" id="adv-brandColor"><button class="color-reset-btn" data-reset-adv="brandColor" title="Reset this color" aria-label="Reset color">↺</button></div>
|
||||
<div class="color-row"><label title="Hamburger menu"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" style="vertical-align:-2px;"><line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="12" x2="21" y2="12"/><line x1="3" y1="18" x2="21" y2="18"/></svg></label><input type="color" id="adv-hamburgerColor"><button class="color-reset-btn" data-reset-adv="hamburgerColor" title="Reset this color" aria-label="Reset color">↺</button></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="theme-adv-group">
|
||||
<div class="theme-adv-group-label">Chat Input / Prompt Area</div>
|
||||
<div class="theme-custom">
|
||||
<div class="color-row"><label>Input Bg</label><input type="color" id="adv-inputBg"><button class="color-reset-btn" data-reset-adv="inputBg" title="Reset this color">↺</button></div>
|
||||
<div class="color-row"><label>Input Border</label><input type="color" id="adv-inputBorder"><button class="color-reset-btn" data-reset-adv="inputBorder" title="Reset this color">↺</button></div>
|
||||
<div class="color-row"><label>Send Btn</label><input type="color" id="adv-sendBtnBg"><button class="color-reset-btn" data-reset-adv="sendBtnBg" title="Reset this color">↺</button></div>
|
||||
<div class="color-row"><label>Send Hover</label><input type="color" id="adv-sendBtnHover"><button class="color-reset-btn" data-reset-adv="sendBtnHover" title="Reset this color">↺</button></div>
|
||||
<div class="color-row"><label>Input Bg</label><input type="color" id="adv-inputBg"><button class="color-reset-btn" data-reset-adv="inputBg" title="Reset this color" aria-label="Reset color">↺</button></div>
|
||||
<div class="color-row"><label>Input Border</label><input type="color" id="adv-inputBorder"><button class="color-reset-btn" data-reset-adv="inputBorder" title="Reset this color" aria-label="Reset color">↺</button></div>
|
||||
<div class="color-row"><label>Send Btn</label><input type="color" id="adv-sendBtnBg"><button class="color-reset-btn" data-reset-adv="sendBtnBg" title="Reset this color" aria-label="Reset color">↺</button></div>
|
||||
<div class="color-row"><label>Send Hover</label><input type="color" id="adv-sendBtnHover"><button class="color-reset-btn" data-reset-adv="sendBtnHover" title="Reset this color" aria-label="Reset color">↺</button></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="theme-adv-group">
|
||||
<div class="theme-adv-group-label">Code Blocks</div>
|
||||
<div class="theme-custom">
|
||||
<div class="color-row"><label>Code Bg</label><input type="color" id="adv-codeBg"><button class="color-reset-btn" data-reset-adv="codeBg" title="Reset this color">↺</button></div>
|
||||
<div class="color-row"><label>Code Text</label><input type="color" id="adv-codeFg"><button class="color-reset-btn" data-reset-adv="codeFg" title="Reset this color">↺</button></div>
|
||||
<div class="color-row"><label>Code Bg</label><input type="color" id="adv-codeBg"><button class="color-reset-btn" data-reset-adv="codeBg" title="Reset this color" aria-label="Reset color">↺</button></div>
|
||||
<div class="color-row"><label>Code Text</label><input type="color" id="adv-codeFg"><button class="color-reset-btn" data-reset-adv="codeFg" title="Reset this color" aria-label="Reset color">↺</button></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="theme-adv-group">
|
||||
<div class="theme-adv-group-label">Controls</div>
|
||||
<div class="theme-custom">
|
||||
<div class="color-row"><label>Toggle On</label><input type="color" id="adv-toggleActive"><button class="color-reset-btn" data-reset-adv="toggleActive" title="Reset this color">↺</button></div>
|
||||
<div class="color-row"><label>Toggle On</label><input type="color" id="adv-toggleActive"><button class="color-reset-btn" data-reset-adv="toggleActive" title="Reset this color" aria-label="Reset color">↺</button></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="theme-adv-group">
|
||||
@@ -559,7 +559,7 @@
|
||||
<div class="theme-fd-row">
|
||||
<div class="theme-fd-group">
|
||||
<label class="theme-fd-label">Font</label>
|
||||
<select id="theme-font-select" class="theme-fd-select">
|
||||
<select id="theme-font-select" class="theme-fd-select" aria-label="Font">
|
||||
<option value="mono">Monospace</option>
|
||||
<option value="sans">Sans-serif</option>
|
||||
<option value="serif">Serif</option>
|
||||
@@ -567,7 +567,7 @@
|
||||
</div>
|
||||
<div class="theme-fd-group">
|
||||
<label class="theme-fd-label">Density</label>
|
||||
<select id="theme-density-select" class="theme-fd-select">
|
||||
<select id="theme-density-select" class="theme-fd-select" aria-label="Density">
|
||||
<option value="compact">Compact</option>
|
||||
<option value="comfortable">Comfortable</option>
|
||||
<option value="spacious">Spacious</option>
|
||||
@@ -993,7 +993,7 @@
|
||||
<button type="button" class="model-picker-btn" id="model-picker-btn" title="Switch model"><span id="model-picker-label">Select model</span> <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 15 12 9 18 15"/></svg></button>
|
||||
<div class="model-picker-menu hidden" id="model-picker-menu">
|
||||
<div class="model-picker-search-row">
|
||||
<input type="text" id="model-picker-search" placeholder="Search models..." autocomplete="off">
|
||||
<input type="text" id="model-picker-search" placeholder="Search models..." autocomplete="off" aria-label="Search models">
|
||||
<button type="button" class="model-picker-action-btn primary" id="model-picker-add-models-btn" title="Add model endpoints" aria-label="Add model endpoints">
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="M12 5v14"/><path d="M5 12h14"/></svg>
|
||||
</button>
|
||||
@@ -1007,7 +1007,7 @@
|
||||
<div class="chat-input-left">
|
||||
<!-- Overflow menu (+) — always first/left -->
|
||||
<div class="overflow-wrapper">
|
||||
<button type="button" class="input-icon-btn overflow-plus-btn" id="overflow-plus-btn" title="More tools">
|
||||
<button type="button" class="input-icon-btn overflow-plus-btn" id="overflow-plus-btn" title="More tools" aria-label="More tools" aria-haspopup="true">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
|
||||
<polyline points="6 15 12 9 18 15"/>
|
||||
</svg>
|
||||
@@ -1051,13 +1051,13 @@
|
||||
</div>
|
||||
</div>
|
||||
<!-- Web search (magnifying glass) -->
|
||||
<button type="button" class="input-icon-btn" title="Web search" id="web-toggle-btn" data-mode-tool="true">
|
||||
<button type="button" class="input-icon-btn" title="Web search" id="web-toggle-btn" data-mode-tool="true" aria-label="Web search" aria-pressed="false">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/>
|
||||
</svg>
|
||||
</button>
|
||||
<!-- Shell commands (terminal) -->
|
||||
<button type="button" class="input-icon-btn" title="Shell Access" id="bash-toggle-btn" data-mode-tool="true">
|
||||
<button type="button" class="input-icon-btn" title="Shell Access" id="bash-toggle-btn" data-mode-tool="true" aria-label="Shell access" aria-pressed="false">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<polyline points="4 17 10 11 4 5"/><line x1="12" y1="19" x2="20" y2="19"/>
|
||||
</svg>
|
||||
@@ -1099,8 +1099,8 @@
|
||||
<div class="chat-input-right">
|
||||
<!-- Agent / Chat mode toggle -->
|
||||
<div class="mode-toggle">
|
||||
<button type="button" class="mode-toggle-btn active" id="mode-agent-btn">Agent</button>
|
||||
<button type="button" class="mode-toggle-btn" id="mode-chat-btn">Chat</button>
|
||||
<button type="button" class="mode-toggle-btn active" id="mode-agent-btn" aria-pressed="true">Agent</button>
|
||||
<button type="button" class="mode-toggle-btn" id="mode-chat-btn" aria-pressed="false">Chat</button>
|
||||
</div>
|
||||
<button type="submit" form="chat-form" class="send-btn newchat-mode" data-mode="newchat" aria-label="New chat">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg><span class="send-btn-label">+ New</span>
|
||||
|
||||
@@ -964,6 +964,11 @@ import createResearchSynapse from './researchSynapse.js';
|
||||
return;
|
||||
}
|
||||
|
||||
// Mark the chat log busy while streaming so screen readers wait for the
|
||||
// settled response instead of announcing every token. Cleared in finally.
|
||||
const _chatLog = document.getElementById('chat-history');
|
||||
if (_chatLog) _chatLog.setAttribute('aria-busy', 'true');
|
||||
|
||||
const reader = res.body.getReader();
|
||||
const decoder = new TextDecoder();
|
||||
let buffer = '';
|
||||
@@ -2702,6 +2707,9 @@ import createResearchSynapse from './researchSynapse.js';
|
||||
}
|
||||
} finally {
|
||||
clearProcessingProbe();
|
||||
// Streaming done — let screen readers announce the settled response.
|
||||
const _chatLogDone = document.getElementById('chat-history');
|
||||
if (_chatLogDone) _chatLogDone.setAttribute('aria-busy', 'false');
|
||||
// Always clean up research tracking regardless of background state
|
||||
_researchingStreamIds.delete(streamSessionId);
|
||||
if (_researchingStreamIds.size === 0) {
|
||||
|
||||
Reference in New Issue
Block a user