Skip to content

Fix mobile sidebar swipe over timeline prose - #1026

Merged
ymichael merged 1 commit into
mainfrom
bb/fix-mobile-sidebar-swipe-thr_ikctgwdefe
Aug 5, 2026
Merged

ymichael merged 1 commit into
mainfrom
bb/fix-mobile-sidebar-swipe-thr_ikctgwdefe

Conversation

@ymichael

@ymichael ymichael commented Aug 5, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • allow the compact sidebar swipe gesture to begin over selectable timeline message prose
  • cancel a pending sidebar swipe when native text selection appears within that message
  • preserve existing exclusions for controls and horizontal scroll regions
  • add regression coverage for swipe opening and long-press selection arbitration

Root cause

Selectable message prose was blanket-marked with data-no-sidebar-swipe, so most of the thread timeline was a gesture dead zone. This replaces the blanket opt-out with intent-based selection arbitration.

Validation

  • pnpm exec turbo run test --filter=@bb/app --force (312 files, 2,347 tests)
  • pnpm exec turbo run typecheck --filter=@bb/app
  • mobile dev preview verified healthy through bb Connect

@SawyerHood

Copy link
Copy Markdown
Collaborator

🚨 SLOP COP 🚨 · review

I am the SlopCop. I am reviewing this pull request under the configured security, code quality, performance, architecture, and end-to-end rules.

Stack: #1026 only.

I will post a comment after each review phase. I will not approve this pull request or request changes.

@SawyerHood

Copy link
Copy Markdown
Collaborator

🚨 SLOP COP 🚨 · review

Performance phase complete.

No confirmed performance finding exists. The new global selectionchange listener only reads a reference when no selectable swipe is active. DOM selection and containment checks run only during the relevant gesture. Listener cleanup and retained element release are correct.

Checks: diff inspection, gesture hot-path review, listener lifecycle review, retained-reference review, and git diff --check.

@SawyerHood

Copy link
Copy Markdown
Collaborator

🚨 SLOP COP 🚨 · review

Security phase complete.

No security finding exists. The selection check reads only DOM endpoints. It does not read or transmit selected text. Message HTML still passes through the existing sanitizer, and the document listeners have correct cleanup.

Checks: diff inspection, pointer and touch flow review, sanitization boundary review, listener cleanup review, and unsafe HTML path search.

@SawyerHood

Copy link
Copy Markdown
Collaborator

🚨 SLOP COP 🚨 · review

Code quality and architecture phase complete.

Medium finding: a late selectionchange can cancel an active swipe after the drawer has partial drag styles. clearSwipeSession() removes listeners and clears the session, but it does not close the drawer or clear drag styles. The removed end listener cannot settle the gesture.

The new test covers selection before the first move. It does not cover touchstart, a qualified touchmove, expanded selection, selectionchange, and touchend.

The repository has other selection listeners, but they serve different policies. A shared refactor would not improve this change.

@ymichael
ymichael merged commit c0d9261 into main Aug 5, 2026
10 checks passed
@ymichael
ymichael deleted the bb/fix-mobile-sidebar-swipe-thr_ikctgwdefe branch August 5, 2026 19:02
@SawyerHood

Copy link
Copy Markdown
Collaborator

🚨 SLOP COP 🚨 · review

End-to-end phase complete on the top stack state.

I ran the dev app at SHA 83eec01e1. A mobile-width browser confirmed that a right swipe over selectable prose opens the sidebar.

The browser also confirmed the medium finding. After touchstart, a qualified touchmove, expanded text selection, selectionchange, and touchend, the panel remained present with transform: translate3d(-56.25%, 0px, 0px) and transition: none.

The sidebar therefore stays partly open after the cancellation path.

selectionRoot !== undefined &&
hasExpandedTextSelectionWithin(selectionRoot)
) {
clearSwipeSession();

@SawyerHood SawyerHood Aug 5, 2026 •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚨 slopcop/review — A late text selection can leave the mobile drawer partly open.

A qualified move already opens the drawer and applies drag styles. This call removes the move and end listeners, but it does not close the drawer, settle it, or clear those styles. The browser reproduced the sequence touchstart → qualified touchmove → expanded selection → selectionchange → touchend; the panel stayed at translate3d(-56.25%, 0px, 0px) with transition: none.

Please settle or reset an active drag before removing its end listeners. Add a regression test with this event order and verify the drawer closes and clears all drag styles.

@SawyerHood SawyerHood left a comment •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚨 SLOP COP 🚨 · review

Stack reviewed: #1026 only.

High finding: a late native text selection can leave the mobile sidebar partly open. A qualified swipe applies partial drag styles. The new cancellation path then removes the move and end listeners without closing or settling the drawer. The browser reproduced the full event order and left the panel at translate3d(-56.25%, 0px, 0px) with transition: none.

Please settle or reset an active drag before listener removal. Add a regression test with touchstart, a qualified touchmove, expanded selection, selectionchange, and touchend.

No security or performance finding exists. The repository scan found no useful shared refactor because the other selection listeners serve different policies.

Validation passed:

  • pnpm exec turbo run typecheck --filter=@bb/app
  • pnpm exec turbo run test --filter=@bb/app --force: 312 files and 2,347 tests
  • All applicable GitHub checks
  • Mobile-width dev-browser normal swipe and late-selection scenarios
  • GPT-5.6 synthesis gate

This is a comment-only review under the configured rule.

SawyerHood added a commit that referenced this pull request Aug 10, 2026
…1281)

## Summary

- Defer the sidebar swipe recognizer's horizontal-scroll-region probe
off the tap path. It now runs at most once per gesture, at the first
`touchmove` that shows horizontal intent — never on
`pointerdown`/`touchstart`.
- Keep the probe on the wheel path, but only after the direction test,
so vertical trackpad scrolls stop paying it.
- Add regression tests: the tap path performs zero forced-layout reads,
and a swipe that starts inside a horizontal scroll region still yields
to native scroll.

Fixes #1269

## Root cause

The mobile swipe recognizer listens for `pointerdown` and `touchstart`
on `document` (capture). On every tap it called
`isInsideHorizontalScrollRegion`, which pairs
`getComputedStyle(...).overflowX` with a `scrollWidth`/`clientWidth`
read for each ancestor. Each pair forces a synchronous style + layout
pass of the whole document, and the handler runs for both events, so one
tap paid the flush twice. On a large streamed thread (un-virtualized
timeline, styles dirty almost every frame) that flush blocks a mobile
main thread for seconds — which also freezes the running animations,
exactly as reported.

The path became hot in #1026, which replaced the blanket
`data-no-sidebar-swipe` opt-out on message prose with runtime
arbitration. After that, taps on prose — most of the mobile screen —
fell through to the expensive walk.

## Performance

Measured in Chromium at a 390×844 viewport against a 7.5k-node DOM
inside `SidebarInset`, with styles dirtied before each tap (as a
streamed thread does), 15 trials per variant on the same DOM:

| Variant | Tap dispatch median | Tap dispatch max |
|---|---|---|
| Before | 75.3 ms | 110.7 ms |
| After | ~0 ms | 0.5 ms |

The pre-fix cost equals one full forced style+layout flush (~75–95 ms on
a desktop CPU); phone CPUs are several times slower and real threads
have far more nodes, which produces the multi-second freezes in the
issue.

## Behavior verification

In a real browser at a compact viewport: a right swipe over message
prose still opens the sidebar; a swipe that starts inside a horizontally
scrollable region still does not.

## Testing

- `pnpm exec turbo run typecheck --filter=@bb/app`
- `pnpm exec turbo run test --filter=@bb/app` — 326 files, 2,459 tests
pass, including two new regression tests in `sidebar.test.tsx`.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
amadad pushed a commit to amadad/bb that referenced this pull request Aug 14, 2026
## Summary

- allow the compact sidebar swipe gesture to begin over selectable
timeline message prose
- cancel a pending sidebar swipe when native text selection appears
within that message
- preserve existing exclusions for controls and horizontal scroll
regions
- add regression coverage for swipe opening and long-press selection
arbitration

## Root cause

Selectable message prose was blanket-marked with
`data-no-sidebar-swipe`, so most of the thread timeline was a gesture
dead zone. This replaces the blanket opt-out with intent-based selection
arbitration.

## Validation

- `pnpm exec turbo run test --filter=@bb/app --force` (312 files, 2,347
tests)
- `pnpm exec turbo run typecheck --filter=@bb/app`
- mobile dev preview verified healthy through bb Connect
amadad pushed a commit to amadad/bb that referenced this pull request Aug 14, 2026
…et-bb#1281)

## Summary

- Defer the sidebar swipe recognizer's horizontal-scroll-region probe
off the tap path. It now runs at most once per gesture, at the first
`touchmove` that shows horizontal intent — never on
`pointerdown`/`touchstart`.
- Keep the probe on the wheel path, but only after the direction test,
so vertical trackpad scrolls stop paying it.
- Add regression tests: the tap path performs zero forced-layout reads,
and a swipe that starts inside a horizontal scroll region still yields
to native scroll.

Fixes get-bb#1269

## Root cause

The mobile swipe recognizer listens for `pointerdown` and `touchstart`
on `document` (capture). On every tap it called
`isInsideHorizontalScrollRegion`, which pairs
`getComputedStyle(...).overflowX` with a `scrollWidth`/`clientWidth`
read for each ancestor. Each pair forces a synchronous style + layout
pass of the whole document, and the handler runs for both events, so one
tap paid the flush twice. On a large streamed thread (un-virtualized
timeline, styles dirty almost every frame) that flush blocks a mobile
main thread for seconds — which also freezes the running animations,
exactly as reported.

The path became hot in get-bb#1026, which replaced the blanket
`data-no-sidebar-swipe` opt-out on message prose with runtime
arbitration. After that, taps on prose — most of the mobile screen —
fell through to the expensive walk.

## Performance

Measured in Chromium at a 390×844 viewport against a 7.5k-node DOM
inside `SidebarInset`, with styles dirtied before each tap (as a
streamed thread does), 15 trials per variant on the same DOM:

| Variant | Tap dispatch median | Tap dispatch max |
|---|---|---|
| Before | 75.3 ms | 110.7 ms |
| After | ~0 ms | 0.5 ms |

The pre-fix cost equals one full forced style+layout flush (~75–95 ms on
a desktop CPU); phone CPUs are several times slower and real threads
have far more nodes, which produces the multi-second freezes in the
issue.

## Behavior verification

In a real browser at a compact viewport: a right swipe over message
prose still opens the sidebar; a swipe that starts inside a horizontally
scrollable region still does not.

## Testing

- `pnpm exec turbo run typecheck --filter=@bb/app`
- `pnpm exec turbo run test --filter=@bb/app` — 326 files, 2,459 tests
pass, including two new regression tests in `sidebar.test.tsx`.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants