Let markdown tables and code blocks scroll sideways in mobile messages - #2283
Merged
SawyerHood merged 1 commit intoAug 22, 2026
Merged
Conversation
On the new architecture a ScrollView refuses to take over a touch while one of its ancestors is the JS responder, and a Pressable claims the responder as soon as a touch starts. Timeline messages wrap their markdown in long-press Pressables, so tables under them never scrolled sideways; CodeBlock wrapped its own ScrollView in a copy Pressable and never scrolled anywhere. Move each long-press target inside its ScrollView (a descendant responder is cancelled normally once the drag starts). Tables take the block's quote handler, or the body-level message handler as a fallback, so user and generated messages keep their long-press on tables. Code bodies carry the body padding on the inner Pressable so gutter drags scroll too. Share one LONG_PRESS_DELAY_MS across nested targets. The work-rows showcase ends with a wide table; the phase4a-work-rows e2e flow swipes it, asserts the hidden column appears, and long-presses it. Co-Authored-By: Claude <noreply@anthropic.com>
SawyerHood
deleted the
bb/fix-mobile-table-horizontal-scrolling-thr_4hxgryxmas
branch
August 22, 2026 08:08
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What was wrong
Markdown tables in mobile timeline messages did not scroll sideways, and fenced code blocks never scrolled sideways anywhere. On the new architecture,
RCTScrollViewComponentView._shouldDisableScrollInteractionwalks a ScrollView's ancestors and, if one of them is the JS responder,touchesShouldCancelInContentView:returns NO, so the UIScrollView never takes over the touch. APressableclaims the JS responder as soon as a touch starts. Timeline messages wrap their markdown in long-pressPressables (message actions, quote-this-block), so every table under them was blocked.CodeBlockwrapped its own ScrollView in a copy-on-long-pressPressable, so it was blocked even in the dev showcase.What changed
apps/mobile/src/markdown/MarkdownTable.tsx— the long-press target is aPressableinside the horizontal ScrollView. A descendant responder claims the touch first, and the ScrollView cancels it normally once the drag starts. Takes anonLongPressprop.apps/mobile/src/markdown/render-blocks.tsx—MarkdownBlockbranches onnode.type === "table": plainViewwrapper, handler passed directly. The handler is the block's quote shortcut, or the new body-level fallback when there is none. The comment notes that code blocks copy instead of quoting.apps/mobile/src/markdown/MarkdownContext.tsx,Markdown.tsx— newonLongPress(body-level fallback).AssistantMessageRow,AuthoredUserMessage, andGeneratedMessageRowpass their message-actions handler, so tables in user and generated messages keep their long-press.apps/mobile/src/markdown/CodeBlock.tsx— innerPressablearound the body, with the body padding on it so a drag that starts in a gutter scrolls too. The outerPressablestays for the header.apps/mobile/src/ui/long-press.ts—LONG_PRESS_DELAY_MS(350) shared by all sevendelayLongPresssites and both innerPressables, so nested targets never race each other.apps/mobile/src/screens/dev/work-row-fixtures.ts— the closing assistant fixture in the work-rows showcase carries a table wider than the screen.apps/mobile/e2e/flows/phase4a-work-rows.yaml— swipes that table, asserts the hidden column appears, long-presses a cell, and asserts "Copy text" → "Copied".No wire, CLI, or doc changes.
Not in this PR: moving message-level long-press to
react-native-gesture-handler(which would remove the need for innerPressables altogether) is a larger design change for a follow-up. On Android, the handler-less innerPressableon surfaces that leaveselectableat its default (file previews, skill details) may cancel native text-selection long-press on table cells; not confirmed on a device.How you verified
AssistantMessageRow; a barePressablearound a table reproduced it in isolation. Instrumented frame (368pt) vs. content (858pt) widths confirmed the ScrollView had room to scroll and received no scroll events.phase4a-work-rows.yamlsteps fail before the fix (the hidden column never appears) and pass after.pnpm exec turbo run lint typecheck test --filter=@bb/mobile— all tasks pass; the 19 lint warnings are pre-existing in untouched files.