Summary
When a side chat streams Markdown in the secondary panel, its follow-to-bottom scrolling dismisses an open text-selection menu in the primary timeline. The unrelated scroll removes both "Add to chat" and "Reply in side chat" before the user can click them. The menu should stay open when a sibling panel scrolls.
Versions and environment
- bb 0.39.0, running from source on
main at fff3ae82fe61578e582ca5cd5b070cd6a80ed438
- macOS 26.3, build 25D125
- Node.js v24.6.0
- Codex provider
- Side chat experiment enabled, side chat rendered as a contained secondary-panel
ThreadChat
Steps to reproduce
- Open a thread and start a side chat in the secondary panel.
- Ask the side chat to produce enough incremental Markdown to keep the panel scrolling, for example:
Write 50 short Markdown paragraphs, one paragraph at a time.
- While the side chat is streaming, select text in an assistant message in the primary timeline.
- Confirm that the selection menu shows "Add to chat" and "Reply in side chat".
- Do not interact with the primary timeline. Wait for the next side-chat chunk to make the secondary panel follow the bottom.
Smallest source-level reproduction:
- Render
TimelineSelectionMenu with a non-null selection and an onDismiss spy.
- Dispatch
scroll from an unrelated sibling element.
- Observe that
onDismiss is called once even though the selection and its panel did not move.
Did not reproduce with a parent rerender alone. Rerendering TimelineSelectionMenu with an equivalent selection does not call onDismiss.
Expected vs actual
Expected:
The primary timeline selection menu remains open when the side-chat panel scrolls. It closes only when the selected timeline or one of its scroll ancestors moves, the selection is cleared, the viewport resizes, or the user dismisses it.
Actual:
The menu disappears as soon as the streaming side chat produces a scroll event. Both actions disappear together.
The focused regression test failed with:
FAIL TimelineSelectionMenu > keeps a valid selection menu open when streaming auto-scrolls the timeline
AssertionError: expected "vi.fn()" to not be called at all, but actually been called 1 times
Number of calls: 1
Evidence
Observed cause:
The reproduction command was:
pnpm exec turbo run test --filter=@bb/app -- --run src/components/thread/timeline/TimelineSelectionMenu.test.tsx
Investigation thread: thr_6kvafkfm3m. The local bb server is not connected to getbb.app, so this thread has no externally reachable URL.
Suggested fix:
Carry the selection's source element in MessageProseSelection, then dismiss on scroll only when the event target is the source element's scroll ancestor. A sibling side-chat scroll should be ignored. Add tests for both cases: sibling scroll keeps the menu open, source-ancestor scroll closes it.
What you ruled out
- No duplicate found in open or closed issues for the action labels, selection-menu scrolling, or side-chat streaming terms.
- A React parent rerender alone does not dismiss the menu.
- This is not specific to the side-chat plugin action. "Add to chat" disappears in the same state transition.
- The focused source-level reproduction fails on
main at fff3ae82fe61578e582ca5cd5b070cd6a80ed438.
Suggested priority and effort
Medium priority, low effort. It interrupts a common selection action during concurrent side-chat work but does not lose data. The workaround is to wait for streaming to finish and select the text again.
AGENT GENERATED
Summary
When a side chat streams Markdown in the secondary panel, its follow-to-bottom scrolling dismisses an open text-selection menu in the primary timeline. The unrelated scroll removes both "Add to chat" and "Reply in side chat" before the user can click them. The menu should stay open when a sibling panel scrolls.
Versions and environment
mainatfff3ae82fe61578e582ca5cd5b070cd6a80ed438ThreadChatSteps to reproduce
Write 50 short Markdown paragraphs, one paragraph at a time.Smallest source-level reproduction:
TimelineSelectionMenuwith a non-null selection and anonDismissspy.scrollfrom an unrelated sibling element.onDismissis called once even though the selection and its panel did not move.Did not reproduce with a parent rerender alone. Rerendering
TimelineSelectionMenuwith an equivalent selection does not callonDismiss.Expected vs actual
Expected:
The primary timeline selection menu remains open when the side-chat panel scrolls. It closes only when the selected timeline or one of its scroll ancestors moves, the selection is cleared, the viewport resizes, or the user dismisses it.
Actual:
The menu disappears as soon as the streaming side chat produces a scroll event. Both actions disappear together.
The focused regression test failed with:
Evidence
Observed cause:
TimelineSelectionMenuinstalls a capture-phase scroll listener onwindow. It callsonDismissfor every scroll event in the document, without checking which panel moved.ThreadChat, which owns a separate scroll container.activeSelection, so the global dismiss removes "Add to chat" and plugin actions together.The reproduction command was:
Investigation thread:
thr_6kvafkfm3m. The local bb server is not connected to getbb.app, so this thread has no externally reachable URL.Suggested fix:
Carry the selection's source element in
MessageProseSelection, then dismiss on scroll only when the event target is the source element's scroll ancestor. A sibling side-chat scroll should be ignored. Add tests for both cases: sibling scroll keeps the menu open, source-ancestor scroll closes it.What you ruled out
mainatfff3ae82fe61578e582ca5cd5b070cd6a80ed438.Suggested priority and effort
Medium priority, low effort. It interrupts a common selection action during concurrent side-chat work but does not lose data. The workaround is to wait for streaming to finish and select the text again.