fix(desktop): make Ctrl+Z after paste undo the paste, not prior keystroke - #54662
fix(desktop): make Ctrl+Z after paste undo the paste, not prior keystroke#54662euxaristia wants to merge 2 commits into
Conversation
…ndows The desktop OAuth disconnect flow hands the user a one-click "run in terminal" command for external providers. For claude-code, that command used POSIX `rm -f ~/.claude/.credentials.json`, which the embedded Windows terminal cannot execute -- on Windows the user sees the disconnect action do nothing. - macOS keeps the keychain + `rm` combo. - Windows now hands the terminal a PowerShell `Remove-Item` call with `-Force` (handles read-only) and `-ErrorAction SilentlyContinue` (no-op when missing, mirroring `rm -f`). $env:USERPROFILE is expanded by the child PowerShell process, so the literal $ survives the embedded shell's quoting. - Other platforms keep the legacy POSIX `rm -f` form. Test updated to assert the Windows form specifically (no `rm -f`, contains 'powershell', references .claude).
…roke
The desktop prompt's paste handler used insertPlainTextAtCaret, which
builds a DocumentFragment and inserts it via range.insertNode. That
mutation never goes through Chromium's editing pipeline, so it does not
land on the contenteditable undo stack. Pressing Ctrl+Z after a paste
reverted the previous in-editor change (the last typed character)
rather than the paste itself — the most visible symptom is that pasting
a link and immediately pressing Ctrl+Z removes the text BEFORE the
link, as if the link had never been entered.
Route the paste through document.execCommand('insertText') instead.
Chromium treats that call as a single native editing transaction, so
Ctrl+Z reverts the whole paste in one step. Chromium's editing pipeline
emits the same text-node + <br> shape the composer already uses, and
composerPlainText round-trips it correctly, so no post-insert DOM
mutation is needed (any mutation would split the undo entry into two
and reintroduce the bug).
- rich-editor.ts: add insertPlainTextUndoable() helper
- index.tsx: switch the paste handler from insertPlainTextAtCaret to
the undoable variant
- rich-editor.test.ts: add tests covering the routes through
execCommand and the user-visible plain-text round-trip
Competing with open fix PR #49746 for the same regression (#49745, introduced by merged #45812). This PR routes the whole paste through |
|
Closing in favor of #49746 — that PR handles this correctly with a 4096-char size threshold (so the O(n²) freeze that PR #45812 fixed on large pastes isn't reintroduced) and also handles the unfocused-editor case where I also accidentally bundled the Windows credential-disconnect fix from #54297 into this branch. The Windows fix already has its own PR and doesn't belong here. #49746 is a strictly better fix on every axis (correctness, scope, perf). |
Summary
On the desktop prompt, pasting text and then pressing Ctrl+Z to undo
the paste reverts the previous in-editor change (the last typed
character) rather than the paste itself. The most visible symptom is
that pasting a link and immediately pressing Ctrl+Z deletes the text
right before the link — as if the link had never been entered.
Root cause
The paste handler in
apps/desktop/src/app/chat/composer/index.tsxcalled
event.preventDefault()and theninsertPlainTextAtCaret,which builds a
DocumentFragmentand inserts it viarange.insertNode(...). That DOM mutation never goes throughChromium's editing pipeline, so it does not land on the contenteditable
undo stack. Pressing Ctrl+Z reverts the most recent native editing
change — the last keystroke — not the paste.
Fix
Route the paste through
document.execCommand('insertText'), whichChromium treats as a single native editing transaction. The whole paste
now lands as one undoable step. Chromium's editing pipeline emits the
same text-node +
<br>shape the composer already uses, andcomposerPlainTextround-trips it correctly — so no post-insert DOMmutation is needed (any post-insert mutation would split the undo
entry and reintroduce the bug).
Changes
apps/desktop/src/app/chat/composer/rich-editor.ts: newinsertPlainTextUndoable(editor, text)helper that wrapsdocument.execCommand('insertText')and returns the caret rangeChromium leaves at the end of the inserted text.
apps/desktop/src/app/chat/composer/index.tsx: paste handler nowuses the undoable variant.
apps/desktop/src/app/chat/composer/rich-editor.test.ts: testsfor the new helper. jsdom does not implement
execCommand, so thetest stubs it with the exact DOM mutations Chromium's pipeline
performs for a plain-text insert (text node +
<br>per newline).The existing
insertPlainTextAtCaretis kept and still used forsynthetic inserts (chip authoring, completion replacements) where the
mutation should NOT appear on the undo stack.
Test plan
hello(note the trailing space).https://example.com.hellois left behind; the link is stillthere. Pressing Ctrl+Z again removes the space.
https://example.comlink isremoved;
hellois intact.