fix: add split pane button to tab bar for discoverability (#135)

* fix: add split pane button to tab bar for discoverability

The split pane feature was only accessible via right-click context menu
or Ctrl+\ keyboard shortcut. Add a subtle split icon (⧉) to the tab
bar that appears at low opacity in single-pane mode. Hidden in
multi-pane mode where pane headers already provide split/close controls.

* fix: address review — change tab-bar from tablist to toolbar role

The tab bar contains both tabs and action buttons (new workstream,
split pane), which is invalid for role=tablist. Change to role=toolbar
which correctly describes a container of mixed interactive controls.

* fix: address design review — WCAG contrast, ARIA structure, mobile

- Drop opacity approach, use border: dashed var(--border) matching
  #new-tab-btn pattern (fixes WCAG contrast failure at 35% opacity)
- Nest tabs in #tab-list[role=tablist] inside toolbar (fixes invalid
  role=tab children inside role=toolbar)
- Hide split button on mobile (<600px) where splits can't work
- Add aria-keyshortcuts to both action buttons
This commit is contained in:
Patrick Buckley
2026-03-20 20:01:35 -07:00
committed by GitHub
parent 2f906ea1f9
commit a94051a995
3 changed files with 43 additions and 5 deletions
+16 -2
View File
@@ -1112,6 +1112,19 @@ function updatePaneHeaders() {
} else {
root.classList.remove("multi-pane");
}
// Hide tab-bar split button when already in multi-pane mode
var splitBtn = document.getElementById("split-btn");
if (splitBtn) {
if (leafCount > 1) {
splitBtn.classList.add("hidden");
} else {
splitBtn.classList.remove("hidden");
}
}
}
function splitFocusedPane() {
if (focusedPaneId) splitPane(focusedPaneId, "horizontal");
}
// --- Tree helpers ---
@@ -1860,10 +1873,11 @@ document
// ===========================================================================
var tabBar = document.getElementById("tab-bar");
var tabList = document.getElementById("tab-list");
var newTabBtn = document.getElementById("new-tab-btn");
function renderTabBar() {
tabBar.querySelectorAll(".ws-tab").forEach(function (t) {
tabList.querySelectorAll(".ws-tab").forEach(function (t) {
t.remove();
});
@@ -1910,7 +1924,7 @@ function renderTabBar() {
tab.appendChild(close);
}
tabBar.insertBefore(tab, newTabBtn);
tabList.appendChild(tab);
});
}
+4 -2
View File
@@ -35,8 +35,10 @@
<button id="logout-btn" class="header-btn" onclick="logout()" style="display:none">logout</button>
</div>
<div id="tab-bar" role="tablist">
<button id="new-tab-btn" onclick="newWorkstream()" title="New workstream (Ctrl+T)" aria-label="New workstream">+</button>
<div id="tab-bar" role="toolbar" aria-label="Workstreams">
<div id="tab-list" role="tablist"></div>
<button id="new-tab-btn" onclick="newWorkstream()" title="New workstream (Ctrl+T)" aria-label="New workstream" aria-keyshortcuts="Control+t">+</button>
<button id="split-btn" onclick="splitFocusedPane()" title="Split pane (Ctrl+\)" aria-label="Split pane" aria-keyshortcuts="Control+Backslash">&#x29C9;</button>
</div>
<div id="dashboard" class="dashboard-overlay" role="dialog" aria-modal="true" aria-label="Dashboard">
+23 -1
View File
@@ -112,6 +112,7 @@
#hamburger-btn { width: 40px; height: 40px; }
.hmenu-item { padding: 12px 14px; }
.ws-tab .tab-close { opacity: 1; padding: 4px 6px; font-size: 16px; }
#split-btn { display: none; }
}
/* ==========================================================================
@@ -127,6 +128,11 @@
flex-shrink: 0;
overflow-x: auto;
}
#tab-list {
display: flex;
align-items: center;
gap: 2px;
}
#tab-bar::-webkit-scrollbar { height: 3px; }
#tab-bar::-webkit-scrollbar-thumb { background: var(--border-strong); border-radius: 2px; }
@@ -190,6 +196,22 @@
}
#new-tab-btn:hover { background: var(--bg-highlight); color: var(--accent); border-color: var(--accent); }
#split-btn {
background: none;
border: 1px dashed var(--border);
color: var(--fg-dim);
border-radius: var(--radius-sm) var(--radius-sm) 0 0;
padding: 6px 8px;
cursor: pointer;
font-family: inherit;
font-size: 13px;
line-height: 1;
transition: color 0.15s, border-color 0.15s, background 0.15s;
margin-left: 2px;
}
#split-btn:hover { background: var(--bg-highlight); color: var(--accent); border-color: var(--accent); }
#split-btn.hidden { display: none; }
/* ==========================================================================
Split panes
========================================================================== */
@@ -1259,7 +1281,7 @@ body { position: static; }
.tool-output-stream { animation: none; border-left-color: var(--accent); }
.judge-spinner-dot { animation: none; opacity: 1; }
.thinking-indicator::after { animation: none; content: '...'; }
.ws-tab, .ws-tab .tab-close, #new-tab-btn,
.ws-tab, .ws-tab .tab-close, #new-tab-btn, #split-btn,
.hmenu-item, .dashboard-card,
.approval-btn, .approval-feedback-input,
#plan-buttons button, .pane-input-area button,