fix(app): make the window-level clipboard fallback the sole ⌘V path - #130
Merged
Conversation
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
marked this pull request as ready for review
August 7, 2026 22:17
This was referenced Aug 8, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
Scope
prompt-input.tsx), the v2 composer'shandleFramedPaste(interaction.ts,attachments.ts), thedata-amc-clipboard="self"markers on both editors, and the structural test rewritten to assert a single mod+V path.Assumptions / Open Qs
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;onKeyDownno longer special-cases V.packages/session-ui/src/v2/components/prompt-input/attachments.ts—handleFramedPasteremoved; the paste-time fallbacks (readClipboardImage/readClipboardTextvia the bridge) remain on the nativehandlePastepath.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
packages/appunit suite: 827 passing, 0 failing (was 828 — the superseded v1-marker assertion is replaced by the single-path rewrite).bun typecheck: clean inpackages/appandpackages/session-ui..buildinfo: branchfix/paste-duplication-patch, commit0ecbc8aab, dirty: false at test time), so the verified artefact is attributable.Key Decisions
Constraints & Invariants
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.