From 29da949ee98082064f5c4aa58c542eef7f9bf172 Mon Sep 17 00:00:00 2001 From: nesquena-hermes Date: Sat, 25 Apr 2026 21:06:05 +0000 Subject: [PATCH] feat: 'Keep workspace panel open' toggle in Appearance settings (#999) --- static/boot.js | 11 ++++++++--- static/i18n.js | 12 ++++++++++++ static/index.html | 7 +++++++ static/panels.js | 16 ++++++++++++++++ tests/test_bugbatch_apr2026.py | 8 ++++++-- 5 files changed, 49 insertions(+), 5 deletions(-) diff --git a/static/boot.js b/static/boot.js index 789d0fae..dcda6ecf 100644 --- a/static/boot.js +++ b/static/boot.js @@ -42,6 +42,8 @@ function _setWorkspacePanelMode(mode){ const open=_workspacePanelMode!=='closed'; document.documentElement.dataset.workspacePanel=open?'open':'closed'; // Persist open/closed across refreshes (browse/preview → open; closed → closed) + // Do NOT overwrite the user's "keep open" preference — only track runtime state + // so that toggleWorkspacePanel(false) from the toolbar doesn't clear the setting. localStorage.setItem('hermes-webui-workspace-panel', open ? 'open' : 'closed'); layout.classList.toggle('workspace-panel-collapsed',!open); if(_isCompactWorkspaceViewport()){ @@ -870,9 +872,12 @@ function applyBotName(){ if(saved){ try{ await loadSession(saved); - // Only restore the panel from localStorage when the session actually has a workspace. - // Without this guard, sessions without a workspace snap open then immediately closed. - if(S.session&&S.session.workspace&&localStorage.getItem('hermes-webui-workspace-panel')==='open'){ + // Restore the panel from localStorage when the session has a workspace. + // Preference key takes priority over runtime state so that closing + // the panel via toolbar X doesn't suppress the "keep open" setting. + const panelPref=localStorage.getItem('hermes-webui-workspace-panel-pref')==='open' + || localStorage.getItem('hermes-webui-workspace-panel')==='open'; + if(S.session&&S.session.workspace&&panelPref){ _workspacePanelMode='browse'; } S._bootReady=true; diff --git a/static/i18n.js b/static/i18n.js index ecb16abb..d31f0d85 100644 --- a/static/i18n.js +++ b/static/i18n.js @@ -245,6 +245,8 @@ const LOCALES = { settings_updates_available: '{count} update(s) available', settings_updates_disabled: 'Update checks disabled', settings_update_check_failed: 'Update check failed', + settings_label_workspace_panel_open: 'Keep workspace panel open by default', + settings_desc_workspace_panel_open: 'When enabled, the workspace / file browser panel opens automatically with each new session. You can still close it manually at any time.', settings_dropdown_conversation: 'Conversation', settings_dropdown_appearance: 'Appearance', settings_dropdown_preferences: 'Preferences', @@ -1201,6 +1203,8 @@ const LOCALES = { settings_updates_available: 'Доступно обновлений: {count}', settings_updates_disabled: 'Проверка обновлений отключена', settings_update_check_failed: 'Ошибка проверки обновлений', + settings_label_workspace_panel_open: 'Открывать панель рабочей области по умолчанию', + settings_desc_workspace_panel_open: 'При включении панель файлов будет открываться автоматически в каждой новой сессии.', settings_section_system_title: 'System', settings_tab_appearance: 'Appearance', settings_tab_conversation: 'Conversation', @@ -1756,6 +1760,8 @@ const LOCALES = { settings_updates_available: '{count} actualización(es) disponible(s)', settings_updates_disabled: 'Comprobación de actualizaciones desactivada', settings_update_check_failed: 'Error al comprobar actualizaciones', + settings_label_workspace_panel_open: 'Mantener panel de espacio abierto', + settings_desc_workspace_panel_open: 'Al activar, el panel de archivos se abre automáticamente en cada nueva sesión. Aún puedes cerrarlo manualmente.', settings_section_system_title: 'System', settings_tab_appearance: 'Appearance', settings_tab_conversation: 'Conversation', @@ -2094,6 +2100,8 @@ const LOCALES = { settings_updates_available: '{count} Update(s) verfügbar', settings_updates_disabled: 'Update-Prüfung deaktiviert', settings_update_check_failed: 'Update-Prüfung fehlgeschlagen', + settings_label_workspace_panel_open: 'Arbeitsbereich-Panel standardmäßig öffnen', + settings_desc_workspace_panel_open: 'Wenn aktiviert, wird der Datei-Browser bei jeder neuen Sitzung automatisch geöffnet. Er kann jederzeit manuell geschlossen werden.', settings_section_system_title: 'System', settings_tab_appearance: 'Appearance', settings_tab_conversation: 'Conversation', @@ -2646,6 +2654,8 @@ const LOCALES = { settings_updates_available: '有 {count} 个更新可用', settings_updates_disabled: '更新检查已禁用', settings_update_check_failed: '更新检查失败', + settings_label_workspace_panel_open: '默认保持工作区面板打开', + settings_desc_workspace_panel_open: '启用后,工作区/文件浏览器面板会在每次新会话时自动打开。您仍可随时手动关闭。', settings_section_system_title: 'System', settings_tab_appearance: 'Appearance', settings_tab_conversation: 'Conversation', @@ -2824,6 +2834,8 @@ const LOCALES = { settings_updates_available: '有 {count} 個更新可用', settings_updates_disabled: '更新檢查已禁用', settings_update_check_failed: '更新檢查失敗', + settings_label_workspace_panel_open: '預設保持工作區面板開啓', + settings_desc_workspace_panel_open: '啟用後,工作區/檔案瀏覽器面板會在每次新會話時自動開啓。您仍可隨時手動關閉。', settings_dropdown_conversation: '對話', settings_dropdown_appearance: '外觀', settings_dropdown_preferences: '偏好設定', diff --git a/static/index.html b/static/index.html index a3314570..ac3b674c 100644 --- a/static/index.html +++ b/static/index.html @@ -592,6 +592,13 @@ +
+ +
When enabled, the workspace / file browser panel opens automatically with each new session. You can still close it manually at any time.
+
diff --git a/static/panels.js b/static/panels.js index b948bb94..cafa073a 100644 --- a/static/panels.js +++ b/static/panels.js @@ -2246,6 +2246,22 @@ async function loadSettingsPanel(){ const fontSizeSel=$('settingsFontSize'); if(fontSizeSel) fontSizeSel.value=fontSizeVal; if(typeof _syncFontSizePicker==='function') _syncFontSizePicker(fontSizeVal); + // Workspace panel default-open toggle (localStorage-backed) + // Uses a separate key (hermes-webui-workspace-panel-pref) so that + // closing the panel via toolbar X does not clear the user's preference. + const wsPanelCb=$('settingsWorkspacePanelOpen'); + if(wsPanelCb){ + wsPanelCb.checked=localStorage.getItem('hermes-webui-workspace-panel-pref')==='open'; + wsPanelCb.onchange=function(){ + const open=this.checked; + localStorage.setItem('hermes-webui-workspace-panel-pref',open?'open':'closed'); + // Also sync the runtime key so the current session reflects the change + localStorage.setItem('hermes-webui-workspace-panel',open?'open':'closed'); + document.documentElement.dataset.workspacePanel=open?'open':'closed'; + if(open&&_workspacePanelMode==='closed') openWorkspacePanel('browse'); + else if(!open&&_workspacePanelMode!=='closed') toggleWorkspacePanel(false); + }; + } const resolvedLanguage=(typeof resolvePreferredLocale==='function') ? resolvePreferredLocale(settings.language, localStorage.getItem('hermes-lang')) : (settings.language || localStorage.getItem('hermes-lang') || 'en'); diff --git a/tests/test_bugbatch_apr2026.py b/tests/test_bugbatch_apr2026.py index c470d05a..a58c9b2e 100644 --- a/tests/test_bugbatch_apr2026.py +++ b/tests/test_bugbatch_apr2026.py @@ -104,16 +104,20 @@ def test_user_bubble_selection_is_scoped_to_user_message_body(): def test_576_panel_restore_gated_on_workspace(): """boot.js: localStorage panel restore must be gated on session.workspace.""" # The guard must appear: session.workspace check before _workspacePanelMode='browse' - assert "S.session&&S.session.workspace&&localStorage.getItem('hermes-webui-workspace-panel')" in BOOT_JS, ( + # Panel pref key takes priority over runtime key (toolbar close must not clear preference) + assert "S.session&&S.session.workspace&&panelPref" in BOOT_JS, ( "Workspace panel localStorage restore must be gated on S.session.workspace " "to prevent snap-open-then-closed on sessions without a workspace (#576)" ) + assert "'hermes-webui-workspace-panel-pref'" in BOOT_JS, ( + "Panel restore must check the preference key so toolbar close does not clear it" + ) def test_576_restore_happens_after_load_session(): """boot.js: loadSession() must come before the panel restore guard.""" load_pos = BOOT_JS.find("await loadSession(saved)") - restore_pos = BOOT_JS.find("S.session&&S.session.workspace&&localStorage") + restore_pos = BOOT_JS.find("panelPref") assert load_pos != -1, "loadSession call not found in boot.js" assert restore_pos != -1, "workspace panel restore guard not found" assert load_pos < restore_pos, (