feat(timeline): select message text and copy just the part you picked - #290
Open
tyreseluo wants to merge 2 commits into
Open
feat(timeline): select message text and copy just the part you picked#290tyreseluo wants to merge 2 commits into
tyreseluo wants to merge 2 commits into
Conversation
Copying a message was all-or-nothing: you could take the whole body from the context menu, but you could not drag across a sentence and copy that. Makepad already ships the machinery — `TextFlow.selectable` plus `PortalList.selectable`, which owns a cross-item anchor/cursor, auto-scrolls at the viewport edge and answers `Hit::TextCopy`. The timeline just never opted in, and two things stood in the way once it did. **The plaintext body was a `Label`.** `Label` has no selection support at all, so the most common kind of message was the one you could not select. Bodies without links used it; bodies with links already went through the HTML renderer, because `linkify_get_urls` escapes what it rewrites. Both now take the HTML path, which is a `TextFlow` and therefore selectable. **Everything claimed to be a click target.** `Widget::is_interactive` defaults to `true`, and a selectable `PortalList` declines to begin a drag over anything interactive, on the grounds that the press belongs to that widget. `HtmlOrPlaintext` inherited that default, and so does makepad's `Html` (unlike `Markdown` and `TextFlow`, which both return `false`). So a drag on message text became a drag-to-scroll and no selection was ever created — the timeline slid under the pointer. `HtmlOrPlaintext` now delegates to its inner `View`; the `Html` half is fixed upstream in ZhangHanDong/makepad#5, and until that merges `scripts/patch-makepad-html-selectable.sh` applies it locally. The invariant this rests on is that at most one body per item reports text. `View` resolves a selection by taking the first child that answers and does not skip hidden ones, and `TextFlow` clears its `SelectionTracker` only in `begin()`, which a hidden widget never reaches — so a merely-hidden body keeps answering hit tests, and copy, with what it last held. Idle bodies are therefore drawn EMPTY rather than hidden (`clear_body`), and the plain body is declared before the bot card so a normal message wins outright. Also here: * Agent replies select too — the bot card's three renderers opt in. * Right-click still opens the context menu. A selectable `PortalList` only forwards `MouseDown` to items over an interactive child, which would have silently dropped it; the secondary button is re-dispatched to the items, where `Message` decides what it means as before. * "Copy Text" copies the selection when there is one, captured as the menu opens (showing it takes key focus, and the list drops its selection on focus loss) and matched by event id so a copy aimed at another message cannot pick up these words. Not covered: image/video captions (`TextOrImage`) and small state events, which render through widgets with no selection support. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…syntax
The keyboard-selection highlight in the room filter's search results never
rendered. Its `draw_bg` block declared the pixel shader with makepad 1.x's
`fn pixel(self) -> vec4 { … }`; the 2.0 DSL wants the property form
`pixel: fn() { … }` and rejects the old one with
[E] src/shared/room_filter_search_results.rs:38:10 - cannot push to frozen vec
which fails the whole `draw_bg` block, taking the `selected` instance with
it — so the Animator had nothing to drive and the row never highlighted.
This was the last 1.x shader declaration in the tree; every other
`draw_bg` already uses the property form.
The body is unchanged. Mixing up from a fully transparent base scales rgb
and alpha together, so the result is already premultiplied and must not be
wrapped in `Pal.premul` on top.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.
Copying a message was all-or-nothing: you could take the whole body from the context menu, but you could not drag across a sentence and copy that.
What you can do now
Why it did not already work
Makepad ships the machinery —
TextFlow.selectableplusPortalList.selectable, which owns a cross-item anchor/cursor, auto-scrolls at the viewport edge and answersHit::TextCopy. The timeline just never opted in, and two things stood in the way once it did.The plaintext body was a
Label, which has no selection support at all — so the most common kind of message was the one you could not select. Bodies without links used it; bodies with links already went through the HTML renderer, becauselinkify_get_urlsescapes what it rewrites. Both now take the HTML path, which is aTextFlow.Everything claimed to be a click target.
Widget::is_interactivedefaults totrue, and a selectablePortalListdeclines to begin a drag over anything interactive, on the grounds that the press belongs to that widget:HtmlOrPlaintextinherited that default, and so does makepad'sHtml— unlikeMarkdownandTextFlow, which both returnfalse. A drag on message text therefore became a drag-to-scroll and no selection was ever created: the timeline slid under the pointer, which is exactly what this looked like from the outside.Instrumenting the hit test is what settled it. The press resolved to the
Htmlwidget itself, with a rect that correctly covered the pointer — so this was not a stale-area problem — and the boundary lined up with the card's left edge at x=481:has_selection=true✅…htmlhas_selection=false❌Markdown-rendered bot cards selected fine; the same content rendered through
Htmldid not.The invariant this rests on
At most one body per item may report text.
Viewresolves a selection by taking the first child that answers and does not skip hidden children, andTextFlowclears itsSelectionTrackeronly insidebegin()— which a hidden widget never reaches. A merely-hidden body keeps answering hit tests, and copy, with whatever it last held, so it would shadow the visible one and copy out text that is not on screen.So idle bodies are drawn empty rather than hidden (
HtmlOrPlaintext::clear_body, guarded becauseHtml::set_textre-parses and redraws unconditionally and these callers run in the draw pass), and the plain body is declared before the bot card so a normal message wins outright.Dependency
Html's missingis_interactiveoverride is fixed upstream in ZhangHanDong/makepad#5.[patch]still points at the pre-fix rev, so this branch is not self-sufficient yet: a fresh clone, another machine, and CI will all build without the fix and selection will silently not work (it degrades to drag-to-scroll).scripts/patch-makepad-html-selectable.shre-applies it to the local cargo checkout in the meantime. Delete the script and let[patch]pick up the new rev once the PR merges.Also here: the room-filter highlight (
80e801c6)Noticed while verifying the above — the app was logging this on every startup:
The keyboard-selection highlight in the room filter's search results declared its pixel shader with makepad 1.x's
fn pixel(self) -> vec4 { … }. The 2.0 DSL wantspixel: fn() { … }and rejects the old form, which fails the wholedraw_bgblock — taking theselectedinstance with it, so the Animator had nothing to drive and the row never highlighted. This was the last 1.x shader declaration in the tree.Shader body unchanged; it was already correctly premultiplied.
Not covered
Image/video captions (
TextOrImage) and small state events render through widgets with no selection support, so those stay unselectable.Testing
Manually exercised on desktop: plain and multi-line messages, agent cards in both HTML and Markdown modes, cross-message drags, edge auto-scroll, copy via keyboard and via the context menu, right-click menu still opening, and the fold/
View formattedcontrols inside agent cards still clickable (a press on aButtoncorrectly declines to start a selection). Startup log is now free of DSL errors.🤖 Generated with Claude Code