Skip to content

fix(app): make the window-level clipboard fallback the sole ⌘V path - #130

Merged
gennadiryan merged 5 commits into
local/amicodefrom
fix/paste-duplication-patch
Aug 7, 2026
Merged

fix(app): make the window-level clipboard fallback the sole ⌘V path#130
gennadiryan merged 5 commits into
local/amicodefrom
fix/paste-duplication-patch

Conversation

@gennadiryan

Copy link
Copy Markdown
Member

Important

Problem

Pasting into the chat composer inside the Amicode webview inserted the same text twice. The marker route (sibling PR from fix/paste-duplication-amicode-261) suppresses only the window-level fallback; the composer's own mod+V interceptor remains a second insert path, and the remote retest reproduced the double paste.

Approach

Make the window-level clipboard fallback the sole mod+V path: remove the composers' own mod+V keydown interceptors and the now-dead markers. Key reason: a single path cannot double-insert, and no suppression mechanism has to stay correct across two listeners.

Approaches Considered

  • Remove the composer's own handler (chosen) — one insert path remains; nothing left to disagree about. Accepted cost: plain Ctrl+V pastes text only.
  • Add stopPropagation to the global fallback — superficially tempting and actively harmful: the composer's handler is the only path that tries the image bridge, and suppressing it would silently kill screenshot paste while appearing to fix the bug.
  • Mark the composer with the self-owner attribute (marker route, sibling PR) — suppresses only the fallback; the composer's interceptor stays live; remote retest reproduced the double paste.

Scope

  • In: removal of the v1 composer's mod+V keydown block (prompt-input.tsx), the v2 composer's handleFramedPaste (interaction.ts, attachments.ts), the data-amc-clipboard="self" markers on both editors, and the structural test rewritten to assert a single mod+V path.
  • Out: the fallback's logic, unchanged. Copy and cut, which deliberately stay on the global path. The native paste path (Ctrl+Shift+V, context menu), which still attaches images. The bridge protocol and message kinds.

Assumptions / Open Qs

  • Whether plain-Ctrl+V being text-only is an acceptable long-term trade is a pending design decision; this PR is the stopgap that makes the webview single-paste while that is decided.

What was failing

Two mod+V handlers are live inside the framed app, and both insert.

The window-level fallback runs in the capture phase and serves every editable that has no bridged paste of its own — necessary because native paste never fires inside the webview iframe.

The composer's own handler intercepts mod+V, tries the image bridge first (screenshots), then falls back to text, and inserts through the composer's structured model.

The fallback calls preventDefault but deliberately not stopPropagation. That is correct for its purpose, but it means the composer's interceptor still receives the keystroke afterwards — and both handlers bridge the clipboard into the document. Two paths, two inserts. Marking the composer only exempted the first path; the second remained, which is why the marker route alone did not hold up under remote retest.

What changed

  • packages/app/src/components/prompt-input.tsx — the v1 composer's mod+V keydown interception block removed; the marker and its comment removed; unused imports dropped.
  • packages/session-ui/src/v2/components/prompt-input/interaction.ts — the mod+V interception (amicode patch Fix chat scroll jitter during streaming; respect reduced motion #11 parity block) removed; onKeyDown no longer special-cases V.
  • packages/session-ui/src/v2/components/prompt-input/attachments.tshandleFramedPaste removed; the paste-time fallbacks (readClipboardImage/readClipboardText via the bridge) remain on the native handlePaste path.
  • packages/session-ui/src/v2/components/prompt-input/index.tsx — marker and comment removed.
  • packages/app/src/utils/global-clipboard.ts — header re-documented: this module is now the sole mod+V path in the webview; the marker contract covers only the profile fields' paste fallback.
  • packages/app/src/components/prompt-input-clipboard-structure.test.ts — rewritten to assert the single-path contract: no composer-level V interception, no markers in either composer, the fallback owns the mod+V branch, and the native paste wiring with bridge fallbacks is intact.

Verification

  • Structural test: 5 passing (single-path assertions listed above).
  • packages/app unit suite: 827 passing, 0 failing (was 828 — the superseded v1-marker assertion is replaced by the single-path rewrite).
  • bun typecheck: clean in packages/app and packages/session-ui.
  • Remote GUI retest (amicode:main + this branch): Ctrl+V pastes exactly once; Ctrl+Shift+V pastes once with images intact. The retested binary is the stamped build (.buildinfo: branch fix/paste-duplication-patch, commit 0ecbc8aab, dirty: false at test time), so the verified artefact is attributable.

Key Decisions

  • Removal over suppression. A suppression mechanism leaves two listeners that must stay in agreement; removal leaves one. This is the same class of bug as the sweep's other findings — a mechanism and its integration disagreeing — and the single-path design has no agreement left to maintain.
  • Text-only on plain Ctrl+V, accepted for the stopgap. The extension bridge reads text; images on plain Ctrl+V are sacrificed. Native paste (Ctrl+Shift+V, context menu) keeps the image path, so screenshots remain reachable while the design question is decided.
  • Markers deleted, not kept. With the composer interceptor gone there is no second listener to exempt; a marker asserting nothing would be inert code.

Constraints & Invariants

  • The fallback's behaviour for every other editable is untouched.
  • Copy and cut still mirror to the OS clipboard through the global path.
  • No change to the bridge protocol or to the set of message kinds the host accepts.
  • No change to the keybind matcher or the native paste handler.

Source

Closes harmoniqs/amicode#261 (stopgap; acceptance pending the design decision above).

Companion to the marker-route PR from fix/paste-duplication-amicode-261; the two are alternatives, not stacked changes. Found during a bug sweep of the amicode ↔ vendored-opencode seam; related seam work is harmoniqs/amicode#243.

Notes

Why this shipped instead of the marker route: the marker mechanism and its single integration disagreed, and the retest showed the mechanism suppressing only one of two insert paths. "One path" is not a compromise on correctness — it is the only arrangement where the double insert is structurally impossible. The cost is a UX decision (plain Ctrl+V text-only) that is explicitly parked for design review, not silently accepted here.

Amicode Sweep and others added 5 commits August 7, 2026 08:05
Paste inserted the same text twice inside the amicode webview.

Two mod+V handlers are live in the framed app: the window-level capture-phase
fallback in utils/global-clipboard.ts, and the composer's own image-first
handler in prompt-input.tsx. The fallback exempts elements matching
CLIPBOARD_SELF_SELECTOR, and it calls preventDefault() but deliberately not
stopPropagation() — so an unmarked composer receives both insertions.

The marker had been applied to the home-cards credential fields but never to
the composer, even though global-clipboard.ts's own header names "the prompt
input's Cmd+V handler" as the element the exemption exists for.

Marking the composer is the correct fix rather than adding stopPropagation():
the composer's handler is the only path that tries the image bridge, so
suppressing it would silently kill screenshot paste. The marker owns PASTE
only; Cmd+C / Cmd+X keep mirroring through the global path.

Adds a structural guard. The existing global-clipboard test asserts the
exemption against a synthetic element it builds itself, which is precisely why
this shipped — the mechanism was covered, its single real integration was not.

Closes harmoniqs/amicode#261
…leanup of inputs forwarded to controller that should not be
@gennadiryan
gennadiryan marked this pull request as ready for review August 7, 2026 22:17
@gennadiryan
gennadiryan requested a review from kateebonner August 7, 2026 22:17
@gennadiryan
gennadiryan merged commit e246c2b into local/amicode Aug 7, 2026
1 of 4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

BUG: paste duplicates input — clipboard content inserted twice

1 participant