Skip to content

feat: make thread text selectable, keeping line breaks on copy - #43

Merged
divyanshub024 merged 2 commits into
mainfrom
dv/flow-ui-selectable-text-0cdae8
Sep 3, 2026
Merged

feat: make thread text selectable, keeping line breaks on copy#43
divyanshub024 merged 2 commits into
mainfrom
dv/flow-ui-selectable-text-0cdae8

Conversation

@divyanshub024

@divyanshub024 divyanshub024 commented Sep 1, 2026

Copy link
Copy Markdown
Member

Summary

Text in a FlowThread is selectable, the way a chat in a browser is: drag across turns with a mouse, long-press on touch, copy with the platform's shortcut or menu.

  • FlowThread hosts one SelectionArea across every turn, on by default. selectable: false leaves selection to a host that installs its own area around a larger surface. The area and the per-block containers live in lib/src/utils/flow_selection.dart (internal, not exported).
  • Flutter joins the selected text of separate paragraphs with nothing between them, so each paragraph, code body and turn is a SelectionContainer whose copied text ends in a line break when the selection runs past it. Paragraphs copy on their own lines and turns a blank line apart; the area trims trailing newlines, so a selection drawn to the end never copies a blank line.
  • Chrome stays out of a selection: button labels, the code block's header, the attachment type pill, the settled confirmation chip, the thinking line, and list markers (as in a browser).
  • FlowCodeBlock renders its code as one paragraph that joins a thread's selection, and hosts its own selection area on its own, so a standalone block stays selectable with the platform's handles and menu.
  • The highlight, handles and toolbar take primary (the highlight at 30%), and the composer's caret and highlight now match. An explicit ThemeData.textSelectionTheme wins field by field.
  • Docs pages (markdown, message and thread, code block), the README row, the changelog entry, and the thread demo's snippet are updated.

Reviewer notes:

  • The highlight opacity is a private constant, pending a design frame for selection; the palette gains no token.
  • Select-all covers the turns the lazy list has built; a short thread's shrink-wrap flip clears an active selection; table cells copy newline-joined. All three are stated in the docs.
  • SelectableRegion writes the clipboard itself on copy, as the code block's SelectableText already did.

Screenshots

Selection is state rather than a component, so the before column is the same thread with nothing selected.

Before After, light After, dark Standalone code block

How this was verified

  • Playground embeds ?embed=thread&variant=default and ?embed=code-block&variant=dart in Chrome, light and dark: drags from a user bubble through prose into code, drags starting inside the code text and in the empty space beside it, double-click word selection in prose and code, and the highlight surviving a scroll. The code header and copy button never highlight.
  • Throwaway widget tests (deleted, not committed) checked the exact clipboard text: a blank line between turns, single line breaks between paragraphs and list items with markers dropped, no trailing newline after a drag to the end or after select-all, the standalone block copying its own text, no region under SelectionContainer.disabled or with selectable: false, and the thinking label excluded from select-all.
  • flutter analyze clean for the package and playground/; example/ reports only its pre-existing missing env.g.dart. dart format . applied.
  • Not exercised on a device: touch handles and toolbar. Flutter builds them inside captured inherited themes, so they take the same colours.

Checklist

  • flutter analyze lib and flutter analyze in example/ and playground/ are clean (example/ only lacks its generated env.g.dart, unrelated)
  • dart format . applied
  • Exercised in the playground — with a stage demo added or updated if this is a new component or variant
  • No new entries under dependencies: in pubspec.yaml (Flutter SDK and flutter.dev packages only)
  • Nothing model-facing — no prompts, schemas, or provider/network calls
  • New public API is exported from lib/flow_ui.dart and documented in docs/ and the README table
  • CHANGELOG.md updated for user-facing changes, with breaking changes called out
  • PR title follows conventional commits (feat:, fix:, refactor:, docs:, chore:)

Note

Medium Risk
Touches selection and clipboard semantics across the thread, markdown, code blocks, and composer; regressions could affect copy formatting or interaction with host-wrapped SelectionAreas, but there is no auth or data-handling change.

Overview
FlowThread now hosts built-in text selection (default selectable: true): one FlowSelectionArea spans turns so users can drag or long-press across prose, code, and tables, with highlight/handles/toolbar using theme primary (host textSelectionTheme still wins per field). selectable: false defers to a parent SelectionArea or disables thread selection entirely via SelectionContainer.disabled.

New internal flow_selection.dart wires FlowSelectionBlock delegates so copied text keeps paragraph line breaks and blank lines between turns, while the area trims trailing newlines from the clipboard. FlowCodeBlock drops standalone SelectableText in favor of joining the thread selection or hosting its own area when used alone; header labels and other chrome (buttons, list markers, thinking/shimmer lines, attachment pills) are wrapped in SelectionContainer.disabled.

FlowComposer applies the same flowTextSelectionTheme for caret and highlight. Docs, README, changelog, and the thread demo snippet document the behavior.

Reviewed by Cursor Bugbot for commit b6f6199. Bugbot is set up for automated code reviews on this repo. Configure here.

Flutter joins the selected text of separate paragraphs with nothing
between them, so each paragraph, code body and turn is a selection
container that ends its copied text in a line break when the selection
runs past it, and the thread's area trims the trailing ones. The
selection colours derive from `primary` (the highlight at 30%) behind an
explicit host `textSelectionTheme`; the palette gains no token.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit df6d381. Configure here.

Comment thread lib/src/widgets/flow_code_block.dart
A code block hosts its own selection area whenever no registrar scope
sits above it, which made an opted-out thread with no host area
indistinguishable from a standalone block: every fence and code part
stayed selectable and took long-press. The thread now becomes a disabled
scope in that case, and only in that case, so its content still joins a
host's area when one exists above.
@divyanshub024
divyanshub024 merged commit f5c2240 into main Sep 3, 2026
5 checks passed
@divyanshub024
divyanshub024 deleted the dv/flow-ui-selectable-text-0cdae8 branch September 3, 2026 08:07
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.

1 participant