mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-08 16:39:42 +02:00
feat(web): optional collapsible left session sidebar
The header tab strip stops working past roughly a dozen sessions: it wraps into two or three rows, eats vertical space and still cannot be scanned. This adds a vertical session list in a left <aside> as an ALTERNATIVE layout — a filter box, a live count, and a 44px collapsed rail that keeps the ambient signal (status dot, task badge) visible. The strip is not removed. Settings -> Display -> Tab Bar -> Session List Layout switches between them and the default stays 'header', so existing users see no change until they opt in. Structure: one #sessionTabs element, two mount points. applySessionListLayout() re-parents the SAME node between #sessionTabsHost and #sessionSidebarList, which is why there is no second renderer and no duplicated wiring — app.$() caches getElementById results and never invalidates them, so a moved node keeps every existing consumer (settings-ui, webview-tabs, the generated gesture bundle, the mobile tests) working untouched. Notable integration points: - Below 1024px the sidebar is an off-canvas drawer overlaying the terminal; closed it gets inert + aria-hidden so it cannot be tabbed into, and touch swipes over it no longer switch sessions. - Subagent and ultracode windows anchor to the right edge of a sidebar row instead of its bottom, connector curves follow. - Alt+B toggles; the chord is gated out of the PTY so xterm cannot also write ESC b into a live session. - Collapse state lives in its own localStorage key (the settings blob is rebuilt from DOM controls on every save) and falls back to in-memory intent where storage throws. Verified: frontend syntax + public asset checks, tsc, eslint, 26 new jsdom tests, and a headless-Chromium harness (scripts/verify-session-sidebar.mts) that renders a synthetic 25-session fleet in both layouts at 1600/1000/393px and asserts mount point, widths, inert/aria state and row count. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -168,6 +168,11 @@ const MobileDetection = {
|
||||
resizeTimeout = setTimeout(() => {
|
||||
this.updateBodyClass();
|
||||
this.updateAppHeight();
|
||||
// Whether the session sidebar is a docked column or a modal overlay is
|
||||
// decided at 1024px, so crossing that width has to re-sync the drawer
|
||||
// state — otherwise the `inert`/aria-hidden set on a closed overlay
|
||||
// drawer survives into the docked rail and makes it unclickable.
|
||||
if (typeof app !== 'undefined') app.applySessionListLayout?.();
|
||||
// Tab auto-wrap is width-driven, so it must re-evaluate on resize — the only
|
||||
// other trigger is a tab content render. No-op on mobile/tablet (method bails).
|
||||
if (typeof app !== 'undefined') app.updateTabOverflowMode?.();
|
||||
@@ -581,6 +586,7 @@ const SwipeHandler = {
|
||||
_touchStartHandler: null,
|
||||
_touchEndHandler: null,
|
||||
_element: null,
|
||||
_ignoreGesture: false,
|
||||
|
||||
/** Initialize swipe handling */
|
||||
init() {
|
||||
@@ -609,6 +615,12 @@ const SwipeHandler = {
|
||||
},
|
||||
|
||||
onTouchStart(e) {
|
||||
// The session sidebar is an overlay child of .main, so its touches bubble in
|
||||
// here. Swiping across the open session drawer — the natural "dismiss it"
|
||||
// gesture — would otherwise fire nextSession() and drop the user into a
|
||||
// session they never tapped.
|
||||
this._ignoreGesture = !!e.target?.closest?.('.session-sidebar');
|
||||
if (this._ignoreGesture) return;
|
||||
if (!e.touches || e.touches.length !== 1) return;
|
||||
this.startX = e.touches[0].clientX;
|
||||
this.startY = e.touches[0].clientY;
|
||||
@@ -616,6 +628,10 @@ const SwipeHandler = {
|
||||
},
|
||||
|
||||
onTouchEnd(e) {
|
||||
if (this._ignoreGesture) {
|
||||
this._ignoreGesture = false;
|
||||
return;
|
||||
}
|
||||
if (!e.changedTouches || e.changedTouches.length !== 1) return;
|
||||
|
||||
const endX = e.changedTouches[0].clientX;
|
||||
|
||||
Reference in New Issue
Block a user