feat(chat): double-press paste shortcut to expand a collapsed paste - #9299
feat(chat): double-press paste shortcut to expand a collapsed paste#9299chenmingwei23 wants to merge 1 commit into
Conversation
|
Intent: Let a user open a collapsed |
First Principles Review (Fable 5) — 🟡 CONCERNSPremise-level review of All evidence is in. The base composer already has a live click/tap expand path ( First-Principles-Verdict: CONCERNS The composer already expands a paste into editable text (double-click/tap → What this change shipsIntent: let a user open a collapsed paste back into editable text in the composer before sending — an ADDITION (issue #8513).
Watch
Subtractions
[FIRST-PRINCIPLES-REVIEWED] f0be2bf |
Opus 4.8 Review — ✅ no blocking findingsReviewed Review detailsThe single candidate hinges on "if the paste event still fires" — an explicit "might/if". The codebase's own No findings. [OPUS-REVIEWED] f0be2bf Verdict parsed from the review's SHA-scoped output markers for commit False positive or not applicable? A repository writer can comment: |
GPT 5.6 Review — ✅ no blocking findingsGPT 5.6 completed its review of This comment is updated in place on each push. Review detailsNo findings. False positive or not applicable? A repository writer can comment: |
Design Review (Fable 5) — 🟡 CONCERNSDesign-level review of Analysis complete. The gesture's trigger condition is confirmed in Design-Verdict: CONCERNS Positional-only trigger has no recency bound, so it hijacks any later "paste new clipboard here" at the caret's most natural resting spot. Watch
Suggestions
[DESIGN-REVIEWED] f0be2bf |
UX Review (Fable 5) — 🟡 CONCERNSUX-level review of All evidence is in: no blind read ran (nothing visual was committed), the diff adds one invisible keyboard gesture that overloads Cmd/Ctrl+V on a collapsed paste token, and the repo's custom undo snapshots (value + blocks) make the expand reversible via Ctrl+Z. UX-Verdict: CONCERNS An invisible Paste overload: right after pasting, Cmd/Ctrl+V with new clipboard content expands the old paste instead of pasting — undiscoverable until it surprises. Watch
Evidence gaps
[UX-REVIEWED] f0be2bf |
9734eb8 to
202b591
Compare
self-added: yes
|
…nline When the caret rests on a collapsed [ Paste #N ] token in the composer, pressing Cmd/Ctrl+V again replaces that one token with its full content, inline and editable, so the paste can be reviewed and trimmed before send. The first Cmd/Ctrl+V keeps its native, instant paste; the gesture is positional (caret on a token), not timed, so a single press never feels slower. Adds expandTokenAt() to pasteTokens.ts and one keydown branch in ChatInput.tsx, reusing the existing atomic-token handling model. Refs #8513
202b591 to
f0be2bf
Compare
self-added: yes
|
Standing down: the capability already exists, and the paste-key gesture cannot be made safeWhat the composer already does. A collapsed Why the requested keyboard gesture (double-press Cmd/Ctrl+V) cannot be bound safely. The gesture keys on caret position: a second Cmd/Ctrl+V while the caret sits on a token expands it. Three review lanes flagged one defect from three angles -- it is a position-based mode, so a user who pastes, copies something new, and presses paste again at that same caret spot gets the OLD paste expanded instead of their new clipboard content. Their paste silently vanishes. That is the worst surprise a text input can produce, and nothing on screen signals the key means something different there. The correct binding would be "expand only as a continuation of the paste that just happened" -- a short recency window AND the clipboard content unchanged since. But the paste shortcut fires on Recommendation. Close the PR (its keyboard override is a paste key that sometimes does not paste). The issue's stated need is already served by the existing double-click and tap expand. If a keyboard route is still wanted for accessibility, it should be a DISTINCT chord (not the paste key) that expands the token at the caret and reuses Evidence gathered on head f0be2bf. This comment records the finding; the disposition (close, redesign to a non-paste chord, or accept the existing pointer/touch expand as sufficient) is the maintainer's. |
|
Closing unmerged. The analysis is on this PR and on #8513, and the short version is that the Clipboard text is available synchronously only inside the Three review lanes found that same defect from three directions, which is what prompted the Worth recording that the capability itself already exists for pointer users -- the composer The issue stays open: its underlying need is legitimate and a distinct chord reusing the existing |
Problem / Motivation
A large paste into the dashboard composer collapses to a
[ Paste #N | M lines ]token so the box stays readable. Today you cannot see what that token holds without
sending the message. The composer only offers a hover/caret PEEK tooltip
(
PasteHoverLayer), which shows the first few lines and cannot be edited. Theclick-to-expand chip in
PastedChipexists only in the SENT bubble, not in thecomposer while you are still writing. So you cannot verify you pasted the right
block, and you cannot trim or edit it in place.
Why it matters
Pasting a log, a diff, or a transcript and being unable to check it before send is
easy to get wrong: the wrong block, an extra copy, a stray line. The user has no way
to open the paste back up and fix it without deleting the whole token and starting
over.
What changed (motivation -> approach -> change)
Goal: let the user open a collapsed paste back into editable text, in place, with a
gesture that matches how Claude Code does it (issue #8513).
Approach and the design trap. The gesture is a SECOND Cmd/Ctrl+V while the caret is
resting on a collapsed token. The important choice is that it is POSITIONAL, not
timed. There is no double-tap interval at all -- the code does not wait, does not
buffer, and does not schedule anything. The first Cmd/Ctrl+V is the ordinary native
paste and fires immediately with zero added latency; it is never delayed and never
undone. The "second press" is simply "the caret is on a paste token and you pressed
Cmd/Ctrl+V again" -- the exact same "caret is on a token" model the composer already
uses for Backspace, Delete, and Arrow keys on these tokens. A timed double-tap would
have made every single paste feel slower while the code waited to see if a second
one was coming; because the trigger is the caret position and not a timer, the
ordinary paste keeps its full speed and the gesture adds nothing to it.
Existing convention. There was no repeated-keypress or double-tap handler in the
composer to match, so none was invented -- the gesture reuses the established
caret-on-token branch structure in
ChatInputhandleKeyDown. There is already anexpand affordance for a SENT paste (the
PastedChipclick toggle); this adds thecomposer-side counterpart the issue asks for, beside the existing hover peek rather
than replacing it.
The change.
expandTokenAt(text, blocks, caret)inpasteTokens.tsreplaces the onetoken under the caret with its block's verbatim content and returns the caret offsets
that select the inserted text.
ChatInputcalls it from a new keydown branch onplain Cmd/Ctrl+V (not Shift+V, which is the existing raw-inline-paste shortcut), calls
preventDefaultso the clipboard is not re-pasted on top of the token, and drops theexpanded block from the tracked set -- an expanded paste is now plain editable text
and must not also be re-sent as a separate paste (send maps the remaining tokens via
pruneBlocks).flowchart LR subgraph Before A1[caret on paste token]:::ctx --> B1[Cmd/Ctrl+V]:::ctx --> C1[re-paste clipboard]:::removed A1 --> P1[hover/caret peek only]:::ctx end subgraph After A2[caret on paste token]:::ctx --> B2[Cmd/Ctrl+V]:::ctx --> C2[token becomes editable text, selected]:::added end classDef added fill:#DCFCE7,stroke:#16A34A,color:#14532D,stroke-width:2px classDef changed fill:#FEF3C7,stroke:#D97706,color:#78350F,stroke-width:2px classDef removed fill:#FEE2E2,stroke:#DC2626,color:#7F1D1D,stroke-dasharray:4 3 classDef ctx fill:#E0F2FE,stroke:#0284C7,color:#0C4A6E linkStyle 2 stroke:#DC2626,stroke-dasharray:4 3 linkStyle 4 stroke:#16A34A,stroke-width:2pxLegend: added, changed, removed, unchanged. Pressing the paste key on a collapsed
token now opens it into text you can trim, instead of pasting the clipboard again.
Scope notes for review:
composer text) is being worked in parallel and also touches
ChatInput.tsx. Myedit is one self-contained new branch inside the existing atomic-token block in
handleKeyDown; it does not touch the highlight-layer wiring (PasteHighlightLayer,PasteHoverLayer) or the composer host files (ChatPane,ChatPage,SideChat)that Collapsed paste highlights detach from composer text #8309 is editing. If the two land close together this branch is an additive
hunk and should rebase cleanly.
not required for the ask. This PR ships the expand direction, which is the issue's
stated need (see what a paste holds, trim/edit it in place) -- hence
Closes. Areversible toggle needs an "expanded but still tracked" block state, and the composer's current model drops a block the moment its
token leaves the text (the
pruneBlockseffect, andpruneBlocksis also what thesend path uses to decide which pastes to send). Keeping an expanded-yet-tracked
block would change that send/prune contract and reach into the same composer wiring
Collapsed paste highlights detach from composer text #8309 is editing. Re-collapse is left as a follow-up rather than widening this PR
into that shared surface.
arrow keys land the caret inside a token (the existing
PasteHoverLayercaretpath), and Cmd/Ctrl+V there expands it, so it needs no pointer and no mouse. For
READING a collapsed paste without the gesture, the existing hover/caret PEEK
tooltip stays in place -- it opens on keyboard focus/caret as well as hover and is
screen-reader announced via
aria-describedby, so a user who cannot perform thedouble press can still see the content. Honest limit: there is no non-gesture
affordance in the COMPOSER that performs the expand-into-editable-text itself
(no click target, no menu item). The sent-bubble chip (
PastedChip) has the fulltitle+aria-label+ click toggle, but the composer token is drawn in anon-interactive mirror layer on purpose --
PasteHoverLayerdocuments that thecomposer keeps no interactive layer above the textarea, because one would intercept
clicks and selection. Adding a composer-side click/menu expand affordance would
reach into that highlight/hover surface, which is Collapsed paste highlights detach from composer text #8309's territory, so it is
deferred to the same follow-up as re-collapse rather than added here. If review
wants a non-gesture expand path in this PR, that is a design call for the
maintainer, since it changes the composer's no-interactive-overlay rule.
Tests
website/src/test/pasteTokens.test.ts--expandTokenAt: replaces the token at thecaret with its content and selects it; expands ONLY the token under the caret,
leaving others collapsed; returns null when the caret is not on a token.
website/src/test/ChatInput.paste.test.tsx-- the keydown wiring: Cmd+V on a tokenreplaces it with the full content; the expanded block is dropped from
onPasteBlocksChange; a caret NOT on a token leaves it collapsed; Cmd+Shift+V (rawinline paste) does not trigger the expand.
Commands run (one file at a time):
Both green (46 and 27 tests). The two wiring assertions were mutation-verified:
disabling the new keydown branch reds exactly "replaces the token" and "drops the
block" and nothing else.
Manual verification
Could not render a live frame in this environment: a fresh
vite buildfails toresolve
@lexical/react/lexical(both are0.50.0deps added by #8310 and arenot present in the available install), and
npm installis disabled on this host.The behaviour is pinned by the unit + wiring tests above, including the mutation
check. A rendered frame should come from CI or a maintainer's built environment.
Screenshots / video
Why no screenshot: the diff adds no new rendered element or style. The collapsed
[ Paste #N ]token and the expanded plain text are both produced by unchangedrendering code; the change only moves existing content into the existing textarea via
onChange. A live frame also could not be produced here (see Manual verification: thelexicaldeps are not installed andnpm installis disabled).Related Issues
Closes #8513