feat: add workspace panel edge reopen toggle

This commit is contained in:
Michael Lam
2026-05-15 18:32:36 -07:00
parent 352064eb93
commit 4d613e723f
5 changed files with 56 additions and 1 deletions
+2
View File
@@ -4,6 +4,8 @@
### Added
- **PR #2339** by @Michaelyklam (refs #2211) — The workspace panel now has a small desktop edge toggle that remains clickable after the right panel is hidden, making it possible to reopen the workspace browser without returning to Settings. The existing panel close button and composer workspace button remain unchanged; the new affordance only appears when the workspace panel is closed on desktop widths.
- **PR #2332** by @Michaelyklam (refs #2290) — Cron run history/output cards now surface token/cost metadata when the underlying cron output markdown includes it. The backend parses optional model/token/cost/duration frontmatter from cron output files and returns it from `/api/crons/history` and `/api/crons/run`; the Tasks panel renders a compact usage strip beside run rows and below expanded output without affecting older outputs that lack usage metadata.
### Fixed
+8 -1
View File
@@ -82,6 +82,7 @@ function _workspacePanelEls(){
layout: document.querySelector('.layout'),
panel: document.querySelector('.rightpanel'),
toggleBtn: $('btnWorkspacePanelToggle'),
edgeToggleBtn: $('btnWorkspacePanelEdgeToggle'),
collapseBtn: $('btnCollapseWorkspacePanel'),
};
}
@@ -176,7 +177,7 @@ function _setButtonTooltip(btn, text){
}
function syncWorkspacePanelUI(){
const {layout,panel,toggleBtn,collapseBtn}= _workspacePanelEls();
const {layout,panel,toggleBtn,edgeToggleBtn,collapseBtn}= _workspacePanelEls();
if(!layout||!panel)return;
const desktopOpen=_workspacePanelMode!=='closed';
const mobileOpen=panel.classList.contains('mobile-open');
@@ -190,6 +191,12 @@ function syncWorkspacePanelUI(){
_setButtonTooltip(toggleBtn, isOpen?'Hide workspace panel':'Show workspace panel');
toggleBtn.disabled=!canBrowse;
}
if(edgeToggleBtn){
edgeToggleBtn.classList.toggle('active',isOpen);
edgeToggleBtn.setAttribute('aria-expanded',isOpen?'true':'false');
_setButtonTooltip(edgeToggleBtn, isOpen?'Hide workspace panel':'Show workspace panel');
edgeToggleBtn.disabled=!canBrowse;
}
if(collapseBtn){
_setButtonTooltip(collapseBtn, isCompact?'Close workspace panel':'Hide workspace panel');
}
+3
View File
@@ -1178,6 +1178,9 @@
</div>
</div>
</main>
<button class="workspace-panel-edge-toggle has-tooltip has-tooltip--left" id="btnWorkspacePanelEdgeToggle" type="button" onclick="toggleWorkspacePanel(true)" data-tooltip="Show workspace panel" aria-label="Show workspace panel" aria-expanded="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="9 18 15 12 9 6"/></svg>
</button>
<aside class="rightpanel">
<div class="resize-handle" id="rightpanelResize"></div>
<div class="panel-header">
+6
View File
@@ -1329,6 +1329,9 @@
@container rightpanel (max-width: 420px){
.workspace-hidden-indicator{display:none!important;}
}
.workspace-panel-edge-toggle{position:fixed;right:10px;top:50%;transform:translateY(-50%) translateX(12px);z-index:120;width:34px;height:44px;border:1px solid var(--border2);border-radius:999px;background:var(--surface);color:var(--muted);box-shadow:0 10px 28px rgba(0,0,0,.22);display:none;align-items:center;justify-content:center;cursor:pointer;opacity:0;pointer-events:none;transition:opacity .18s ease,transform .18s ease,color .15s ease,background .15s ease,border-color .15s ease;}
.workspace-panel-edge-toggle:hover{color:var(--text);background:var(--hover-bg);border-color:var(--accent);}
.workspace-panel-edge-toggle:disabled{opacity:.3;cursor:not-allowed;transform:translateY(-50%);}
/* Small accent dot on the kebab button when a non-default pref is on */
#btnWorkspacePrefs{position:relative;}
#btnWorkspacePrefs .workspace-prefs-dot{position:absolute;top:3px;right:3px;width:6px;height:6px;border-radius:50%;background:var(--accent-text);box-shadow:0 0 0 1.5px var(--surface);pointer-events:none;}
@@ -1413,6 +1416,8 @@
@media(min-width:901px){
html[data-workspace-panel="closed"] .rightpanel{width:0 !important;opacity:0;transform:translateX(14px);border-left-color:transparent;pointer-events:none;}
.layout.workspace-panel-collapsed .rightpanel{width:0 !important;opacity:0;transform:translateX(14px);border-left-color:transparent;pointer-events:none;}
html[data-workspace-panel="closed"] .workspace-panel-edge-toggle{display:inline-flex;opacity:1;pointer-events:auto;transform:translateY(-50%);}
html[data-workspace-panel="open"] .workspace-panel-edge-toggle{display:none;}
}
/* Sidebar collapse breakpoint matches `_isDesktopWidth()` (min-width:641px) so
@@ -1431,6 +1436,7 @@
}
@media(max-width:900px){
.workspace-panel-edge-toggle{display:none!important;}
.rightpanel{display:none}
.workspace-toggle-btn,.mobile-files-btn{display:inline-flex!important;}
}
@@ -0,0 +1,37 @@
"""Regression coverage for issue #2211 workspace panel reopen affordance."""
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
HTML = (ROOT / "static" / "index.html").read_text(encoding="utf-8")
CSS = (ROOT / "static" / "style.css").read_text(encoding="utf-8")
BOOT_JS = (ROOT / "static" / "boot.js").read_text(encoding="utf-8")
CHANGELOG = (ROOT / "CHANGELOG.md").read_text(encoding="utf-8")
def test_workspace_panel_has_edge_reopen_toggle_outside_hidden_panel():
assert 'id="btnWorkspacePanelEdgeToggle"' in HTML
assert 'class="workspace-panel-edge-toggle' in HTML
assert 'onclick="toggleWorkspacePanel(true)"' in HTML
edge_idx = HTML.index('id="btnWorkspacePanelEdgeToggle"')
aside_idx = HTML.index('<aside class="rightpanel">')
assert edge_idx < aside_idx, "reopen control must remain clickable when .rightpanel is collapsed"
def test_workspace_panel_edge_toggle_only_shows_when_panel_closed_on_desktop():
assert 'html[data-workspace-panel="closed"] .workspace-panel-edge-toggle' in CSS
assert 'html[data-workspace-panel="open"] .workspace-panel-edge-toggle' in CSS
assert '@media(max-width:900px)' in CSS and '.workspace-panel-edge-toggle{display:none!important;}' in CSS
def test_workspace_panel_sync_updates_edge_toggle_state_and_accessibility():
assert "edgeToggleBtn: $('btnWorkspacePanelEdgeToggle')" in BOOT_JS
assert "edgeToggleBtn.classList.toggle('active',isOpen)" in BOOT_JS
assert "edgeToggleBtn.setAttribute('aria-expanded',isOpen?'true':'false')" in BOOT_JS
assert "edgeToggleBtn.disabled=!canBrowse" in BOOT_JS
def test_changelog_mentions_workspace_panel_reopen_affordance():
assert "#2211" in CHANGELOG
assert "workspace panel" in CHANGELOG.lower()
assert "reopen" in CHANGELOG.lower()