Improve workspace switcher screen reader state
This commit is contained in:
1 parent
d6e6e91de4
commit
23de53c004
4 files changed
+91
-8
No files matched your search
@@ -214,6 +214,7 @@ const LOCALES = {
|
||||
model_group_configured: 'Configured',
|
||||
ws_search_placeholder: 'Search workspaces…',
|
||||
ws_no_results: 'No workspaces found',
|
||||
workspace_switcher_aria: 'Switch workspace. Current workspace: {0}.',
|
||||
workspace_new_worktree_conversation: 'New conversation in worktree',
|
||||
workspace_new_worktree_conversation_meta: 'Create an isolated git worktree for this workspace.',
|
||||
workspace_worktree_created: 'Worktree conversation created',
|
||||
@@ -1913,6 +1914,7 @@ const LOCALES = {
|
||||
model_group_configured: 'Configurato',
|
||||
ws_search_placeholder: 'Cerca workspace…',
|
||||
ws_no_results: 'Nessun workspace trovato',
|
||||
workspace_switcher_aria: 'Cambia workspace. Workspace corrente: {0}.',
|
||||
workspace_new_worktree_conversation: 'Nuova conversazione in worktree',
|
||||
workspace_new_worktree_conversation_meta: 'Crea un worktree git isolato per questo workspace.',
|
||||
workspace_worktree_created: 'Conversazione worktree creata',
|
||||
@@ -3603,6 +3605,7 @@ const LOCALES = {
|
||||
model_group_configured: '設定済み',
|
||||
ws_search_placeholder: 'ワークスペースを検索…',
|
||||
ws_no_results: 'ワークスペースが見つかりません',
|
||||
workspace_switcher_aria: 'ワークスペースを切り替えます。現在のワークスペース: {0}。',
|
||||
workspace_new_worktree_conversation: 'worktree で新しい会話',
|
||||
workspace_new_worktree_conversation_meta: 'このワークスペース用に隔離された git worktree を作成します。',
|
||||
workspace_worktree_created: 'worktree 会話を作成しました',
|
||||
@@ -5387,6 +5390,7 @@ const LOCALES = {
|
||||
model_group_configured: 'Настроенные',
|
||||
ws_search_placeholder: 'Поиск рабочих пространств…',
|
||||
ws_no_results: 'Рабочие пространства не найдены',
|
||||
workspace_switcher_aria: 'Переключить рабочее пространство. Текущее рабочее пространство: {0}.',
|
||||
workspace_new_worktree_conversation: 'Новый разговор в worktree',
|
||||
workspace_new_worktree_conversation_meta: 'Создать изолированный git worktree для этого рабочего пространства.',
|
||||
workspace_worktree_created: 'Разговор в worktree создан',
|
||||
@@ -6904,6 +6908,7 @@ const LOCALES = {
|
||||
model_group_configured: 'Configurados',
|
||||
ws_search_placeholder: 'Buscar espacios de trabajo…',
|
||||
ws_no_results: 'No se encontraron espacios de trabajo',
|
||||
workspace_switcher_aria: 'Cambiar espacio de trabajo. Espacio de trabajo actual: {0}.',
|
||||
workspace_new_worktree_conversation: 'Nueva conversación en worktree',
|
||||
workspace_new_worktree_conversation_meta: 'Crear un git worktree aislado para este espacio de trabajo.',
|
||||
workspace_worktree_created: 'Conversación en worktree creada',
|
||||
@@ -9525,6 +9530,7 @@ const LOCALES = {
|
||||
model_group_configured: 'Konfiguriert',
|
||||
ws_search_placeholder: 'Arbeitsbereiche suchen…',
|
||||
ws_no_results: 'Keine Arbeitsbereiche gefunden',
|
||||
workspace_switcher_aria: 'Workspace wechseln. Aktueller Workspace: {0}.',
|
||||
workspace_new_worktree_conversation: 'Neue Unterhaltung in Worktree',
|
||||
workspace_new_worktree_conversation_meta: 'Erstellt einen isolierten git worktree für diesen Arbeitsbereich.',
|
||||
workspace_worktree_created: 'Worktree-Unterhaltung erstellt',
|
||||
@@ -10160,6 +10166,7 @@ const LOCALES = {
|
||||
model_group_configured: '已配置',
|
||||
ws_search_placeholder: '搜索工作区…',
|
||||
ws_no_results: '未找到工作区',
|
||||
workspace_switcher_aria: '切换工作区。当前工作区:{0}。',
|
||||
workspace_new_worktree_conversation: '在 worktree 中新建对话',
|
||||
workspace_new_worktree_conversation_meta: '为此工作区创建隔离的 git worktree。',
|
||||
workspace_worktree_created: '已创建 worktree 对话',
|
||||
@@ -11810,6 +11817,7 @@ const LOCALES = {
|
||||
model_group_configured: '已設定',
|
||||
ws_search_placeholder: '搜尋工作區…',
|
||||
ws_no_results: '找不到工作區',
|
||||
workspace_switcher_aria: '切換工作區。目前工作區:{0}。',
|
||||
workspace_new_worktree_conversation: '在 worktree 中新增對話',
|
||||
workspace_new_worktree_conversation_meta: '為此工作區建立隔離的 git worktree。',
|
||||
workspace_worktree_created: '已建立 worktree 對話',
|
||||
@@ -13436,6 +13444,7 @@ const LOCALES = {
|
||||
model_group_configured: 'Configurados',
|
||||
ws_search_placeholder: 'Buscar espaços de trabalho…',
|
||||
ws_no_results: 'Nenhum espaço de trabalho encontrado',
|
||||
workspace_switcher_aria: 'Alternar espaço de trabalho. Espaço de trabalho atual: {0}.',
|
||||
workspace_new_worktree_conversation: 'Nova conversa em worktree',
|
||||
workspace_new_worktree_conversation_meta: 'Cria um git worktree isolado para este espaço de trabalho.',
|
||||
workspace_worktree_created: 'Conversa em worktree criada',
|
||||
@@ -15002,6 +15011,7 @@ const LOCALES = {
|
||||
model_group_configured: '구성됨',
|
||||
ws_search_placeholder: '워크스페이스 검색…',
|
||||
ws_no_results: '워크스페이스를 찾을 수 없습니다',
|
||||
workspace_switcher_aria: '워크스페이스 전환. 현재 워크스페이스: {0}.',
|
||||
workspace_new_worktree_conversation: 'worktree에서 새 대화',
|
||||
workspace_new_worktree_conversation_meta: '이 워크스페이스용 격리된 git worktree를 만듭니다.',
|
||||
workspace_worktree_created: 'worktree 대화가 생성되었습니다',
|
||||
@@ -16685,6 +16695,7 @@ const LOCALES = {
|
||||
model_group_configured: 'Configuré',
|
||||
ws_search_placeholder: 'Rechercher des espaces de travail\u2026',
|
||||
ws_no_results: 'Aucun espace de travail trouvé',
|
||||
workspace_switcher_aria: 'Changer d’espace de travail. Espace de travail actuel : {0}.',
|
||||
workspace_new_worktree_conversation: 'Nouvelle conversation dans un worktree',
|
||||
workspace_new_worktree_conversation_meta: 'Créer un worktree git isolé pour cet espace de travail.',
|
||||
workspace_worktree_created: 'Conversation Worktree créée',
|
||||
@@ -19787,6 +19798,7 @@ const LOCALES = {
|
||||
workspace_worktree_created: 'Worktree konverzace vytvořena',
|
||||
workspace_worktree_failed: 'Vytvoření worktree selhalo: ',
|
||||
ws_no_results: 'Nenalezeny žádné pracovní prostory',
|
||||
workspace_switcher_aria: 'Přepnout pracovní prostor. Aktuální pracovní prostor: {0}.',
|
||||
ws_search_placeholder: 'Hledat pracovní prostory…',
|
||||
you: 'Vy',
|
||||
// tool/worklog references
|
||||
@@ -20013,6 +20025,7 @@ const LOCALES = {
|
||||
model_group_configured: 'Yapılandırılmış',
|
||||
ws_search_placeholder: 'Çalışma alanlarını arayın\u2026',
|
||||
ws_no_results: 'Çalışma alanı bulunamadı',
|
||||
workspace_switcher_aria: 'Çalışma alanını değiştir. Geçerli çalışma alanı: {0}.',
|
||||
workspace_new_worktree_conversation: 'Worktree\'de yeni konuşma',
|
||||
workspace_new_worktree_conversation_meta: 'Bu çalışma alanı için yalıtılmış bir git çalışma ağacı oluşturun.',
|
||||
workspace_worktree_created: 'Worktree görüşmesi oluşturuldu',
|
||||
@@ -21689,6 +21702,7 @@ const LOCALES = {
|
||||
model_group_configured: 'Skonfigurowane',
|
||||
ws_search_placeholder: 'Szukaj obszarów roboczych…',
|
||||
ws_no_results: 'Nie znaleziono żadnych obszarów roboczych',
|
||||
workspace_switcher_aria: 'Przełącz obszar roboczy. Bieżący obszar roboczy: {0}.',
|
||||
workspace_new_worktree_conversation: 'Nowa konwersacja w worktree',
|
||||
workspace_new_worktree_conversation_meta: 'Utwórz izolowane git worktree dla tego obszaru roboczego.',
|
||||
workspace_worktree_created: 'Utworzono konwersację w worktree',
|
||||
@@ -23351,6 +23365,7 @@ const LOCALES = {
|
||||
model_group_configured: 'Đã cấu hình',
|
||||
ws_search_placeholder: 'Tìm workspace…',
|
||||
ws_no_results: 'Không tìm thấy workspace nào',
|
||||
workspace_switcher_aria: 'Chuyển workspace. Workspace hiện tại: {0}.',
|
||||
workspace_new_worktree_conversation: 'Cuộc trò chuyện mới trong worktree',
|
||||
workspace_new_worktree_conversation_meta: 'Tạo git worktree độc lập cho workspace này.',
|
||||
workspace_worktree_created: 'Đã tạo cuộc trò chuyện worktree',
|
||||
|
||||
+2
-2
@@ -636,7 +636,7 @@
|
||||
<button class="composer-workspace-files-btn" id="btnWorkspacePanelToggle" type="button" onclick="toggleWorkspacePanel()" title="Show workspace panel" aria-pressed="false" aria-label="Toggle workspace files panel">
|
||||
<span class="composer-workspace-icon" aria-hidden="true"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/></svg></span>
|
||||
</button>
|
||||
<button class="composer-workspace-chip" id="composerWorkspaceChip" type="button" onclick="toggleComposerWsDropdown()" title="Switch workspace" disabled>
|
||||
<button class="composer-workspace-chip" id="composerWorkspaceChip" type="button" onclick="toggleComposerWsDropdown()" title="Switch workspace" aria-label="Switch workspace" aria-haspopup="true" aria-expanded="false" aria-controls="composerWsDropdown" disabled>
|
||||
<span class="composer-workspace-label" id="composerWorkspaceLabel"></span>
|
||||
<span class="composer-workspace-chevron" aria-hidden="true"><svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg></span>
|
||||
</button>
|
||||
@@ -722,7 +722,7 @@
|
||||
</button>
|
||||
</div>
|
||||
<div class="composer-mobile-config-panel" id="composerMobileConfigPanel" aria-label="Workspace, model, quota, reasoning, and context settings">
|
||||
<button class="composer-mobile-config-action" id="composerMobileWorkspaceAction" type="button" onclick="toggleComposerWsDropdown()" title="Switch workspace">
|
||||
<button class="composer-mobile-config-action" id="composerMobileWorkspaceAction" type="button" onclick="toggleComposerWsDropdown()" title="Switch workspace" aria-label="Switch workspace" aria-haspopup="true" aria-expanded="false" aria-controls="composerWsDropdown">
|
||||
<span class="composer-workspace-icon" aria-hidden="true"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/></svg></span>
|
||||
<span class="composer-mobile-config-copy"><span class="composer-mobile-config-kicker" data-i18n="composer_mobile_workspace">Workspace</span><span class="composer-mobile-config-value" id="composerMobileWorkspaceLabel"></span></span>
|
||||
</button>
|
||||
|
||||
+23
-6
@@ -5653,14 +5653,19 @@ function syncWorkspaceDisplays(){
|
||||
// flash of "No workspace" before the saved session finishes loading.
|
||||
if(composerLabel) composerLabel.textContent=S._bootReady?label:'';
|
||||
if(mobileLabel) mobileLabel.textContent=S._bootReady?label:'';
|
||||
const composerExpanded=!!(composerDropdown&&composerDropdown.classList.contains('open'));
|
||||
if(composerChip){
|
||||
composerChip.disabled=!hasWorkspace;
|
||||
composerChip.title=hasWorkspace?ws:t('no_workspace');
|
||||
composerChip.classList.toggle('active',!!(composerDropdown&&composerDropdown.classList.contains('open')));
|
||||
composerChip.setAttribute('aria-label',hasWorkspace?t('workspace_switcher_aria',label):t('no_workspace'));
|
||||
composerChip.setAttribute('aria-expanded',composerExpanded?'true':'false');
|
||||
composerChip.classList.toggle('active',composerExpanded);
|
||||
}
|
||||
if(mobileAction){
|
||||
mobileAction.title=hasWorkspace?ws:t('no_workspace');
|
||||
mobileAction.classList.toggle('active',!!(composerDropdown&&composerDropdown.classList.contains('open')));
|
||||
mobileAction.setAttribute('aria-label',hasWorkspace?t('workspace_switcher_aria',label):t('no_workspace'));
|
||||
mobileAction.setAttribute('aria-expanded',composerExpanded?'true':'false');
|
||||
mobileAction.classList.toggle('active',composerExpanded);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5850,8 +5855,14 @@ function toggleComposerWsDropdown(){
|
||||
renderWorkspaceDropdownInto(dd, data.workspaces, S.session?.workspace||S._profileDefaultWorkspace||data.last||'');
|
||||
dd.classList.add('open');
|
||||
_positionComposerWsDropdown();
|
||||
if(chip) chip.classList.add('active');
|
||||
if(mobileAction) mobileAction.classList.add('active');
|
||||
if(chip){
|
||||
chip.classList.add('active');
|
||||
chip.setAttribute('aria-expanded','true');
|
||||
}
|
||||
if(mobileAction){
|
||||
mobileAction.classList.add('active');
|
||||
mobileAction.setAttribute('aria-expanded','true');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -5863,8 +5874,14 @@ function closeWsDropdown(){
|
||||
const mobileAction=$('composerMobileWorkspaceAction');
|
||||
if(dd)dd.classList.remove('open');
|
||||
if(composerDd)composerDd.classList.remove('open');
|
||||
if(composerChip)composerChip.classList.remove('active');
|
||||
if(mobileAction)mobileAction.classList.remove('active');
|
||||
if(composerChip){
|
||||
composerChip.classList.remove('active');
|
||||
composerChip.setAttribute('aria-expanded','false');
|
||||
}
|
||||
if(mobileAction){
|
||||
mobileAction.classList.remove('active');
|
||||
mobileAction.setAttribute('aria-expanded','false');
|
||||
}
|
||||
}
|
||||
document.addEventListener('click',e=>{
|
||||
if(
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
"""Issue #5671: workspace switcher and New Chat workspace announcements."""
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
INDEX_HTML = (ROOT / "static" / "index.html").read_text(encoding="utf-8")
|
||||
PANELS_JS = (ROOT / "static" / "panels.js").read_text(encoding="utf-8")
|
||||
SESSIONS_JS = (ROOT / "static" / "sessions.js").read_text(encoding="utf-8")
|
||||
STYLE_CSS = (ROOT / "static" / "style.css").read_text(encoding="utf-8")
|
||||
I18N_JS = (ROOT / "static" / "i18n.js").read_text(encoding="utf-8")
|
||||
|
||||
|
||||
def _block(source: str, start_marker: str, end_marker: str) -> str:
|
||||
start = source.find(start_marker)
|
||||
assert start != -1, f"{start_marker!r} not found"
|
||||
end = source.find(end_marker, start)
|
||||
assert end != -1, f"{end_marker!r} not found after {start_marker!r}"
|
||||
return source[start:end]
|
||||
|
||||
|
||||
def test_composer_workspace_switchers_expose_action_and_popup_state():
|
||||
assert 'id="composerWorkspaceChip"' in INDEX_HTML
|
||||
assert 'id="composerMobileWorkspaceAction"' in INDEX_HTML
|
||||
assert 'aria-label="Switch workspace"' in INDEX_HTML
|
||||
assert 'aria-haspopup="true"' in INDEX_HTML
|
||||
assert 'aria-expanded="false"' in INDEX_HTML
|
||||
assert 'aria-controls="composerWsDropdown"' in INDEX_HTML
|
||||
assert INDEX_HTML.count('aria-controls="composerWsDropdown"') == 2
|
||||
|
||||
sync = _block(PANELS_JS, "function syncWorkspaceDisplays", "async function loadWorkspaceList")
|
||||
assert "const composerExpanded=!!(composerDropdown&&composerDropdown.classList.contains('open'))" in sync
|
||||
assert "composerChip.setAttribute('aria-label',hasWorkspace?t('workspace_switcher_aria',label):t('no_workspace'))" in sync
|
||||
assert "composerChip.setAttribute('aria-expanded',composerExpanded?'true':'false')" in sync
|
||||
assert "composerChip.classList.toggle('active',composerExpanded)" in sync
|
||||
assert "mobileAction.setAttribute('aria-label',hasWorkspace?t('workspace_switcher_aria',label):t('no_workspace'))" in sync
|
||||
assert "mobileAction.setAttribute('aria-expanded',composerExpanded?'true':'false')" in sync
|
||||
assert "mobileAction.classList.toggle('active',composerExpanded)" in sync
|
||||
|
||||
|
||||
def test_composer_workspace_dropdown_keeps_aria_expanded_in_sync():
|
||||
toggle = _block(PANELS_JS, "function toggleComposerWsDropdown", "function closeWsDropdown")
|
||||
close = _block(PANELS_JS, "function closeWsDropdown", "document.addEventListener('click'")
|
||||
|
||||
assert "chip.setAttribute('aria-expanded','true')" in toggle
|
||||
assert "mobileAction.setAttribute('aria-expanded','true')" in toggle
|
||||
assert "composerChip.setAttribute('aria-expanded','false')" in close
|
||||
assert "mobileAction.setAttribute('aria-expanded','false')" in close
|
||||
|
||||
|
||||
def test_workspace_switcher_i18n_key_exists_in_english_locale():
|
||||
assert "workspace_switcher_aria: 'Switch workspace. Current workspace: {0}.'" in I18N_JS
|
||||
Reference in new issue
Block a user