From 36de8f1fc668d3c81fc31696c621667840c0a006 Mon Sep 17 00:00:00 2001 From: ai-ag2026 <261867348+ai-ag2026@users.noreply.github.com> Date: Thu, 7 May 2026 19:18:21 +0200 Subject: [PATCH] fix: hide workspace file tree cruft by default --- static/i18n.js | 9 ++++ static/index.html | 4 ++ static/style.css | 3 ++ static/ui.js | 41 +++++++++++++++++-- .../test_issue1793_file_tree_cruft_filter.py | 31 ++++++++++++++ 5 files changed, 84 insertions(+), 4 deletions(-) create mode 100644 tests/test_issue1793_file_tree_cruft_filter.py diff --git a/static/i18n.js b/static/i18n.js index 0728de6d..56560be6 100644 --- a/static/i18n.js +++ b/static/i18n.js @@ -311,6 +311,7 @@ const LOCALES = { terminal_error: 'Terminal error', workspace_empty_no_path: 'No workspace selected. Set a workspace in Settings \u2192 Workspace to browse files.', workspace_empty_dir: 'This workspace is empty.', + workspace_show_hidden_files: 'Show hidden files', dialog_confirm_title: 'Confirm action', dialog_prompt_title: 'Enter a value', dialog_confirm_btn: 'Confirm', @@ -1324,6 +1325,7 @@ const LOCALES = { terminal_error: 'ターミナルエラー', workspace_empty_no_path: 'ワークスペースが選択されていません。設定 → ワークスペースで選択してください。', workspace_empty_dir: 'このワークスペースは空です。', + workspace_show_hidden_files: 'Show hidden files', dialog_confirm_title: '操作の確認', dialog_prompt_title: '値を入力', dialog_confirm_btn: '確認', @@ -2236,6 +2238,7 @@ const LOCALES = { settings_autosave_failed: 'Не удалось сохранить', settings_autosave_retry: 'Повторить', workspace_empty_dir: 'Это рабочее пространство пусто.', + workspace_show_hidden_files: 'Show hidden files', workspace_empty_no_path: 'Рабочее пространство не выбрано. Настройте его в Настройки → Рабочее пространство.', available_personalities: 'Доступные личности:', personality_switch_hint: '\n\nИспользуйте `/personality ` для переключения или `/personality none` для сброса.', @@ -3192,6 +3195,7 @@ const LOCALES = { terminal_error: 'Error del terminal', workspace_empty_no_path: 'No hay espacio de trabajo seleccionado. Configure un espacio de trabajo en Ajustes \u2192 Workspace para explorar archivos.', workspace_empty_dir: 'Este espacio de trabajo está vacío.', + workspace_show_hidden_files: 'Show hidden files', // workspace.js unsaved_confirm: 'Tienes cambios sin guardar en la vista previa. ¿Descartar y navegar?', save: 'Guardar', @@ -4120,6 +4124,7 @@ const LOCALES = { terminal_error: 'Terminalfehler', workspace_empty_no_path: 'Kein Workspace ausgewählt. Wähle einen Workspace unter Einstellungen \u2192 Workspace, um Dateien zu durchsuchen.', workspace_empty_dir: 'Dieser Workspace ist leer.', + workspace_show_hidden_files: 'Versteckte Dateien anzeigen', dialog_confirm_title: 'Aktion bestätigen', dialog_prompt_title: 'Wert eingeben', dialog_confirm_btn: 'Bestätigen', @@ -5063,6 +5068,7 @@ const LOCALES = { workspace_empty_no_path: '未选择工作区。请在 设置 → 工作区 中设置工作区以浏览文件。', workspace_empty_dir: '此工作区为空。', + workspace_show_hidden_files: 'Show hidden files', no_personalities: '\u6ca1\u6709\u627e\u5230\u4eba\u8bbe\uff08\u53ef\u6dfb\u52a0\u5230 ~/.hermes/personalities/\uff09', available_personalities: '\u53ef\u7528\u4eba\u8bbe\uff1a', personality_switch_hint: '\n\n\u4f7f\u7528 `/personality ` \u5207\u6362\uff0c\u6216\u7528 `/personality none` \u6e05\u7a7a\u3002', @@ -5965,6 +5971,7 @@ const LOCALES = { workspace_empty_no_path: '未選擇工作區。請在 設定 → 工作區 中設定工作區以瀏覽檔案。', workspace_empty_dir: '此工作區為空。', + workspace_show_hidden_files: 'Show hidden files', no_personalities: '\u6c92\u6709\u627e\u5230\u4eba\u8a2d\uff08\u53ef\u6dfb\u52a0\u5230 ~/.hermes/personalities/\uff09', available_personalities: '\u53ef\u7528\u4eba\u8a2d\uff1a', personality_switch_hint: '\n\n\u4f7f\u7528 `/personality ` \u5207\u63db\uff0c\u6216\u7528 `/personality none` \u6e05\u7a7a\u3002', @@ -7017,6 +7024,7 @@ const LOCALES = { no_workspace: 'Nenhum workspace', workspace_empty_no_path: 'Nenhum workspace selecionado. Configure em Configurações → Workspace.', workspace_empty_dir: 'Este workspace está vazio.', + workspace_show_hidden_files: 'Show hidden files', dialog_confirm_title: 'Confirmar ação', dialog_prompt_title: 'Digite um valor', dialog_confirm_btn: 'Confirmar', @@ -7913,6 +7921,7 @@ const LOCALES = { terminal_error: '터미널 오류', workspace_empty_no_path: 'No workspace selected. Set a workspace in Settings \u2192 Workspace to browse files.', workspace_empty_dir: 'This workspace is empty.', + workspace_show_hidden_files: 'Show hidden files', dialog_confirm_title: 'Confirm action', dialog_prompt_title: 'Enter a value', dialog_confirm_btn: 'Confirm', diff --git a/static/index.html b/static/index.html index ac90bd8b..fd394c0b 100644 --- a/static/index.html +++ b/static/index.html @@ -1131,6 +1131,10 @@ +
diff --git a/static/style.css b/static/style.css index 8958b168..ace8219e 100644 --- a/static/style.css +++ b/static/style.css @@ -1185,6 +1185,9 @@ .close-preview{cursor:pointer;opacity:.6;}.close-preview:hover{opacity:1;} /* Breadcrumb navigation */ .breadcrumb-bar{display:flex;align-items:center;gap:2px;padding:6px 12px;font-size:12px;border-bottom:1px solid var(--border);flex-shrink:0;overflow:hidden;white-space:nowrap;} + .workspace-hidden-toggle{display:flex;align-items:center;gap:7px;padding:6px 12px;border-bottom:1px solid var(--border);color:var(--muted);font-size:11px;line-height:1.2;user-select:none;cursor:pointer;} + .workspace-hidden-toggle:hover{color:var(--text);background:var(--hover-bg);} + .workspace-hidden-toggle input{width:13px;height:13px;margin:0;accent-color:var(--accent-text);} .breadcrumb-seg{padding:1px 3px;border-radius:3px;} .breadcrumb-link{color:var(--muted);cursor:pointer;transition:color .12s;} .breadcrumb-link:hover{color:var(--text);background:var(--hover-bg);} diff --git a/static/ui.js b/static/ui.js index 15b7735e..91845460 100644 --- a/static/ui.js +++ b/static/ui.js @@ -1,4 +1,4 @@ -const S={session:null,messages:[],entries:[],busy:false,pendingFiles:[],toolCalls:[],activeStreamId:null,currentDir:'.',activeProfile:'default'}; +const S={session:null,messages:[],entries:[],busy:false,pendingFiles:[],toolCalls:[],activeStreamId:null,currentDir:'.',activeProfile:'default',showHiddenWorkspaceFiles:false}; const INFLIGHT={}; // keyed by session_id while request in-flight const SESSION_QUEUES={}; // keyed by session_id for queued follow-up turns // Tracks which session's queue to drain in setBusy(false). @@ -6071,6 +6071,38 @@ function renderBreadcrumb(){ } } +const WORKSPACE_HIDDEN_FILE_NAMES=new Set([ + '.DS_Store','._.DS_Store','.AppleDouble','.Spotlight-V100','.Trashes','.fseventsd', + 'Thumbs.db','Desktop.ini','ehthumbs.db','$RECYCLE.BIN', + '.directory','.git','.svn','.hg','node_modules','__pycache__', + '.pytest_cache','.mypy_cache','.ruff_cache','.tox','.venv','venv' +]); +const WORKSPACE_HIDDEN_FILE_PREFIXES=['._','.Trash-']; +function _workspaceShouldHideEntry(item){ + if(!item||S.showHiddenWorkspaceFiles)return false; + const name=String(item.name||''); + if(!name)return false; + if(WORKSPACE_HIDDEN_FILE_NAMES.has(name))return true; + return WORKSPACE_HIDDEN_FILE_PREFIXES.some(prefix=>name.startsWith(prefix)); +} +function _visibleWorkspaceEntries(entries){ + const list=Array.isArray(entries)?entries:[]; + return S.showHiddenWorkspaceFiles?list:list.filter(item=>!_workspaceShouldHideEntry(item)); +} +function _syncWorkspaceHiddenToggle(){ + const el=$('workspaceShowHiddenFiles'); + if(el)el.checked=!!S.showHiddenWorkspaceFiles; +} +function toggleWorkspaceHiddenFiles(value){ + S.showHiddenWorkspaceFiles=!!value; + try{localStorage.setItem('hermes-workspace-show-hidden-files',S.showHiddenWorkspaceFiles?'1':'0');}catch(_){} + _syncWorkspaceHiddenToggle(); + renderFileTree(); +} +try{S.showHiddenWorkspaceFiles=localStorage.getItem('hermes-workspace-show-hidden-files')==='1';}catch(_){} +if(document.readyState==='loading')document.addEventListener('DOMContentLoaded',_syncWorkspaceHiddenToggle); +else _syncWorkspaceHiddenToggle(); + // Track expanded directories for tree view if(!S._expandedDirs) S._expandedDirs=new Set(); // Cache of fetched directory contents: path -> entries[] @@ -6090,11 +6122,12 @@ function renderFileTree(){ } if(emptyEl) emptyEl.style.display='none'; box.style.display=''; - if(!S.entries||!S.entries.length){ + const visibleEntries=_visibleWorkspaceEntries(S.entries); + if(!visibleEntries.length){ if(emptyEl){emptyEl.textContent=t('workspace_empty_dir');emptyEl.style.display='flex';} return; } - _renderTreeItems(box, S.entries, 0); + _renderTreeItems(box, visibleEntries, 0); } function _renderTreeItems(container, entries, depth){ @@ -6239,7 +6272,7 @@ function _renderTreeItems(container, entries, depth){ // Render children if directory is expanded if(item.type==='dir'&&S._expandedDirs.has(item.path)){ - const children=S._dirCache[item.path]||[]; + const children=_visibleWorkspaceEntries(S._dirCache[item.path]||[]); if(children.length){ _renderTreeItems(container, children, depth+1); }else{ diff --git a/tests/test_issue1793_file_tree_cruft_filter.py b/tests/test_issue1793_file_tree_cruft_filter.py new file mode 100644 index 00000000..e9f60cf6 --- /dev/null +++ b/tests/test_issue1793_file_tree_cruft_filter.py @@ -0,0 +1,31 @@ +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[1] +INDEX_HTML = (ROOT / "static" / "index.html").read_text(encoding="utf-8") +UI_JS = (ROOT / "static" / "ui.js").read_text(encoding="utf-8") +I18N_JS = (ROOT / "static" / "i18n.js").read_text(encoding="utf-8") + + +def test_workspace_panel_has_show_hidden_files_toggle(): + """File-tree cruft must be recoverable via an explicit user toggle.""" + assert 'id="workspaceShowHiddenFiles"' in INDEX_HTML + assert "toggleWorkspaceHiddenFiles" in UI_JS + assert "workspace_show_hidden_files" in I18N_JS + + +def test_file_tree_filters_common_cruft_by_default(): + """macOS/Windows/VCS/cache noise should not render by default.""" + assert "WORKSPACE_HIDDEN_FILE_NAMES" in UI_JS + for name in [".DS_Store", "Thumbs.db", "Desktop.ini", ".git", "__pycache__", "node_modules"]: + assert name in UI_JS + assert "_visibleWorkspaceEntries" in UI_JS + assert "S.showHiddenWorkspaceFiles" in UI_JS + assert "_workspaceShouldHideEntry" in UI_JS + + +def test_hidden_file_toggle_invalidates_tree_render_without_refetch(): + """The toggle should re-render cached entries instead of changing workspace state.""" + assert "function toggleWorkspaceHiddenFiles" in UI_JS + assert "renderFileTree()" in UI_JS[UI_JS.index("function toggleWorkspaceHiddenFiles"):] + assert "localStorage.setItem('hermes-workspace-show-hidden-files'" in UI_JS