feat: 'Keep workspace panel open' toggle in Appearance settings (#999)

This commit is contained in:
nesquena-hermes
2026-04-25 21:06:05 +00:00
parent 2b6a3a7e1d
commit 29da949ee9
5 changed files with 49 additions and 5 deletions
+8 -3
View File
@@ -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;
+12
View File
@@ -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: '偏好設定',
+7
View File
@@ -592,6 +592,13 @@
</div>
<input type="hidden" id="settingsFontSize" value="default">
</div>
<div class="settings-field">
<label style="display:flex;align-items:center;gap:8px;cursor:pointer">
<input type="checkbox" id="settingsWorkspacePanelOpen" style="width:15px;height:15px;accent-color:var(--accent)">
<span data-i18n="settings_label_workspace_panel_open">Keep workspace panel open by default</span>
</label>
<div style="font-size:11px;color:var(--muted);margin-top:4px" data-i18n="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.</div>
</div>
<button class="sm-btn" onclick="saveSettings()" style="margin-top:12px;width:100%;padding:8px;font-weight:600" data-i18n="settings_save_btn">Save Settings</button>
</div>
<div class="settings-pane" id="settingsPanePreferences">
+16
View File
@@ -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');
+6 -2
View File
@@ -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, (