Skip to content

fix(desktop): make Ctrl+Z after paste undo the paste, not prior keystroke - #54662

Closed
euxaristia wants to merge 2 commits into
NousResearch:mainfrom
euxaristia:fix/desktop-composer-undo-after-paste
Closed

fix(desktop): make Ctrl+Z after paste undo the paste, not prior keystroke#54662
euxaristia wants to merge 2 commits into
NousResearch:mainfrom
euxaristia:fix/desktop-composer-undo-after-paste

Conversation

@euxaristia

Copy link
Copy Markdown

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.tsx
called event.preventDefault() and then insertPlainTextAtCaret,
which builds a DocumentFragment and inserts it via
range.insertNode(...). That DOM mutation never goes through
Chromium'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'), which
Chromium 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, and
composerPlainText round-trips it correctly — so no post-insert DOM
mutation 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: new
    insertPlainTextUndoable(editor, text) helper that wraps
    document.execCommand('insertText') and returns the caret range
    Chromium leaves at the end of the inserted text.
  • apps/desktop/src/app/chat/composer/index.tsx: paste handler now
    uses the undoable variant.
  • apps/desktop/src/app/chat/composer/rich-editor.test.ts: tests
    for the new helper. jsdom does not implement execCommand, so the
    test stubs it with the exact DOM mutations Chromium's pipeline
    performs for a plain-text insert (text node + <br> per newline).

The existing insertPlainTextAtCaret is kept and still used for
synthetic inserts (chip authoring, completion replacements) where the
mutation should NOT appear on the undo stack.

Test plan

  1. Open the desktop prompt.
  2. Type hello (note the trailing space).
  3. Paste https://example.com.
  4. Press Ctrl+Z once.
  5. Expected (before fix): hello is left behind; the link is still
    there. Pressing Ctrl+Z again removes the space.
  6. Expected (after fix): the entire https://example.com link is
    removed; hello is intact.

…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
@alt-glitch alt-glitch added type/bug Something isn't working comp/desktop Electron desktop app (apps/desktop/*) P2 Medium — degraded but workaround exists labels Jun 29, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

This was generated by AI during triage.

Competing with open fix PR #49746 for the same regression (#49745, introduced by merged #45812). This PR routes the whole paste through document.execCommand('insertText'); #49746 uses a size threshold (small pastes undoable via execCommand, large pastes keep the perf bypass). A human should pick one. Note: this PR also bundles an unrelated Windows credential-disconnect fix in hermes_cli/web_server.py — consider splitting it into its own PR.

@euxaristia

Copy link
Copy Markdown
Author

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 execCommand would target a stray selection. Mine routes everything through execCommand which would re-introduce the 4474ms freeze on 47KB pastes.

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).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/desktop Electron desktop app (apps/desktop/*) P2 Medium — degraded but workaround exists type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants