Skip to content

fix(desktop): paste text undoable via Ctrl+Z on Windows (#6531) / 修复ctrl+z无法撤回ctrl+v的内容 - #6571

Merged
SivanCola merged 9 commits into
esengine:main-v2from
JesonChou:fix/issue-6531-paste-undo
Jul 25, 2026
Merged

fix(desktop): paste text undoable via Ctrl+Z on Windows (#6531) / 修复ctrl+z无法撤回ctrl+v的内容#6571
SivanCola merged 9 commits into
esengine:main-v2from
JesonChou:fix/issue-6531-paste-undo

Conversation

@JesonChou

@JesonChou JesonChou commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes #6531.

Desktop text paste is applied programmatically after the browser default is prevented, so WebView2 cannot reliably place that edit in its native undo history. This PR adds a bounded, per-draft transaction history for programmatic composer edits while preserving the browser-native history for ordinary typing.

  • Preserves the real order between native edits and programmatic paste/cut transactions, including native edits whose net text returns to an earlier value.
  • Restores text, structured invocations, folded paste blocks, paste IDs, and the complete rich-composer selection.
  • Keeps long-paste preview state out of document-history matching, so opening a preview does not disable paste undo.
  • Preserves afterInvocationId, so undo returns the caret to the correct side of an inline skill or subagent token.
  • Tracks programmatic rich-composer edits, including invocation insertion/removal, without treating browser input as a duplicate custom edit.
  • Keeps draft histories isolated across tabs and sessions, including asynchronous context-menu paste and prompt-history results.
  • Uses Ctrl/Cmd+Z for undo and Ctrl/Cmd+Shift+Z for redo. On Windows/Linux, Ctrl+Y remains the default YOLO shortcut; if the user rebinds YOLO, Ctrl+Y becomes the standard redo fallback.
  • Registers Undo and Redo in the shared shortcut catalog so Settings and shortcut help show the platform keys as locked native-editing rows.
  • Adds Undo and Redo to the composer context menu with live enabled/disabled state and the same per-draft transaction history.
  • Makes composer YOLO tooltips follow the current custom binding instead of displaying a hard-coded Ctrl/Cmd+Y.
  • Reserves standard undo/redo chords inside editable fields even when an older installation has a legacy global binding on the same keys.
  • Documents the desktop behavior in the English and Chinese guides and tool-approval references.
  • Includes main-v2 through 87999b974.

This PR changes the Desktop frontend and user-facing documentation; it does not change CLI/TUI behavior or the embedded terminal.

Verification

  • pnpm test:all
  • pnpm build
  • go test ./...
  • cd desktop && go test ./...
  • git diff --check
  • Focused regression coverage:
    • composer session drafts: 124 passed
    • composer goal/rich input: 179 passed
    • rich composer selection: 80 passed
    • context-menu clipboard/history: 15 passed
    • shortcut registry: 71 passed
    • shortcut settings recorder: 25 passed
  • Real desktop browser verification:
    • Settings shows locked Undo/Redo rows with platform-specific keys.
    • Rebinding YOLO updates the composer tooltip immediately; the test binding was restored afterward.
    • Context-menu Undo and Redo restore the pre-paste and post-paste drafts.
    • No browser console warnings or errors.

All checks passed locally on commit 3afcafb03.

Compatibility

Surface Existing behavior/data Conclusion
Composer drafts Undo transactions remain bounded, in-memory, and scoped per draft Backward-safe
Native text editing Ordinary typing remains in the browser history; custom transactions respect native boundaries Preserved
Existing custom shortcuts Standard undo/redo chords take precedence only while editing; legacy bindings still work outside editable fields Safe migration
Persisted state and configuration No format or field changes Unchanged
Wails and provider contracts No API, prompt, tool-schema, or serialization changes Unchanged

Cache and security impact

  • Cache impact: none. No provider-visible prompt, tool schema/order, request serialization, memory, compaction, or reasoning path changed.
  • Security impact: no new dependencies, network calls, file access, persistence, permission changes, or clipboard exfiltration.

@github-actions github-actions Bot added v2 Go rewrite (1.x) — main-v2 branch, active development desktop Wails desktop app (desktop/**) tui Terminal UI / CLI (internal/cli, internal/control) labels Jul 16, 2026
@JesonChou
JesonChou force-pushed the fix/issue-6531-paste-undo branch from 23c4478 to a85fbf9 Compare July 16, 2026 09:22
On Windows Ctrl+Z has no job-control semantics, so repurpose it as
undo for paste operations. A LIFO undo stack (capped at 50 entries)
records the input state before each paste; Ctrl+Z pops and restores.
Folded pastes and image refs participate without double-pushing.
…6531)

Replace the unreliable browser-native undo approach (execCommand /
setRangeText in WebView2) with an explicit paste undo stack in the
Composer. Before each paste the current input text is pushed onto a
ref-managed stack; a Ctrl+Z keydown handler pops and restores the
last saved state. Falls through to browser-native undo when the
stack is empty, preserving normal text-editing undo.
@JesonChou
JesonChou force-pushed the fix/issue-6531-paste-undo branch from a85fbf9 to 7e10583 Compare July 17, 2026 17:16
@JesonChou JesonChou changed the title fix: paste text undoable via Ctrl+Z on Windows (#6531) / 修复ctrl+z无法撤回ctrl+v的内容 fix(desktop): paste text undoable via Ctrl+Z on Windows (#6531) / 修复ctrl+z无法撤回ctrl+v的内容 Jul 23, 2026
@JesonChou

Copy link
Copy Markdown
Contributor Author

@SivanCola 大佬,可以审下这个PR么?这个bug也是比较久了,关于desktop剪切板体验的。当前desktop对于ctrl+v到输入框的内容不能通过ctrl+z撤回掉,对于大段文字的粘贴修改体验比较差。

Problem:
Desktop paste is applied through React after preventing the browser default, so Ctrl+Z cannot see it. The initial fix used a global text-only stack that crossed drafts and missed folded pastes.

Root cause:
Programmatic edits live outside the WebView native undo history, while a component-global snapshot cannot model per-draft structured composer state.

Fix:
- Track bounded paste and cut transactions per draft with text, invocation, folded-block, counter, and selection state.
- Intercept undo and redo only at exact transaction boundaries so native typing undo remains intact.
- Clear histories on replace and submit, and keep TUI suspend behavior unchanged.
- Add regressions for short paste, redo, folded paste, and cross-draft isolation.

Verification:
- pnpm test:typecheck
- pnpm test
- pnpm build
- go test ./... (desktop)
- go test ./internal/cli/...
Problem: Programmatic paste redo survived later native edits, and rich-composer undo snapshots lost the invocation-side caret anchor.

Root cause: The custom redo stack was cleared only by another recorded transaction, while edit snapshots and selection restoration stored text offsets without afterInvocationId. The Windows Ctrl+Y fallback was also unreachable because the existing YOLO shortcut owns that chord.

Fix: Invalidate per-draft redo on native or structured composer edits, preserve complete rich selections across paste and restore paths, keep invocation offsets stable for menu paste, and reserve Ctrl+Y for the established approval shortcut. Merge the latest main-v2 baseline.

Verification: pnpm test; pnpm test:typecheck; pnpm build; desktop go test ./...; go test ./internal/cli/...; git diff --check.

Co-authored-by: SivanCola <32437197+SivanCola@users.noreply.github.com>
Problem:
Programmatic paste transactions could jump ahead of newer browser edits, become unavailable while a folded preview was open, and miss Ctrl+Y redo after the YOLO shortcut was rebound.

Root cause:
The custom history inferred ordering only from snapshot equality and treated preview-only UI state as document state. Rich-composer programmatic edits also did not identify their history origin.

Fix:
Track native undo/redo barriers per draft, preserve mixed custom/native ordering, separate browser and programmatic rich-input changes, keep preview state out of history matching, and support the platform Ctrl+Y fallback without overriding the configured YOLO shortcut.

Verification:
pnpm test:all
pnpm build
go test ./...
cd desktop && go test ./...
@SivanCola SivanCola removed the tui Terminal UI / CLI (internal/cli, internal/control) label Jul 24, 2026
Integration:
Sync PR esengine#6571 with main-v2 at 87999b9 before the user-facing shortcut follow-up.

Verification:
The relevant Composer, shortcut, locale, and guide paths have no upstream conflicts; full verification follows in the next commit.
Problem:
Composer undo and redo behavior was not discoverable in Settings, help, context menus, or the user guides. YOLO tooltips also kept showing its default binding after a rebind.

Root cause:
The composer handled editing chords and Reasonix transactions locally instead of registering the actions in the shared shortcut catalog. The custom context menu exposed clipboard actions only, and documentation still described Ctrl/Cmd+Y as YOLO-only.

Fix:
Register locked platform undo and redo actions, protect their editing chords from legacy global bindings, reuse the transaction history from the context menu, render live YOLO shortcut labels, and update English and Chinese guidance.

Verification:
pnpm test:all
pnpm build
go test ./... in desktop
Real browser checks for Settings rows, live YOLO rebind labels, context-menu undo/redo, and a clean console
Problem:
The rich composer did not open Reasonix's edit context menu, so drafts containing skill or subagent tokens could not use the transaction-aware Undo and Redo actions.

Root cause:
Only the plain textarea wired the shared context-menu handler; RichComposerInput did not expose or forward a contextmenu event.

Fix:
Forward context-menu events from RichComposerInput and reuse the existing Composer edit menu for both editable surfaces. Add regression coverage for undoing and redoing a paste after an inline invocation.

Verification:
- pnpm exec tsx src/__tests__/composer-goal-toggle.test.tsx
- pnpm exec tsx src/__tests__/composer-session-draft.test.tsx
- pnpm exec tsx src/__tests__/composer-context-menu-clipboard.test.tsx
- pnpm test:typecheck
- pnpm build
- git diff --check

Co-authored-by: SivanCola <32437197+SivanCola@users.noreply.github.com>
Problem:
Legacy custom YOLO bindings on Ctrl/Cmd+Z or Ctrl/Cmd+Shift+Z intercepted
Composer undo and redo, toggling approval mode instead of editing the draft.

Root cause:
Composer matched the local YOLO shortcut before its transaction-aware history
handler and did not apply the reserved editing-chord policy used globally.

Fix:
Exclude reserved Composer undo and redo chords from local YOLO handling. Add
upgrade regression coverage for legacy bindings, rebound YOLO, and the default
Ctrl+Y behavior.

Verification:
- pnpm exec tsx src/__tests__/composer-session-draft.test.tsx (130 passed)
- pnpm exec tsx src/__tests__/keyboard-shortcuts.test.ts (71 passed)
- pnpm exec tsx src/__tests__/composer-goal-toggle.test.tsx (185 passed)
- pnpm exec tsx src/__tests__/shortcut-recorder.test.tsx (25 passed)
- pnpm test:typecheck
- pnpm exec vite build --logLevel error
- git diff --check

Co-authored-by: SivanCola <32437197+SivanCola@users.noreply.github.com>
@SivanCola

Copy link
Copy Markdown
Collaborator

@SivanCola 大佬,可以审下这个PR么?这个bug也是比较久了,关于desktop剪切板体验的。当前desktop对于ctrl+v到输入框的内容不能通过ctrl+z撤回掉,对于大段文字的粘贴修改体验比较差。

做了一些修复,下个版本就带上去

@SivanCola
SivanCola enabled auto-merge (squash) July 25, 2026 02:23
@SivanCola
SivanCola merged commit acdef99 into esengine:main-v2 Jul 25, 2026
16 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

desktop Wails desktop app (desktop/**) v2 Go rewrite (1.x) — main-v2 branch, active development

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: 粘贴的文字无法撤销

2 participants