fix(web): pane tabs activate on pointerdown (focus-follows click-swallow, CI-caught) (#1028)

* fix(web): pane tabs activate on pointerdown — the focus-follows cascade could swallow the click on a peeking master (CI-caught)

The E2E (Playwright) job caught what fast local runs missed: clicking a
peeking master's tab fires pointerdown (focus-follows flips activePane →
peeking-state re-render cascade) and on slow runners the subsequent click
lands after the churn and is swallowed — activeTab never set, the Details
panel never shows, fill times out. Same same-click detach class as
BUG-2281. Activating on pointerdown (click retained for keyboard) sets the
tab in the same tick as the detector, before any re-render can intervene.

Verified: the two CI-failing specs at --repeat-each=3 locally, 45/45.

* fix(web): pointerdown tab activation is mouse-only (touch scroll-start must not switch panels — Codex)
This commit is contained in:
xarmian
2026-07-24 20:03:16 -04:00
committed by GitHub
parent d04b714ccb
commit 059cbcdcf3
@@ -4547,6 +4547,12 @@
}}
>
{#each PANE_TABS as t (t.id)}
<!-- pointerdown + click both activate (idempotent): pointerdown wins
the race against the focus-follows activation cascade — on a
peeking master the pointerdown-triggered re-render can swallow
the subsequent click on slow machines (CI caught it; same
same-click detach class as BUG-2281). click remains for
keyboard (Enter/Space). -->
<button
class="pane-tab"
class:on={activeTab === t.id}
@@ -4556,6 +4562,13 @@
? `pane-panel-feed-${uid}`
: `pane-panel-${t.id}-${uid}`}
tabindex={activeTab === t.id ? 0 : -1}
onpointerdown={(e) => {
// Mouse only: touch pointerdown fires on scroll-start, and a
// drag beginning on a tab must not switch panels (Codex).
// Mobile has no peeking master, so the click-swallow race
// this guards against doesn't exist there.
if (e.pointerType === 'mouse') activeTab = t.id;
}}
onclick={() => (activeTab = t.id)}
>
{t.label}