Repository navigation
fix(messages): keep text selection inside the message history - #3505
joshfriend wants to merge 3 commits into
Conversation
A drag that starts in a channel or thread history now stops at that history's edges instead of selecting the sidebar, headers, composer and the other pane. Reactions, reply summaries and Jump to latest are no longer selectable, so copies carry only the conversation, and a byline copies as "Name 2:31 PM" rather than "Name2:31 PM". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The reaction row stays unselectable, but a selection across it now copies each reaction with how many people chose it, e.g. "👍 2, 🎉 1". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
joahg
left a comment
There was a problem hiding this comment.
🤖 Commenting only, not approving: this is a UI behavior change and needs a design or core-team reviewer. No FOUNDATION-marked files are edited.
Needs attention before merge: CI has real failures that look caused by this change.
emoji.spec.mjs("community picker uses keyboard, proxy thumbnails...") fails on chromium and webkit. It still expects the old copied text (Fixture ReaderJan 1, 1970..., no reaction line). It now receives:party: 1andFixture Reader Jan 1, 1970..., so the expectation needs updating.composer-paste.spec.mjs("a triple-clicked heading message pastes into the current line") fails on chromium: copied html is<div data-buzz-copy="timeline">Title</div>instead of...<h2>Title</h2>.... The test comment says a native triple-click range ends past the reaction row. Making that rowuser-select: noneplausibly changes where the browser ends that range, so the heading no longer serializes as a heading and pastes without##. I did not reproduce it locally, so I can't confirm the cause. This is the one finding that is a real copy/paste regression rather than a stale expectation.- Other red jobs (webkit
local-agent-activity,route-teardown) look unrelated, but a rerun would confirm.
Looked fine:
- Listener cleanup is ref-counted and removes
selectionchangetogether withcopy. The clamp is idempotent: aftersetBaseAndExtentthe next event returns atside === 0, so there is no loop. - Editors and selections whose anchor is outside a
[data-message-scroller]are left alone, so composer and sidebar selection still work. user-select: nonedoes not change focus order or what screen readers announce. The reaction buttons, reply summaries and jump button stay focusable and operable, anddata-copy-texton the group is not exposed to assistive tech.- Copying across rows still carries reaction counts, and the unit tests cover partial ranges.
Not reviewed or verified: keyboard selection (Shift+arrows at a history edge, Select All, triple-click on the last message in each engine), Firefox and Windows WebView behavior (the browser test only covers chromium and webkit), and the screenshots, which I could not judge visually.
With the reaction row unselectable, a native triple-click ends inside the block's own text, so a heading copied as bare text. A selection holding all of a block's text now serializes that block. Update the emoji copy expectation for the spaced byline and reaction count line. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
🤖 Addressed in c22ee1e: emoji.spec now expects the spaced byline and the |
joahg
left a comment
There was a problem hiding this comment.
🤖 Commenting only, not approving: this account isn't approving UI changes, and this changes selection and copy behavior, so it needs a design or core-team reviewer.
Fixed since my last review:
- CI: every job on this head is green (browser chromium 9/9 and webkit 18/18, flake checks, measurements,
CI required); only "Windows native validation" is skipped. The earlier webkit failures did not recur. emoji.spec: expectation updated to the spaced byline plus the:party: 1line.- Triple-clicked heading losing its
<h2>: fixed bywholeBlock, which serializes the outermost block whose text the range holds entirely. The cause was the one suspected: with the reaction row unselectable, the native range ends inside the heading's own text.composer-paste.specnow passes. - Shift+arrow at the history edges: the author reports it stays clamped in Chromium and WebKit. I didn't re-test that in a browser.
- Flex-gap byline spacing: the author's reply is right; the space is only added for a nonzero
column-gap.
Still open:
- P3 (new, from
wholeBlock): it applies to any range that covers a block's whole text, not just a triple-click. I ranserializeSelectionbefore and after this commit on a range covering exactly one block's text. A heading went fromTitleto## Title(markdown) and<h2>Title</h2>(html). A one-word list item went fromwordto- word(markdown) and<ul><li>word</li></ul>(html). A paragraph is unchanged. So double-clicking a one-word heading or list item, or dragging exactly over its text, now copies block markup. This may be intended, but it is a visible change that no unit test pins. - Firefox and Windows WebView are still not covered by the browser suite, so the selection clamp isn't verified there.
None of the edited files is marked FOUNDATION.
| /** The outermost block of a row whose text a range holds entirely. A | ||
| * triple-click ends inside a block's own text when only unselectable chrome | ||
| * follows it, and the block still copies as itself, a heading as a heading. */ | ||
| function wholeBlock(element: Element | null, range: Range) { |
There was a problem hiding this comment.
🤖 This also fires when the range simply covers one block's full text, not only for a triple-click. I checked serializeSelection before and after this commit on a range over exactly one block's text. A heading goes from Title to ## Title (markdown), and a one-word <li> from word to - word. A paragraph is unchanged. Double-clicking a one-word heading or list item, or dragging exactly over its text, now carries block markup into the composer. If that isn't wanted, limit it to a range that also ends past the block, which is what a triple-click leaves. Either way, a unit case in selection-copy.test.tsx would pin the intent; this commit adds none.
Dragging a selection in a channel or thread used to run straight out of the message history and pick up the sidebar, channel header, composer and the other pane, and the copy included reaction emoji, reply counts and "Jump to latest". A selection that starts in a message history now stops at that history's edges, the way Slack bounds it to the channel. Reactions, reply summaries and the jump button are no longer selectable; a copy still carries each reaction with its count (
👍 2, 🎉 1) through adata-copy-textattribute on the reaction row, and a byline copies asName 2:31 PMinstead ofName2:31 PM(the copy serializer now reads a flex row's gap as a space).The clamp is a
selectionchangelistener registered alongside the existing timeline copy handler inselection-copy.ts; selections in editors or outside message histories are untouched. Unit tests cover the clamp and the byline spacing, and a new Chromium and WebKit browser case inmessage-selection.spec.mjsdrags from the last message into the sidebar and checks the selection and copied text; it fails on main and passes here.Before (drag from the last message up into the sidebar):
After:
Copied text after: