From 9286eb09ba0ee6a227204cf7afde6741e4dd76d2 Mon Sep 17 00:00:00 2001 From: naps62-yolo Date: Wed, 24 Jun 2026 15:53:23 +0000 Subject: [PATCH] feat(maestro): tighten mobile session UX MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On phones (single tab, one pane at a time): - drop the pane border/padding/focus-ring so the session fills the screen - collapse the redundant tab bar; float a ☰ to keep the sidebar drawer reachable - disable pull-to-refresh (overscroll-behavior) so terminal scroll wins Co-Authored-By: Claude Opus 4.8 (1M context) --- web/src/multiview.ts | 15 +++++++++++++++ web/src/styles.css | 43 +++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 56 insertions(+), 2 deletions(-) diff --git a/web/src/multiview.ts b/web/src/multiview.ts index b8d4de5..522d2ce 100644 --- a/web/src/multiview.ts +++ b/web/src/multiview.ts @@ -344,6 +344,9 @@ export function createMultiView(opts: MultiViewOptions): MultiView { renderTabs(); canvas.replaceChildren(); const tab = activeTab(layout); + // Mobile single-tab: the tab bar carries no information (one tab, one pane), + // so CSS collapses it and we float a ☰ instead (rendered in renderMobile). + el.classList.toggle("mv-mobile-solo", isMobile && layout.tabs.length === 1); if (!tab.root) { pool.setVisible([]); @@ -380,6 +383,18 @@ export function createMultiView(opts: MultiViewOptions): MultiView { const wrap = document.createElement("div"); wrap.className = "mv-mobile"; + // With a single tab the tab bar (and its ☰) is hidden — float a ☰ so the + // sidebar drawer stays reachable. + if (layout.tabs.length === 1) { + const fab = document.createElement("button"); + fab.type = "button"; + fab.className = "mv-mobile-fab"; + fab.title = "Sessions"; + fab.textContent = "☰"; + fab.addEventListener("click", () => opts.onToggleSidebar?.()); + wrap.appendChild(fab); + } + if (ls.length > 1) { const bar = document.createElement("div"); bar.className = "mv-panebar"; diff --git a/web/src/styles.css b/web/src/styles.css index 423cc45..bfd3865 100644 --- a/web/src/styles.css +++ b/web/src/styles.css @@ -183,6 +183,8 @@ html, body { height: 100%; margin: 0; + /* No pull-to-refresh / scroll chaining — terminal scroll wins on touch. */ + overscroll-behavior: none; } body { @@ -1914,6 +1916,11 @@ textarea { display: flex; flex-direction: column; } +/* Mobile, single tab: the tab bar is pure chrome — collapse it (a floating ☰ + inside the pane keeps the sidebar drawer reachable). */ +.mv-mobile-solo > .mv-tabs { + display: none; +} /* tab bar */ .mv-tabs { @@ -2233,14 +2240,46 @@ textarea { .mv-mobile { position: absolute; - inset: var(--sp-2); + /* Flush to the edges — a single full-screen pane needs no canvas inset. */ + inset: 0; display: flex; flex-direction: column; - gap: var(--sp-2); + gap: 0; +} +/* One pane fills the screen: no border, radius, or focus ring to box it in. */ +.mv-mobile > .mv-pane, +.mv-mobile > .mv-pane.is-focused { + border: none; + border-radius: 0; + box-shadow: none; +} +/* Floating ☰ that replaces the collapsed tab bar, tucked into the header bar. */ +.mv-mobile-fab { + position: absolute; + top: 0; + left: 0; + z-index: var(--z-sticky); + display: inline-grid; + place-items: center; + width: 34px; + height: var(--bar-h); + border: none; + background: transparent; + color: var(--ink); + font-size: var(--fs-15); + cursor: pointer; +} +/* Make room for the ☰: shift the title (single pane) or pad the pill strip. */ +.mv-mobile-fab + .mv-pane .attach-title { + margin-left: 30px; +} +.mv-mobile-fab + .mv-panebar { + padding-left: 38px; } .mv-panebar { display: flex; gap: 4px; + padding: var(--sp-1) var(--sp-2); overflow-x: auto; scrollbar-width: none; flex: none;