fix: disable workspace heading affordance without workspace

This commit is contained in:
Frank Song
2026-05-08 13:32:05 +08:00
parent 5005f1c8ba
commit ee0828f53d
6 changed files with 49 additions and 4 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View File

@@ -1118,7 +1118,7 @@
<aside class="rightpanel">
<div class="resize-handle" id="rightpanelResize"></div>
<div class="panel-header">
<span id="workspacePanelHeading" class="workspace-panel-heading" role="button" tabindex="0" title="Workspace root">Workspace</span><span id="workspaceHiddenIndicator" class="workspace-hidden-indicator" data-i18n-title="workspace_hidden_files_visible_title" title="Hidden files are visible — click for options" hidden onclick="toggleWorkspacePrefsMenu(event)"><svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg><span data-i18n="workspace_hidden_files_visible">hidden visible</span></span>
<span id="workspacePanelHeading" class="workspace-panel-heading" title="Workspace">Workspace</span><span id="workspaceHiddenIndicator" class="workspace-hidden-indicator" data-i18n-title="workspace_hidden_files_visible_title" title="Hidden files are visible — click for options" hidden onclick="toggleWorkspacePrefsMenu(event)"><svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg><span data-i18n="workspace_hidden_files_visible">hidden visible</span></span>
<span class="git-badge" id="gitBadge" style="display:none"></span>
<div class="panel-actions">
<button class="panel-icon-btn has-tooltip has-tooltip--bottom" id="btnCollapseWorkspacePanel" data-tooltip="Hide workspace panel" onclick="toggleWorkspacePanel(false)"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="15 18 9 12 15 6"/></svg></button>

View File

@@ -1160,8 +1160,10 @@
@container queries below cut to display:none at hard breakpoints. */
.panel-header{padding:12px 16px;border-bottom:1px solid var(--border);font-size:11px;font-weight:600;color:var(--muted);text-transform:uppercase;letter-spacing:.1em;display:flex;align-items:center;gap:6px;overflow:visible;}
.panel-header > span:first-child{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;min-width:0;flex-shrink:2;}
.workspace-panel-heading{cursor:pointer;border-radius:6px;padding:2px 4px;margin:-2px -4px;}
.workspace-panel-heading:hover,.workspace-panel-heading:focus{color:var(--text);background:var(--hover-bg);outline:none;}
.workspace-panel-heading{cursor:default;border-radius:6px;padding:2px 4px;margin:-2px -4px;}
.workspace-panel-heading.workspace-panel-heading--enabled{cursor:pointer;}
.workspace-panel-heading.workspace-panel-heading--enabled:hover,
.workspace-panel-heading.workspace-panel-heading--enabled:focus-visible{color:var(--text);background:var(--hover-bg);outline:none;}
.git-badge{font-size:9px;font-weight:600;color:var(--muted);background:var(--hover-bg);padding:2px 7px;border-radius:4px;letter-spacing:.02em;white-space:nowrap;font-family:'SF Mono',ui-monospace,monospace;flex-shrink:3;overflow:hidden;min-width:0;}
.topbar-source-badge{display:inline-flex;align-items:center;margin-left:6px;padding:2px 7px;border-radius:999px;background:var(--accent-bg);color:var(--accent-text);font-size:10px;font-weight:700;letter-spacing:.02em;vertical-align:middle;}
.git-badge.dirty{color:var(--accent-text);background:var(--accent-bg);}

View File

@@ -3762,6 +3762,7 @@ function syncTopbar(){
if(!S.session){
document.title=window._botName||'Hermes';
if(typeof syncWorkspaceDisplays==='function') syncWorkspaceDisplays();
if(typeof _syncWorkspaceHeadingState==='function') _syncWorkspaceHeadingState();
if(typeof syncModelChip==='function') syncModelChip();
if(typeof syncTerminalButton==='function') syncTerminalButton();
if(typeof _syncHermesPanelSessionActions==='function') _syncHermesPanelSessionActions();
@@ -3795,6 +3796,7 @@ function syncTopbar(){
}
}
if(typeof syncAppTitlebar==='function') syncAppTitlebar();
if(typeof _syncWorkspaceHeadingState==='function') _syncWorkspaceHeadingState();
// If a profile switch just happened, apply its model rather than the session's stale value.
// S._pendingProfileModel is set by switchToProfile() and cleared here after one application.
const modelOverride=S._pendingProfileModel;
@@ -6234,16 +6236,37 @@ function bindWorkspaceHeadingActions(){
};
heading.onclick=goRoot;
heading.onkeydown=(e)=>{
if(!(S.session&&S.session.workspace)) return;
if(e.key==='Enter'||e.key===' '){
e.preventDefault();
goRoot();
}
};
heading.oncontextmenu=(e)=>{
if(!(S.session&&S.session.workspace)) return;
e.preventDefault();
e.stopPropagation();
if(S.session&&S.session.workspace) _showWorkspaceRootContextMenu(e);
_showWorkspaceRootContextMenu(e);
};
_syncWorkspaceHeadingState();
}
function _syncWorkspaceHeadingState(){
const heading=$('workspacePanelHeading');
if(!heading) return;
const enabled=!!(S.session&&S.session.workspace);
heading.classList.toggle('workspace-panel-heading--enabled',enabled);
if(enabled){
heading.setAttribute('role','button');
heading.setAttribute('tabindex','0');
heading.setAttribute('aria-disabled','false');
heading.title='Workspace root';
} else {
heading.removeAttribute('role');
heading.removeAttribute('tabindex');
heading.setAttribute('aria-disabled','true');
heading.title=t('no_workspace');
}
}
if(document.readyState==='loading') document.addEventListener('DOMContentLoaded',bindWorkspaceHeadingActions);
else bindWorkspaceHeadingActions();

View File

@@ -21,3 +21,23 @@ def test_workspace_heading_context_menu_exposes_root_reveal_and_copy_path():
assert "path:'.'" in UI_JS.replace(" ", "")
assert "copy_file_path" in UI_JS
assert "reveal_in_finder" in UI_JS
def test_workspace_heading_affordance_requires_workspace():
"""The heading should only advertise button behavior when a workspace exists."""
heading_line = next(line for line in INDEX_HTML.splitlines() if 'id="workspacePanelHeading"' in line)
assert 'role="button"' not in heading_line
assert 'tabindex="0"' not in heading_line
assert "_syncWorkspaceHeadingState" in UI_JS
assert "heading.classList.toggle('workspace-panel-heading--enabled',enabled)" in UI_JS
assert "heading.setAttribute('role','button')" in UI_JS
assert "heading.setAttribute('tabindex','0')" in UI_JS
assert "heading.removeAttribute('role')" in UI_JS
assert "heading.removeAttribute('tabindex')" in UI_JS
assert "if(!(S.session&&S.session.workspace)) return;" in UI_JS
assert "typeof _syncWorkspaceHeadingState==='function'" in UI_JS
context_idx = UI_JS.find("heading.oncontextmenu")
guard_idx = UI_JS.find("if(!(S.session&&S.session.workspace)) return;", context_idx)
prevent_idx = UI_JS.find("e.preventDefault()", context_idx)
assert context_idx < guard_idx < prevent_idx