mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-10 01:09:43 +02:00
fix(tabs): keep the active tab in view when it changes state band on a phone
On phones and 600-767px tablets the header strip stays one horizontally scrolling row. #538 (Tab Layout, default "by state") orders that row in one flex `order` band per state, so when the ACTIVE session changes state (a prompt sent: idle to working; a permission prompt: needs you) its chip moves to another band while scrollLeft stays put, and the tab in use left the screen (measured at 390px: x 165 to -870). #257's reveal rules only covered a CHANGED active tab: _updateActiveTabImmediate reveals on a switch, and _fullRenderSessionTabs restores scrollLeft and re-reveals only when _lastRenderedActiveTabId changed, while a state change is an incremental pass that never reveals at all. _noteActiveTabBand() records the active tab's band (read off the element, so it is what is on screen) and reports when it moved while the tab stayed active. Both render paths reveal on that, in the single scrolling row only (_isScrollingTabRow: not wrapping, not a vertical list). It is keyed on the band, not the raw order value, because another tab entering or leaving the active tab's band shifts that value by one and another tab's move must not yank a strip the user is browsing. _updateActiveTabImmediate records too, so the pass right after a switch still counts (viewing a waiting tab spends its alert and drops it into the idle row). The incremental path reveals after updateTabOverflowMode(), and only from the branch that reached _syncTabTriageChrome(), so a pass that falls through to a full rebuild mid-loop leaves the record for the rebuild to compare. Tests in test/tab-triage.test.ts pin the reveal on both paths and after a switch, and that another tab's band change, a wrapping strip, a vertical list and an active web tab never scroll. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -891,6 +891,8 @@ Tests: `test/terminal-touch-tap.test.ts`.
|
||||
|
||||
**Mobile tab strip scrolling** (issue #257): under 768px the tab strip is a horizontal scroller (desktop wraps to a second row instead), so the active tab can sit off-screen. Three rules keep it reachable and they only work together: `_updateActiveTabImmediate()` scrolls the selected tab into view via `computeTabScrollLeft()` (pure, in constants.js) using **rect math on the strip's own `scrollLeft`**, never `scrollIntoView()`, which would also scroll the document under a fixed header; `_fullRenderSessionTabs()` **restores `scrollLeft`** across the `innerHTML` rebuild, since ambient rebuilds (a task badge appearing, a session created elsewhere) otherwise snap a mid-swipe strip back to 0; and it re-reveals the active tab **only when it changed** (`_lastRenderedActiveTabId`), so browsing the far end of the strip is not undone by background renders.
|
||||
|
||||
⚠️ **Grouped by state, the active tab can move without being switched to.** The strip orders its tabs in one `order` band per state (`CodemanTabTriage`), so the active session changing state (a prompt sent: idle to working; a permission prompt: needs you) slides its chip into another band while `scrollLeft` stays, and the tab in use left the screen (measured at 390px: x 165 to -870). `_noteActiveTabBand()` records the active tab's band (read off the element, also from `_updateActiveTabImmediate()`, so the pass right after a switch counts: viewing a waiting tab spends its alert and drops it to idle) and both render paths reveal it when that band moved, in the single scrolling row only (`_isScrollingTabRow()`). Keyed on the BAND, never the raw order value: another tab entering or leaving the active tab's band shifts that value by one, and another tab's move must never yank a strip the user is browsing. The incremental path reveals after `updateTabOverflowMode()`, and only from the branch that reached `_syncTabTriageChrome()`, so a pass that falls through to a full rebuild mid-loop leaves the record for the rebuild to compare. Tests: `test/tab-triage.test.ts`.
|
||||
|
||||
⚠️ **The ACTIVE tab is the only one with action icons, and on a phone they can eat it**: `.session-tab.active .tab-name` reserves `min-width: 44px` in the phone block (under 600px), because a short session name rendered a 13px label against a 50px gear+close cluster, putting the tab's geometric CENTRE on the gear, so a thumb aiming at the tab opened Session Options instead of switching (measured at 360/393/430px; only long names cleared it).
|
||||
|
||||
⚠️ **The floor is set by the 10th tab onward, not by the tabs you can see**: `.tab-number` renders only for `_tabIdx < 9`, so tab 10 loses 16px + a gap off its left and its centre sits 10px further right. The centre clears the icons when `reserved > icons + rightEdge - leftRunUp - gap` (= 50 + 9 - 17 - 4 = **38px**), hit-testing snaps to whole pixels so 39px still lands on the gear, and the practical floor is 40px — a NUMBERED tab clears it at 20px, which is exactly why reasoning from the tabs on screen would put the centre back on the gear. `test/mobile-tab-tap-zones.test.ts` recomputes that inequality from the stylesheet, so widening the gear or the padding fails there rather than on a phone. The guarantee is centre-off-the-ICONS, not centre-inside-the-label (on a numberless tab it lands in the gap between them, which still switches). Non-active tabs keep their icons hidden and stay tappable end to end.
|
||||
|
||||
Reference in New Issue
Block a user