Skip to content

[issues/547] Single clipboard write per R-* operation#556

Merged
couimet merged 6 commits into
mainfrom
issues/547
May 13, 2026
Merged

[issues/547] Single clipboard write per R-* operation#556
couimet merged 6 commits into
mainfrom
issues/547

Conversation

@couimet
Copy link
Copy Markdown
Owner

@couimet couimet commented May 13, 2026

Summary

Architectural refactoring that gives ClipboardRouter sole ownership of the clipboard write. Insert factories now only execute paste commands — never touch the clipboard. Eliminates double clipboard writes, nested ClipboardPreservers, and double padding risk. No user-visible behavior changes.

Changes

  • ClipboardRouter owns the sole clipboard write per operation; insert factories (AIAssistantInsertFactory, TerminalInsertFactory) only execute paste commands — their constructors drop from 4→2 and 3→2 params respectively
  • Two-delay model replaces the old single delay: FOCUS_TO_PASTE_DELAY_MS (pre-paste, 200ms) for focus readiness, CLIPBOARD_POST_PASTE_DELAY_MS (post-paste, 200ms) for webview async clipboard read across Electron IPC boundary
  • Padding pre-applied at all 4 call sites (LinkGenerator, TextSelectionPaster, FilePathPaster, TerminalSelectionService) — performPaste() removed from PasteDestinationManager
  • chatPasteConstants.ts renamed to pasteTimingConstants.ts; multi-command paste array replaced with single editor.action.clipboardPasteAction
  • focusAndPasteCommands → focusCommands in BuiltinAiAssistantDef type (backward-compatible: CustomAiAssistantConfig.focusAndPasteCommands preserved)
  • New ADR-0003 with MermaidJS sequence diagram documenting single clipboard write, two-delay model, and webview race

QA & Tests

  • 15 new QA test cases: built-in AI assistant binding/sending (claude-code-001–005, cursor-ai-001–004, github-copilot-chat-001–004), clipboard-preservation-010, ubuntu-ctrl-keybindings-001
  • New builtInAiAssistants.test.ts [assisted] integration tests for cold/warm paste verification across Claude Code, Cursor, and Copilot Chat
  • New platformKeybindings.test.ts integration test for Ubuntu Ctrl keybinding parity
  • QA YAML precondition cleanup: removed redundant "Extension installed from .vsix build" lines; added labels (requires-extensions, clipboard, ubuntu, cursor) to relevant TCs
  • Removed logBasedUiValidation.test.ts — its assertions now live in individual suite tests
  • Test infrastructure: new standardSuite.ts helper, settingsHelpers improvements
  • Coverage maintained at 98.2% statements, 95.2% branches

Test Plan

  • All existing tests pass (pnpm test — 98.2%/95.2%/95.8%/98.3% coverage)
  • New tests added for: AIAssistantInsertFactory simplified constructor, FocusCapabilityFactory, ComposablePasteDestination paste flow, VscodeAdapter paste methods, padding call sites, dest build pipeline
  • pnpm test:release (VS Code host integration tests) — standard suite passes

Closes #547

Summary by CodeRabbit

  • New Features

    • Improved paste support and delivery for Claude Code, Cursor AI, and GitHub Copilot Chat; optimized terminal paste flow.
  • Improvements

    • Single clipboard write per operation with coordinated pre-paste and post-paste delays and clearer clipboard-restoration rules.
    • Smart-padding is applied earlier so pasted content appears correctly without extra spacing.
    • Simplified assistant focus-only orchestration for more reliable pastes.
  • Documentation

    • Added an ADR documenting the new clipboard/paste orchestration.
  • Tests

    • Expanded QA and integration tests covering clipboard preservation and assistant paste flows.

## Summary

Architectural refactoring that gives ClipboardRouter sole ownership of the clipboard write. Insert factories now only execute paste commands — never touch the clipboard. Eliminates double clipboard writes, nested ClipboardPreservers, and double padding risk. No user-visible behavior changes.

## Changes

- ClipboardRouter owns the sole clipboard write per operation; insert factories (AIAssistantInsertFactory, TerminalInsertFactory) only execute paste commands — their constructors drop from 4→2 and 3→2 params respectively
- Two-delay model replaces the old single delay: FOCUS_TO_PASTE_DELAY_MS (pre-paste, 200ms) for focus readiness, CLIPBOARD_POST_PASTE_DELAY_MS (post-paste, 200ms) for webview async clipboard read across Electron IPC boundary
- Padding pre-applied at all 4 call sites (LinkGenerator, TextSelectionPaster, FilePathPaster, TerminalSelectionService) — performPaste() removed from PasteDestinationManager
- chatPasteConstants.ts renamed to pasteTimingConstants.ts; multi-command paste array replaced with single editor.action.clipboardPasteAction
- focusAndPasteCommands → focusCommands in BuiltinAiAssistantDef type (backward-compatible: CustomAiAssistantConfig.focusAndPasteCommands preserved)
- New ADR-0003 with MermaidJS sequence diagram documenting single clipboard write, two-delay model, and webview race

## QA & Tests

- 15 new QA test cases: built-in AI assistant binding/sending (claude-code-001–005, cursor-ai-001–004, github-copilot-chat-001–004), clipboard-preservation-010, ubuntu-ctrl-keybindings-001
- New builtInAiAssistants.test.ts [assisted] integration tests for cold/warm paste verification across Claude Code, Cursor, and Copilot Chat
- New platformKeybindings.test.ts integration test for Ubuntu Ctrl keybinding parity
- QA YAML precondition cleanup: removed redundant "Extension installed from .vsix build" lines; added labels (requires-extensions, clipboard, ubuntu, cursor) to relevant TCs
- Removed logBasedUiValidation.test.ts — its assertions now live in individual suite tests
- Test infrastructure: new standardSuite.ts helper, settingsHelpers improvements
- Coverage maintained at 98.2% statements, 95.2% branches

## Test Plan

- [x] All existing tests pass (pnpm test — 98.2%/95.2%/95.8%/98.3% coverage)
- [x] New tests added for: AIAssistantInsertFactory simplified constructor, FocusCapabilityFactory, ComposablePasteDestination paste flow, VscodeAdapter paste methods, padding call sites, dest build pipeline
- [x] pnpm test:release (VS Code host integration tests) — standard suite passes

Closes #547
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 13, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 43e03ffc-bbfe-4408-a705-a3807fea54da

📥 Commits

Reviewing files that changed from the base of the PR and between f732b06 and d8f8d34.

📒 Files selected for processing (2)
  • packages/rangelink-vscode-extension/src/__tests__/ide/vscode/VscodeAdapter.test.ts
  • packages/rangelink-vscode-extension/src/ide/vscode/VscodeAdapter.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/rangelink-vscode-extension/src/ide/vscode/VscodeAdapter.ts
  • packages/rangelink-vscode-extension/src/tests/ide/vscode/VscodeAdapter.test.ts

Walkthrough

Centralizes clipboard writes in ClipboardRouter (single write per operation); removes clipboard writes from insert factories; pre-applies smart padding at callers; introduces focus-to-paste and post-paste delays; simplifies paste APIs and wiring; updates ADR, tests, and QA cases accordingly.

Changes

Single Clipboard Write & Padding Refactor

Layer / File(s) Summary
ADR, constants, and exports
docs/ADR/0003-single-clipboard-write.md, docs/ADR/README.md, packages/rangelink-vscode-extension/src/constants/pasteTimingConstants.ts, packages/rangelink-vscode-extension/src/constants/index.ts
New ADR describing the refactor; adds FOCUS_TO_PASTE_DELAY_MS and CLIPBOARD_POST_PASTE_DELAY_MS; replaces chatPasteConstants export with pasteTimingConstants.
ClipboardRouter contract and preservation
packages/rangelink-vscode-extension/src/services/ClipboardRouter.ts
executeCopyAndSend now returns Promise<boolean> indicating whether a destination paste succeeded; copyAndSendToDestination passes that boolean into clipboard restoration eligibility.
Pre-apply smart padding at callers
packages/rangelink-vscode-extension/src/services/LinkGenerator.ts, .../FilePathPaster.ts, .../TerminalSelectionService.ts, .../TextSelectionPaster.ts, .../bookmarks/BookmarkService.ts
Moved applySmartPadding into these services so clipboard and send payloads are pre-padded; callers no longer pass paddingMode into destination send methods.
Destination API & manager
packages/rangelink-vscode-extension/src/destinations/PasteDestination.ts, .../ComposablePasteDestination.ts, .../PasteDestinationManager.ts
Public pasteLink/pasteContent signatures drop paddingMode; ComposablePasteDestination forwards raw (already-padded) text to inserters; PasteDestinationManager.isClipboardRestorationApplicable now accepts pasteSucceeded and consults destination failure instructions.
Insert factories (AI, manual, terminal)
packages/rangelink-vscode-extension/src/destinations/capabilities/insertFactories/aiAssistantInsertFactory.ts, .../manualPasteInsertFactory.ts, .../terminalInsertFactory.ts
Removed per-factory clipboard-preserver and multi-command paste logic. AIAssistantInsertFactory delegates to ideAdapter.pasteTextFromClipboard() and returns its boolean result. TerminalInsertFactory calls ideAdapter.pasteIntoTerminal() and ignores the text parameter. ManualPasteInsertFactory no longer writes to clipboard and logs readiness for manual paste.
IDE adapter paste methods
packages/rangelink-vscode-extension/src/ide/vscode/VscodeAdapter.ts, .../__tests__/ide/vscode/VscodeAdapter.test.ts
Added pasteTextFromClipboard(postPasteDelayMs?) which enforces pre/post delays and returns boolean success; replaced clipboard-mediated terminal paste with pasteIntoTerminal(terminal, options?). Tests updated to validate delays and new terminal paste behavior.
Focus capability, builders, wiring
packages/rangelink-vscode-extension/src/destinations/capabilities/FocusCapabilityFactory.ts, .../destinationBuilders.ts, packages/rangelink-vscode-extension/src/createWiringServices.ts
Removed clipboardPreserver from FocusCapabilityFactory; createAIAssistantCapability now accepts only focusCommands; built-in assistant definitions changed to focusCommands; helper createStandardAIAssistantInsertFactory added; wiring updated in createWiringServices.
Tests and QA artifacts
packages/rangelink-vscode-extension/src/__tests__/**, packages/rangelink-vscode-extension/src/__integration-tests__/**, packages/rangelink-vscode-extension/qa/qa-test-cases-v1.1.0.yaml
Extensive test updates: removed paddingMode args, simplified constructors, adapted to new adapter methods/logging/semantics. Added built-in assistant integration tests and new clipboard-preservation QA cases (e.g., clipboard-preservation-010).

Sequence Diagram (new flow):

sequenceDiagram
  participant User
  participant Router as ClipboardRouter
  participant Preserver as ClipboardPreserver
  participant Dest as PasteDestination
  participant Adapter as VscodeAdapter
  User->>Router: copyAndSend(paddedText)
  Router->>Preserver: preserve(currentClipboard)
  Router->>Router: write paddedText to clipboard
  Router->>Dest: sendFn(paddedText)
  Dest->>Dest: focus via focusCommands
  Dest->>Adapter: pasteTextFromClipboard()
  Adapter->>Adapter: execute workbench.action.clipboard.paste (pre/post delays)
  Adapter-->>Dest: pasteSucceeded (boolean)
  Dest-->>Router: return pasteSucceeded
  Router->>Preserver: maybe restore(previousClipboard) based on pasteSucceeded
  Preserver-->>Router: restored
  Router-->>User: done
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~55 minutes

Possibly related issues

  • #434: Send File Path QA/tests — FilePathPaster smart-padding and updated send-file-path tests align with this issue's objectives.
  • #559: Single clipboard write per R-* operation — this PR implements single-router clipboard ownership and the two-delay timing model described.
  • #558: Built-in assistant paste tests — changes to built-in assistant integration tests (cold/warm paste) overlap with this issue.

Possibly related PRs

"🐰 One clipboard write, so clean and true,
A single hop, no double-do.
Padding upstream, factories paste,
Router guards the clipboard's place.
A tidy hop — a rabbit's refactor haste!"

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title directly references the issue (#547) and clearly describes the main architectural change: centralizing clipboard write ownership so each R-* operation performs exactly one write.
Linked Issues check ✅ Passed All coding objectives from issue #547 are met: (1) centralized clipboard write in ClipboardRouter [#547], (2) removed clipboard writes from insert factories [#547], (3) pre-applied padding at call sites and removed performPaste() [#547], (4) two-delay model implemented [#547], (5) backward compatibility preserved [#547], (6) ADR-0003 and QA tests added [#547].
Out of Scope Changes check ✅ Passed All changes directly support the single-clipboard-write refactoring objective. Documentation (ADR-0003), QA test expansion, integration tests, timing constant refactoring, and factory simplifications are all in-scope to the core architectural change.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch issues/547

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 13, 2026

⚠️ QA Coverage Gap Detected

The PR introduces significant changes to clipboard handling and paste operations, which affect user experience and functionality. These changes require additional QA test cases to ensure proper behavior.

Suggested test cases:

  • Clipboard Management: Verify single clipboard write operation (automatable)
  • Clipboard Management: Verify paste command execution without clipboard write (automatable)
  • Clipboard Management: Verify padding is applied correctly during paste operations (manual — Requires verification of visual output and padding effects.)
  • Clipboard Management: Verify clipboard behavior with AI assistants (manual — Requires interaction with the AI assistant's interface.)

Generated by QA Gap Check (GPT-4o-mini via GitHub Models)

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/rangelink-vscode-extension/src/__tests__/destinations/ComposablePasteDestination.integration.test.ts (1)

30-413: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Add logger assertions to these behavior tests.

These tests validate core behavior paths (focus + paste success/failure), but they don’t assert emitted logs. Please add explicit logger assertions in the same test cases so behavior and observability contracts are both covered.

As per coding guidelines, "Always include logger assertions in tests that verify method behavior—logging provides critical visibility for debugging; consolidate logging assertions with behavior tests rather than creating separate logging-only tests".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/rangelink-vscode-extension/src/__tests__/destinations/ComposablePasteDestination.integration.test.ts`
around lines 30 - 413, Tests exercise ComposablePasteDestination.pasteLink with
TerminalFocusCapability, AIAssistantFocusCapability and EditorFocusCapability
but never assert logging; update each relevant test (those calling
ComposablePasteDestination.createForTesting and destination.pasteLink) to assert
mockLogger methods (e.g., mockLogger.info/error/debug) were called with expected
messages such as the configured jumpSuccessMessage on success and an error
message on failure, and verify call counts where appropriate (e.g., success path
-> one info log with 'Focused ...', failure paths -> one error log); use the
existing mockLogger instance passed into
TerminalInsertFactory/AIAssistantInsertFactory/EditorInsertFactory to locate and
add these assertions adjacent to existing behavior expects.
🧹 Nitpick comments (4)
packages/rangelink-vscode-extension/src/__tests__/ide/vscode/VscodeAdapter.test.ts (1)

740-762: ⚡ Quick win

Merge the logging assertion into the main behavior test.

The logging check is currently isolated in a logging-only test; please fold it into the behavior test so behavior and observability are verified together.

As per coding guidelines, “Always include logger assertions in tests that verify method behavior… consolidate logging assertions with behavior tests rather than creating separate logging-only tests.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/rangelink-vscode-extension/src/__tests__/ide/vscode/VscodeAdapter.test.ts`
around lines 740 - 762, Combine the logging assertion into the existing behavior
test for pasteIntoTerminal: in the "should show terminal and execute paste
command" test (which uses createMockTerminal and spies on
adapter.executeCommand), add the expect for mockLogger.debug to assert it was
called with { fn: 'VscodeAdapter.pasteIntoTerminal', terminalName: 'my-terminal'
} and message 'Executing terminal paste command' (use a mockTerminal created
with name 'my-terminal'), then remove the separate "should log terminal name
before executing command" test; keep references to pasteIntoTerminal,
adapter.executeCommand, mockTerminal, and mockLogger.debug when updating the
test.
packages/rangelink-vscode-extension/src/__tests__/destinations/capabilities/insertFactories/terminalInsertFactory.test.ts (1)

52-69: ⚡ Quick win

Include a logger assertion in the closure behavior test.

This behavior test verifies functional routing but omits logging assertions. Add at least one logger expectation (e.g., no error log) to keep behavior+observability covered together.

Proposed update
     expect(pasteIntoTerminalSpy).toHaveBeenNthCalledWith(1, terminal1);
     expect(pasteIntoTerminalSpy).toHaveBeenNthCalledWith(2, terminal2);
+    expect(mockLogger.error).not.toHaveBeenCalled();

As per coding guidelines, "Always include logger assertions in tests that verify method behavior—logging provides critical visibility for debugging; consolidate logging assertions with behavior tests rather than creating separate logging-only tests".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/rangelink-vscode-extension/src/__tests__/destinations/capabilities/insertFactories/terminalInsertFactory.test.ts`
around lines 52 - 69, The test for TerminalInsertFactory closure behavior should
also assert logging: after calling insertFn1 and insertFn2 verify the mockLogger
was used appropriately (for example assert mockLogger.error was not called, or
assert mockLogger.info/debug was called with the expected message), so update
the test that creates TerminalInsertFactory and uses
forTarget/insertFn1/insertFn2 to include at least one expectation on mockLogger
(referencing mockLogger, TerminalInsertFactory, forTarget, pasteIntoTerminal,
insertFn1/insertFn2) to cover observability alongside the existing routing
assertions.
packages/rangelink-vscode-extension/src/__tests__/destinations/capabilities/insertFactories/aiAssistantInsertFactory.test.ts (1)

13-24: ⚡ Quick win

Add logger assertion in the success-path behavior test.

This test validates method behavior but does not assert logging on the success path. Add a negative warning assertion (or explicit success-level assertion) to align with the test contract.

[Suggest patch]

Proposed update
     expect(result).toBe(true);
     expect(pasteSpy).toHaveBeenCalled();
+    expect(mockLogger.warn).not.toHaveBeenCalled();

As per coding guidelines, "Always include logger assertions in tests that verify method behavior—logging provides critical visibility for debugging; consolidate logging assertions with behavior tests rather than creating separate logging-only tests".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/rangelink-vscode-extension/src/__tests__/destinations/capabilities/insertFactories/aiAssistantInsertFactory.test.ts`
around lines 13 - 24, The test for AIAssistantInsertFactory.forTarget currently
verifies pasteTextFromClipboard was called and returned true but omits logger
assertions; update the test to assert logging on the success path by verifying
the mockLogger used in the test (mockLogger) either received an explicit
success/info call (e.g., mockLogger.info called with the expected success
message) or that mockLogger.warn was not called, ensuring you reference
AIAssistantInsertFactory, forTarget, and pasteTextFromClipboard in the change so
the test both validates behavior and the expected logging outcome.
packages/rangelink-vscode-extension/src/__tests__/destinations/PasteDestinationManager.test.ts (1)

2103-2115: ⚡ Quick win

Use the literal auto-paste result in the assertion.

toHaveBeenCalledWith(AutoPasteResult.Failure) ties this check to the enum symbol instead of the contract value the test is meant to freeze. Please assert the literal value here instead.

As per coding guidelines, "In Jest expect() assertions (not test setup/mocks), use string literals for our own enum values, user-facing text, and config keys to freeze contracts; enum values ARE allowed in test setup for type safety".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/rangelink-vscode-extension/src/__tests__/destinations/PasteDestinationManager.test.ts`
around lines 2103 - 2115, The test currently asserts mockDest.getUserInstruction
was called with the enum symbol AutoPasteResult.Failure; change that assertion
to use the literal contract value the enum represents (i.e., replace
toHaveBeenCalledWith(AutoPasteResult.Failure) with
toHaveBeenCalledWith('<literal-value>') where '<literal-value>' is the exact
string/primitive that AutoPasteResult.Failure maps to), keeping the rest of the
test (mock setup of getUserInstruction, manager.boundDestination, and the
isClipboardRestorationApplicable call) unchanged so the assertion freezes the
contract rather than the enum symbol.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/ADR/0003-single-clipboard-write.md`:
- Around line 37-47: The fenced diagram block using triple backticks that begins
with the ASCII diagram should include a language tag to satisfy MD040; update
the opening fence from ``` to ```text (i.e., add the language specifier "text")
so the block becomes ```text ... ``` and leave the diagram content unchanged.

In `@packages/rangelink-vscode-extension/qa/qa-test-cases-v1.1.0.yaml`:
- Line 708: Replace the unsupported key name focusPasteCommands in the Dummy AI
precondition with the suite's canonical custom-assistant config key (the same
key used throughout the YAML for custom assistants) so the "'Custom AI assistant
\"Dummy AI (Focus-Fail)\" configured..." line matches the actual config
contract; update the entry referenced by the clipboard-preservation-010
precondition to use that canonical key name.

In
`@packages/rangelink-vscode-extension/src/__integration-tests__/suite/sendFilePath.test.ts`:
- Around line 282-285: The test updates the global setting via
vscode.workspace.getConfiguration('rangelink').update('smartPadding.pasteFilePath',
'both', vscode.ConfigurationTarget.Global) but never restores it; capture the
previous value before calling update (const prev =
vscode.workspace.getConfiguration('rangelink').get('smartPadding.pasteFilePath'))
and restore it in the test teardown (use afterEach/after or a finally block) by
calling
vscode.workspace.getConfiguration('rangelink').update('smartPadding.pasteFilePath',
prev, vscode.ConfigurationTarget.Global) (or undefined to remove) so the global
state is not leaked to other tests.

In
`@packages/rangelink-vscode-extension/src/__tests__/destinations/capabilities/insertFactories/manualPasteInsertFactory.test.ts`:
- Around line 17-28: The test creates spies on createMockVscodeAdapter
(clipboardSpy, executeCommandSpy) but never injects that mock into the system
under test, so the assertions are non-proving; instead, instantiate or configure
ManualPasteInsertFactory to use the mocked adapter (e.g., pass mockAdapter into
the factory constructor or into ManualPasteInsertFactory.forTarget) so that the
spies on writeTextToClipboard and executeCommand are attached to the actual
adapter used by insertFn; target the symbols ManualPasteInsertFactory,
forTarget, createMockVscodeAdapter, writeTextToClipboard, and executeCommand so
the test calls insertFn(LINK_TEXT) using the injected mockAdapter and then
assert clipboardSpy/executeCommandSpy expectations.

In
`@packages/rangelink-vscode-extension/src/destinations/capabilities/insertFactories/terminalInsertFactory.ts`:
- Around line 3-5: Reorder the import statements to satisfy import/order: place
external packages first, then parent/internal paths, then sibling imports;
specifically import Logger from 'barebone-logger' first, then VscodeAdapter from
'../../../ide/vscode/VscodeAdapter', and finally InsertFactory from
'./InsertFactory' (keep all as type imports).

In `@packages/rangelink-vscode-extension/src/destinations/PasteDestination.ts`:
- Around line 73-103: The JSDoc for the paste contract is inconsistent: callers
now pre-populate the clipboard and destinations must focus before inserting, but
the implementation requirements still say "Focus destination after paste";
update the docs for both pasteLink (method pasteLink) and the corresponding
paste text method (e.g., pasteText) to require focusing the destination before
performing the paste (e.g., "Focus destination before paste (terminal.show(),
chat.open(), etc.)"), keep the other bullets (eligibility check, logging, return
false on failure/no throwing) unchanged, and ensure the wording clearly states
the focus-then-paste sequencing to avoid regressions.

In `@packages/rangelink-vscode-extension/src/services/ClipboardRouter.ts`:
- Around line 41-44: The code always passes true into
destinationManager.isClipboardRestorationApplicable, incorrectly assuming the
send succeeded; instead capture the actual boolean result from
executeCopyAndSend and use it when deciding restoration. Change the preserve
call so the first action runs executeCopyAndSend and returns its boolean result
(via the existing executeCopyAndSend function), and make the second predicate
call destinationManager.isClipboardRestorationApplicable(result) (or a closure
that references that result) so restoration policy reflects the real outcome.

---

Outside diff comments:
In
`@packages/rangelink-vscode-extension/src/__tests__/destinations/ComposablePasteDestination.integration.test.ts`:
- Around line 30-413: Tests exercise ComposablePasteDestination.pasteLink with
TerminalFocusCapability, AIAssistantFocusCapability and EditorFocusCapability
but never assert logging; update each relevant test (those calling
ComposablePasteDestination.createForTesting and destination.pasteLink) to assert
mockLogger methods (e.g., mockLogger.info/error/debug) were called with expected
messages such as the configured jumpSuccessMessage on success and an error
message on failure, and verify call counts where appropriate (e.g., success path
-> one info log with 'Focused ...', failure paths -> one error log); use the
existing mockLogger instance passed into
TerminalInsertFactory/AIAssistantInsertFactory/EditorInsertFactory to locate and
add these assertions adjacent to existing behavior expects.

---

Nitpick comments:
In
`@packages/rangelink-vscode-extension/src/__tests__/destinations/capabilities/insertFactories/aiAssistantInsertFactory.test.ts`:
- Around line 13-24: The test for AIAssistantInsertFactory.forTarget currently
verifies pasteTextFromClipboard was called and returned true but omits logger
assertions; update the test to assert logging on the success path by verifying
the mockLogger used in the test (mockLogger) either received an explicit
success/info call (e.g., mockLogger.info called with the expected success
message) or that mockLogger.warn was not called, ensuring you reference
AIAssistantInsertFactory, forTarget, and pasteTextFromClipboard in the change so
the test both validates behavior and the expected logging outcome.

In
`@packages/rangelink-vscode-extension/src/__tests__/destinations/capabilities/insertFactories/terminalInsertFactory.test.ts`:
- Around line 52-69: The test for TerminalInsertFactory closure behavior should
also assert logging: after calling insertFn1 and insertFn2 verify the mockLogger
was used appropriately (for example assert mockLogger.error was not called, or
assert mockLogger.info/debug was called with the expected message), so update
the test that creates TerminalInsertFactory and uses
forTarget/insertFn1/insertFn2 to include at least one expectation on mockLogger
(referencing mockLogger, TerminalInsertFactory, forTarget, pasteIntoTerminal,
insertFn1/insertFn2) to cover observability alongside the existing routing
assertions.

In
`@packages/rangelink-vscode-extension/src/__tests__/destinations/PasteDestinationManager.test.ts`:
- Around line 2103-2115: The test currently asserts mockDest.getUserInstruction
was called with the enum symbol AutoPasteResult.Failure; change that assertion
to use the literal contract value the enum represents (i.e., replace
toHaveBeenCalledWith(AutoPasteResult.Failure) with
toHaveBeenCalledWith('<literal-value>') where '<literal-value>' is the exact
string/primitive that AutoPasteResult.Failure maps to), keeping the rest of the
test (mock setup of getUserInstruction, manager.boundDestination, and the
isClipboardRestorationApplicable call) unchanged so the assertion freezes the
contract rather than the enum symbol.

In
`@packages/rangelink-vscode-extension/src/__tests__/ide/vscode/VscodeAdapter.test.ts`:
- Around line 740-762: Combine the logging assertion into the existing behavior
test for pasteIntoTerminal: in the "should show terminal and execute paste
command" test (which uses createMockTerminal and spies on
adapter.executeCommand), add the expect for mockLogger.debug to assert it was
called with { fn: 'VscodeAdapter.pasteIntoTerminal', terminalName: 'my-terminal'
} and message 'Executing terminal paste command' (use a mockTerminal created
with name 'my-terminal'), then remove the separate "should log terminal name
before executing command" test; keep references to pasteIntoTerminal,
adapter.executeCommand, mockTerminal, and mockLogger.debug when updating the
test.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: ac5bd727-a9fc-479a-bff1-c83514598741

📥 Commits

Reviewing files that changed from the base of the PR and between 243e9f0 and 9a3cb24.

📒 Files selected for processing (36)
  • docs/ADR/0003-single-clipboard-write.md
  • docs/ADR/README.md
  • packages/rangelink-vscode-extension/qa/qa-test-cases-v1.1.0.yaml
  • packages/rangelink-vscode-extension/src/__integration-tests__/suite/builtInAiAssistants.test.ts
  • packages/rangelink-vscode-extension/src/__integration-tests__/suite/customAiAssistants.test.ts
  • packages/rangelink-vscode-extension/src/__integration-tests__/suite/sendFilePath.test.ts
  • packages/rangelink-vscode-extension/src/__tests__/destinations/ComposablePasteDestination.integration.test.ts
  • packages/rangelink-vscode-extension/src/__tests__/destinations/ComposablePasteDestination.test.ts
  • packages/rangelink-vscode-extension/src/__tests__/destinations/DestinationRegistry.test.ts
  • packages/rangelink-vscode-extension/src/__tests__/destinations/PasteDestinationManager.test.ts
  • packages/rangelink-vscode-extension/src/__tests__/destinations/capabilities/FocusCapabilityFactory.test.ts
  • packages/rangelink-vscode-extension/src/__tests__/destinations/capabilities/insertFactories/aiAssistantInsertFactory.test.ts
  • packages/rangelink-vscode-extension/src/__tests__/destinations/capabilities/insertFactories/manualPasteInsertFactory.test.ts
  • packages/rangelink-vscode-extension/src/__tests__/destinations/capabilities/insertFactories/terminalInsertFactory.test.ts
  • packages/rangelink-vscode-extension/src/__tests__/helpers/destinationTestHelpers.ts
  • packages/rangelink-vscode-extension/src/__tests__/ide/vscode/VscodeAdapter.test.ts
  • packages/rangelink-vscode-extension/src/bookmarks/BookmarkService.ts
  • packages/rangelink-vscode-extension/src/bookmarks/__tests__/BookmarkService.test.ts
  • packages/rangelink-vscode-extension/src/constants/chatPasteConstants.ts
  • packages/rangelink-vscode-extension/src/constants/index.ts
  • packages/rangelink-vscode-extension/src/constants/pasteTimingConstants.ts
  • packages/rangelink-vscode-extension/src/createWiringServices.ts
  • packages/rangelink-vscode-extension/src/destinations/ComposablePasteDestination.ts
  • packages/rangelink-vscode-extension/src/destinations/PasteDestination.ts
  • packages/rangelink-vscode-extension/src/destinations/PasteDestinationManager.ts
  • packages/rangelink-vscode-extension/src/destinations/capabilities/FocusCapabilityFactory.ts
  • packages/rangelink-vscode-extension/src/destinations/capabilities/insertFactories/aiAssistantInsertFactory.ts
  • packages/rangelink-vscode-extension/src/destinations/capabilities/insertFactories/manualPasteInsertFactory.ts
  • packages/rangelink-vscode-extension/src/destinations/capabilities/insertFactories/terminalInsertFactory.ts
  • packages/rangelink-vscode-extension/src/destinations/destinationBuilders.ts
  • packages/rangelink-vscode-extension/src/ide/vscode/VscodeAdapter.ts
  • packages/rangelink-vscode-extension/src/services/ClipboardRouter.ts
  • packages/rangelink-vscode-extension/src/services/FilePathPaster.ts
  • packages/rangelink-vscode-extension/src/services/LinkGenerator.ts
  • packages/rangelink-vscode-extension/src/services/TerminalSelectionService.ts
  • packages/rangelink-vscode-extension/src/services/TextSelectionPaster.ts
💤 Files with no reviewable changes (3)
  • packages/rangelink-vscode-extension/src/constants/chatPasteConstants.ts
  • packages/rangelink-vscode-extension/src/bookmarks/BookmarkService.ts
  • packages/rangelink-vscode-extension/src/bookmarks/tests/BookmarkService.test.ts

Comment thread docs/ADR/0003-single-clipboard-write.md Outdated
Comment thread packages/rangelink-vscode-extension/qa/qa-test-cases-v1.1.0.yaml Outdated
Comment on lines +282 to 285
await vscode.workspace
.getConfiguration('rangelink')
.update('smartPadding.pasteFilePath', 'both', vscode.ConfigurationTarget.Global);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Reset smartPadding.pasteFilePath after this test to avoid global-state leakage.

Line 282 updates a global setting but the suite teardown doesn’t restore it, so later tests can inherit 'both' and become flaky/order-dependent.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/rangelink-vscode-extension/src/__integration-tests__/suite/sendFilePath.test.ts`
around lines 282 - 285, The test updates the global setting via
vscode.workspace.getConfiguration('rangelink').update('smartPadding.pasteFilePath',
'both', vscode.ConfigurationTarget.Global) but never restores it; capture the
previous value before calling update (const prev =
vscode.workspace.getConfiguration('rangelink').get('smartPadding.pasteFilePath'))
and restore it in the test teardown (use afterEach/after or a finally block) by
calling
vscode.workspace.getConfiguration('rangelink').update('smartPadding.pasteFilePath',
prev, vscode.ConfigurationTarget.Global) (or undefined to remove) so the global
state is not leaked to other tests.

Comment thread packages/rangelink-vscode-extension/src/services/ClipboardRouter.ts
couimet added 2 commits May 12, 2026 22:38
…ts, correct docs

Fixed the clipboard preservation guard so it receives the actual paste outcome instead of hardcoded `true`, preventing stale clipboard restoration on failed pastes. Added logger assertions to integration tests, updated enum references to string literals per T003, and fixed a minor docs issue.

Benefits:
- Clipboard is only restored when paste actually succeeded — failed pastes no longer inadvertently overwrite user clipboard
- Integration test logger assertions provide regression protection for diagnostic logging
- ADR code fence now has correct language tag for proper rendering at docs.rangelink.io
- QA YAML uses renamed `focusCommands` field, matching current code

Ignored Feedback:
- Adding `settingsHelpers.resetRangelinkSettings` to send-file-path integration test: the `standardSuite.setup()` wrapper already calls `resetRangelinkSettings()` before every test in the suite, so the explicit call is redundant

Ref: #556 (review)
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
packages/rangelink-vscode-extension/src/__tests__/destinations/capabilities/insertFactories/aiAssistantInsertFactory.test.ts (1)

23-23: ⚡ Quick win

Prefer .toHaveBeenCalledWith() to explicitly assert no parameters.

Using .toHaveBeenCalledWith() instead of .toHaveBeenCalled() makes the expectation explicit that pasteTextFromClipboard is called with no arguments, improving clarity and robustness to signature changes.

♻️ Suggested improvement
-    expect(pasteSpy).toHaveBeenCalled();
+    expect(pasteSpy).toHaveBeenCalledWith();
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/rangelink-vscode-extension/src/__tests__/destinations/capabilities/insertFactories/aiAssistantInsertFactory.test.ts`
at line 23, Replace the generic call assertion in the test with an explicit
no-arguments assertion: update the expectation that verifies
pasteTextFromClipboard (the spy referenced as pasteSpy in
aiAssistantInsertFactory.test.ts) was invoked to use toHaveBeenCalledWith() with
no parameters so the test asserts the function was called with an empty argument
list rather than just being called.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In
`@packages/rangelink-vscode-extension/src/__tests__/destinations/capabilities/insertFactories/aiAssistantInsertFactory.test.ts`:
- Line 23: Replace the generic call assertion in the test with an explicit
no-arguments assertion: update the expectation that verifies
pasteTextFromClipboard (the spy referenced as pasteSpy in
aiAssistantInsertFactory.test.ts) was invoked to use toHaveBeenCalledWith() with
no parameters so the test asserts the function was called with an empty argument
list rather than just being called.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 04bd6eac-618d-42e1-83a1-6b01ba1d9ec0

📥 Commits

Reviewing files that changed from the base of the PR and between 9a3cb24 and 22beb71.

📒 Files selected for processing (11)
  • docs/ADR/0003-single-clipboard-write.md
  • packages/rangelink-vscode-extension/qa/qa-test-cases-v1.1.0.yaml
  • packages/rangelink-vscode-extension/src/__tests__/destinations/ComposablePasteDestination.integration.test.ts
  • packages/rangelink-vscode-extension/src/__tests__/destinations/PasteDestinationManager.test.ts
  • packages/rangelink-vscode-extension/src/__tests__/destinations/capabilities/insertFactories/aiAssistantInsertFactory.test.ts
  • packages/rangelink-vscode-extension/src/__tests__/destinations/capabilities/insertFactories/manualPasteInsertFactory.test.ts
  • packages/rangelink-vscode-extension/src/__tests__/destinations/capabilities/insertFactories/terminalInsertFactory.test.ts
  • packages/rangelink-vscode-extension/src/__tests__/ide/vscode/VscodeAdapter.test.ts
  • packages/rangelink-vscode-extension/src/destinations/PasteDestination.ts
  • packages/rangelink-vscode-extension/src/destinations/capabilities/insertFactories/terminalInsertFactory.ts
  • packages/rangelink-vscode-extension/src/services/ClipboardRouter.ts
🚧 Files skipped from review as they are similar to previous changes (6)
  • packages/rangelink-vscode-extension/src/destinations/PasteDestination.ts
  • packages/rangelink-vscode-extension/src/tests/ide/vscode/VscodeAdapter.test.ts
  • packages/rangelink-vscode-extension/qa/qa-test-cases-v1.1.0.yaml
  • packages/rangelink-vscode-extension/src/tests/destinations/PasteDestinationManager.test.ts
  • packages/rangelink-vscode-extension/src/tests/destinations/capabilities/insertFactories/terminalInsertFactory.test.ts
  • packages/rangelink-vscode-extension/src/tests/destinations/ComposablePasteDestination.integration.test.ts

couimet added 3 commits May 13, 2026 10:49
…istant paste regression

The single-clipboard-write refactoring accidentally dropped FOCUS_TO_PASTE_DELAY_MS from the AI assistant paste path. The old aiAssistantInsertFactory had this 200ms delay between clipboard write and paste command execution — it gave the Electron clipboard write time to propagate from the extension host process through the main process to the webview's renderer. Without it, editor.action.clipboardPasteAction fires before the webview can read the clipboard content, and the paste lands empty even though all code-side signals report success.

Extracted a delay(ms) method in VscodeAdapter that wraps setTimeout. pasteTextFromClipboard calls this.delay(FOCUS_TO_PASTE_DELAY_MS) before executing the paste command, then this.delay(postPasteDelayMs) after a successful paste. Tests spy on delay() via jest.spyOn to verify the delays are enforced in the correct order without fighting fake timers.

Benefits:
- Claude Code, Cursor AI, GitHub Copilot Chat, and custom AI assistants now reliably receive pasted content
- Pre-paste delay (FOCUS_TO_PASTE_DELAY_MS, 200ms) ensures clipboard propagation across Electron IPC boundary
- Post-paste delay (CLIPBOARD_POST_PASTE_DELAY_MS, 200ms) remains intact for clipboard restoration safety
- delay() method keeps production code clean (no testing parameters); tests spy on it for deterministic verification
…ion in AIAssistantInsertFactory test

.toHaveBeenCalled() only asserts the spy was invoked. .toHaveBeenCalledWith() asserts it was called with an empty argument list, which is stricter and catches regressions where the signature changes to pass unintended arguments.

Benefits:
- Explicit no-arguments contract for pasteTextFromClipboard call site
- Stricter assertion catches unintended signature drift

Ref: #556 (review)
@couimet
Copy link
Copy Markdown
Owner Author

couimet commented May 13, 2026

@coderabbitai review -
image
shows you skipped the last run

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 13, 2026

The Comment image shows the GitHub PR checks panel with "CodeRabbit — Review skipped" listed alongside two passing CI checks ("CI / Test & Validate" and "QA Coverage Gap Check"). All checks have passed and there are no merge conflicts.

@couimet — Got it! Triggering a review of PR #556 now.

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@couimet
Copy link
Copy Markdown
Owner Author

couimet commented May 13, 2026

@coderabbitai Do you see #556 (comment) as actionable ? If so, can you create a new GitHub issue on this repo with a detailed plan on how you'd proceed with the implementation of those detected gaps ?

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 13, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

Let me first fetch the content of that comment.


🏁 Script executed:

#!/bin/bash
# Fetch the specific comment content
gh api repos/couimet/rangeLink/issues/comments/4436571116 --jq '{body: .body, user: .user.login, created_at: .created_at}'

Length of output: 923


Yes, the comment is actionable. It was posted by the QA Gap Check bot and identifies four concrete test gaps stemming from the single-clipboard-write refactor in this PR. Let me create the issue now.


🏁 Script executed:

#!/bin/bash
gh issue create \
  --repo couimet/rangeLink \
  --title "[qa] Address QA Coverage Gaps from PR `#556` — Single Clipboard Write Refactor" \
  --body "## Context

PR `#556` refactored clipboard handling so \`ClipboardRouter\` has sole ownership of each clipboard write (single write per R-\* operation). Insert factories (\`AIAssistantInsertFactory\`, \`TerminalInsertFactory\`, \`ManualPasteInsertFactory\`) no longer write to the clipboard; they only execute paste commands. Smart padding was moved upstream to four call sites (\`LinkGenerator\`, \`TextSelectionPaster\`, \`FilePathPaster\`, \`TerminalSelectionService\`).

The QA Gap Check bot flagged four missing test cases in [this comment](https://github.com/couimet/rangeLink/pull/556#issuecomment-4436571116). This issue tracks their implementation.

Requested by `@couimet`.

---

## Gap 1 — Automatable: Verify single clipboard write operation

**Goal:** Assert that \`ClipboardRouter.copyAndSendToDestination\` calls \`vscode.env.clipboard.writeText\` (or the equivalent adapter method) **exactly once** per R-\* operation, regardless of destination type.

**Proposed implementation:**

- Add unit tests in \`ClipboardRouter.test.ts\` (or a new \`ClipboardRouter.single-write.test.ts\`).
- Spy on \`clipboardPreserver.preserve\` / \`writeTextToClipboard\` and assert \`toHaveBeenCalledTimes(1)\` for each destination type: editor, terminal, AI assistant, manual paste.
- Cover the \`executeCopyAndSend\` internal path for the \`no-destination\` and \`self-paste\` early-exit branches (assert clipboard write still happens exactly once via \`ClipboardPreserver\`).
- Confirm the \`pasteSucceeded\` boolean returned by \`executeCopyAndSend\` is forwarded correctly into \`isClipboardRestorationApplicable\`.

---

## Gap 2 — Automatable: Verify paste command execution without clipboard write

**Goal:** Assert that insert factories execute only paste commands and make zero clipboard write calls.

**Proposed implementation (per factory):**

### \`AIAssistantInsertFactory\`
- File: \`aiAssistantInsertFactory.test.ts\`
- Spy on \`vscodeAdapter.writeTextToClipboard\` and assert \`not.toHaveBeenCalled()\`.
- Spy on \`vscodeAdapter.pasteTextFromClipboard\` and assert \`toHaveBeenCalledTimes(1)\`.
- Verify \`insert()\` returns \`true\` on success and \`false\` on \`pasteTextFromClipboard\` rejection (already partially covered — add the no-write assertion).

### \`TerminalInsertFactory\`
- File: \`terminalInsertFactory.test.ts\`
- Spy on \`vscodeAdapter.writeTextToClipboard\` and assert \`not.toHaveBeenCalled()\`.
- Spy on \`vscodeAdapter.pasteIntoTerminal\` and assert \`toHaveBeenCalledTimes(1)\` with the correct terminal.
- Verify the \`text\` argument passed to the returned closure is **ignored** (i.e., \`pasteIntoTerminal\` is called with only the terminal reference).

### \`ManualPasteInsertFactory\`
- File: \`manualPasteInsertFactory.test.ts\`
- Spy on \`vscodeAdapter.writeTextToClipboard\` (if adapter were passed — confirm it is NOT in the constructor) and assert it is never called.
- Assert \`insert()\` resolves \`true\` and logs \`'Link ready for manual paste'\`.

---

## Gap 3 — Manual: Verify padding is applied correctly during paste operations

**Goal:** Confirm that smart padding is pre-applied at the four upstream call sites and that destinations receive already-padded content.

**QA test case additions** (add to \`qa-test-cases-v1.1.0-003.yaml\` or a follow-up YAML):

| ID | Scenario | Expected result |
|----|----------|-----------------|
| \`padding-001\` | R-L on a text selection with smart padding enabled; destination = editor | Pasted text has a leading and trailing space (e.g., \` mySelection \`) |
| \`padding-002\` | R-L on a file path with smart padding enabled; destination = terminal | Terminal receives the quoted, padded path (e.g., \` '/path/to/file' \`) |
| \`padding-003\` | R-L RangeLink with smart padding enabled; destination = AI assistant | Clipboard contains padded link; AI chat input receives padded link |
| \`padding-004\` | R-L on terminal selection with smart padding enabled; destination = editor | Editor receives padded terminal-selected text |
| \`padding-005\` | Smart padding disabled (setting off); destination = any | No leading/trailing spaces added to clipboard or destination |

**Suggested unit test additions:**
- In \`LinkGenerator.test.ts\`: assert \`applySmartPadding\` is called and the padded value is both the \`clipboard\` payload and the \`send\` payload in the \`ClipboardRouter\` strategy.
- In \`FilePathPaster.test.ts\`, \`TextSelectionPaster.test.ts\`, \`TerminalSelectionService.test.ts\`: same pattern — spy on \`applySmartPadding\` and assert the padded value propagates to \`sendFn\` / \`sendTextToDestination\` without a \`paddingMode\` argument.

---

## Gap 4 — Manual: Verify clipboard behavior with AI assistants

**Goal:** End-to-end verification that the clipboard write/restore lifecycle works correctly across the three built-in AI assistants under cold and warm paste flows.

**QA test case additions** (extend the \"Built-in AI Assistants\" section already added in PR `#556`):

| ID | Scenario | Expected result |
|----|----------|-----------------|
| \`clipboard-ai-001\` | Cold paste → Claude Code; prior clipboard content exists | After paste, prior clipboard content is restored |
| \`clipboard-ai-002\` | Warm paste → Claude Code (panel already open) | Single clipboard write; prior content restored after \`CLIPBOARD_POST_PASTE_DELAY_MS\` |
| \`clipboard-ai-003\` | Cold paste → Cursor AI; prior clipboard content exists | After paste, prior clipboard content is restored |
| \`clipboard-ai-004\` | Warm paste → Cursor AI | Single clipboard write; prior content restored |
| \`clipboard-ai-005\` | Cold paste → GitHub Copilot Chat; prior clipboard content exists | After paste, prior clipboard content is restored |
| \`clipboard-ai-006\` | Warm paste → GitHub Copilot Chat | Single clipboard write; prior content restored |
| \`clipboard-ai-007\` | AI assistant focus/paste command throws; prior clipboard content exists | RangeLink remains in clipboard (guided manual-paste); prior content NOT yet restored (user must paste manually) |
| \`clipboard-ai-008\` | Two rapid R-\* operations to same AI assistant | Second write does not clobber first; only the last write is in clipboard before paste |

**Suggested integration test additions** (extend \`builtInAiAssistants.test.ts\`):
- After cold/warm paste, read clipboard via \`vscode.env.clipboard.readText()\` and assert prior content is restored.
- Assert log emission of clipboard restoration (or its absence on failure paths).

---

## Acceptance Criteria

- [ ] \`ClipboardRouter\` unit tests assert single clipboard write per operation for all destination types.
- [ ] \`AIAssistantInsertFactory\`, \`TerminalInsertFactory\`, \`ManualPasteInsertFactory\` tests assert zero clipboard writes inside the factory.
- [ ] QA YAML updated with padding scenarios (Gap 3) covering all four upstream call sites.
- [ ] QA YAML updated with AI assistant clipboard-lifecycle scenarios (Gap 4) covering cold/warm flows and failure path.
- [ ] Optional: integration tests in \`builtInAiAssistants.test.ts\` assert clipboard restoration after successful paste.

---

## References

- PR `#556` — Single clipboard write per R-\* operation
- QA Gap Check comment: https://github.com/couimet/rangeLink/pull/556#issuecomment-4436571116
- ADR-0003: \`docs/ADR/0003-single-clipboard-write.md\`
"

Length of output: 48


@couimet — Yes, the comment is fully actionable. It was posted by the QA Gap Check bot and identified four concrete coverage gaps introduced by the single-clipboard-write refactor in PR #556.

I've created issue #563 with a detailed implementation plan. Here's a summary of what's covered:

Gap Type Plan
Verify single clipboard write Automatable Unit tests in ClipboardRouter.test.ts — spy on writeTextToClipboard / clipboardPreserver.preserve, assert toHaveBeenCalledTimes(1) across all destination types and early-exit branches
Verify factories don't write clipboard Automatable Add not.toHaveBeenCalled() assertions on writeTextToClipboard in aiAssistantInsertFactory.test.ts, terminalInsertFactory.test.ts, and manualPasteInsertFactory.test.ts
Verify padding applied at call sites Manual + Unit 5 QA YAML cases covering LinkGenerator, TextSelectionPaster, FilePathPaster, TerminalSelectionService; unit tests spy on applySmartPadding and assert the padded value reaches sendFn with no paddingMode argument
Verify clipboard lifecycle with AI assistants Manual + Integration 8 QA YAML cases (cold/warm/failure/rapid-fire × Claude Code / Cursor / Copilot Chat); extend builtInAiAssistants.test.ts to assert prior clipboard content is restored after paste

@couimet couimet merged commit db5ea3e into main May 13, 2026
3 checks passed
@couimet couimet deleted the issues/547 branch May 13, 2026 17:28
couimet added a commit that referenced this pull request May 14, 2026
…llback for AI assistant webview delivery

## Summary

The "single clipboard write per R-* operation" refactor (PR #556, `issues/547`) inadvertently reduced a three-command paste fallback chain to a single `editor.action.clipboardPasteAction`. That command only reaches VS Code text editors — for webview-hosted chat inputs (Claude Code, Cursor AI, GitHub Copilot Chat, custom AI Tier 1/2 webview targets) the system-level fallbacks `execPaste` / `paste` were what actually delivered content. Without them the dispatch returned success, but no content arrived in the chat input. This PR restores the fallback chain, logs the successful command for diagnostics, and converts the silent-pass `claude-code-004/005` tests to verdict-based assertions so the same class of regression cannot land green in the future.

## Changes

- Renamed `src/constants/pasteTimingConstants.ts` → `aiAssistantPasteConstants.ts` and added `AI_ASSISTANT_PASTE_COMMANDS = ['editor.action.clipboardPasteAction', 'execPaste', 'paste']` alongside the existing delay constants. The file now correctly reflects its scope (was a name-vs-content drift from #556 that removed the commands).
- `VscodeAdapter.pasteTextFromClipboard()` now iterates `AI_ASSISTANT_PASTE_COMMANDS` and stops on the first success. The `command` field is included in the `Clipboard paste succeeded` log so tests can assert which command delivered the paste. Per-command failures log at `debug` with `Paste command failed, trying next`; an all-failed path logs at `warn` with `allCommandsFailed: true`.
- Inline JSDoc on `pasteTextFromClipboard` explains WHY the fallback chain exists — non-obvious enough that the next refactor would otherwise repeat the same mistake.
- Added `assertPasteCommandLogged(lines, { command })` helper to `logBasedUiAssertions.ts` for integration-test assertions on which paste command succeeded.
- Updated `VscodeAdapter.test.ts` `pasteTextFromClipboard` describe block: 5 tests now cover the fallback chain (first-success, fall-through-to-second, all-fail, default post-paste delay, custom post-paste delay). All assertions include the `command` field per the test contract.
- Converted `claude-code-004` and `claude-code-005` from `waitForHuman` to `waitForHumanVerdict`. The silent-pass trap is closed for these specific TCs (broader audit tracked in #560). Setup is now programmatic (file open + selection via `vscode.workspace.openTextDocument` + `editor.selection`) and the send fires via `executeCommand(CMD_COPY_LINK_RELATIVE)` rather than asking the human to press the chord.
- Added the wave-1 Claude Code TCs from #483: `claude-code-001` (automated picker-inspection in `standardSuite`, uses `openAndDismiss` from #561), `claude-code-002` (assisted send-delivery with `waitForHumanVerdict`), and the deletion of `claude-code-003` (superseded by `claude-code-005`'s richer warm-path scenario). YAML flips for 001 → `automated: true` and 002 → `automated: assisted`.

## Key Discoveries

- The 547 refactor's "Test Plan" claimed `pnpm test:release passes` — technically true, but the with-extensions tests were not in that baseline AND `claude-code-004/005` used `waitForHuman` (Cancel-to-resolve) rather than `waitForHumanVerdict` (PASS/FAIL-to-resolve). Code-side log assertions stayed green even though the webview received nothing. The regression therefore landed undetected. The verdict-helper conversion in this PR closes that specific silent-pass trap; the broader audit lives in #560.
- The `dummy-ai-extension` fixture is already webview-based and exposes `dummyAi.getText` for reading what landed in the webview textareas — the missing piece for a fully automated webview paste-delivery test. Adding one is blocked on a new programmatic bind command for custom AI assistants (the current bind path requires a human picker selection); that command is out of scope here and will be filed as a follow-up issue.
- The 547 paste pipeline was audited beyond the bug-fix change: padding is correctly pre-applied at all 4 call sites (`LinkGenerator`, `TextSelectionPaster`, `FilePathPaster`, `TerminalSelectionService`); the two-delay model is used consistently in the single `pasteTextFromClipboard` chokepoint; the `focusCommands` fallback iteration in `AIAssistantFocusCapability` is intact. No additional regressions found.

## Test Plan

- [x] All existing unit tests pass — 107 suites, 228 tests in `VscodeAdapter.test.ts` (including 5 new fallback tests), 98.42% statement coverage.
- [x] `pnpm test:release:automated` — 165 of 175 passing. The 10 failures are pre-existing flakes (`langswitch-binding-*`, `smart-padding-*`, `stale-viewcolumn-001`) on non-AI-assistant code paths, same as noted in PR #561's description. My changes don't touch terminal/editor paste paths.
- [x] QA coverage validator passes — 121 automated / 115 assisted / 12 false markers all align with integration tests.
- [ ] **Assisted verification (definitive bug-fix proof):** `pnpm test:release:with-extensions --grep "claude-code-00[245]"` runs `claude-code-002` (cleanest reproduction), `claude-code-004` (cold panel paste), and `claude-code-005` (cold→warm sequence). Each prompts PASS/FAIL on whether the RangeLink actually arrived in Claude Code's chat input. All three must produce PASS verdicts.
- [ ] CI `test-with-extensions` job (from #564) runs green on the PR.

## Related

- Closes #559
- Resolves the regression introduced by #556
- Companion to #560 (silent-pass-trap audit) — this PR fixes the 547-introduced subset (`claude-code-004/005`); the broader audit remains open.
- Folds in the Wave-1 Claude Code work from #483 (`claude-code-001` / `claude-code-002` integration tests + the `claude-code-003` deletion).
- Complements #563 — this PR fixes the bug; #563 backfills coverage that would have caught it. They land independently; whichever merges first, the other rebases.
- Follow-up issue to be filed: a programmatic bind command for custom AI assistants (currently the bind path requires a human picker selection), which would unblock a fully automated webview paste-delivery test using the dummy-ai-extension's `dummyAi.getText` reader.
couimet added a commit that referenced this pull request May 14, 2026
…llback for AI assistant webview delivery (#566)

## Summary

The "single clipboard write per R-* operation" refactor (PR #556, `issues/547`) inadvertently reduced a three-command paste fallback chain to a single `editor.action.clipboardPasteAction`. That command only reaches VS Code text editors — for webview-hosted chat inputs (Claude Code, Cursor AI, GitHub Copilot Chat, custom AI Tier 1/2 webview targets) the system-level fallbacks `execPaste` / `paste` were what actually delivered content. Without them the dispatch returned success, but no content arrived in the chat input. This PR restores the fallback chain, logs the successful command for diagnostics, and converts the silent-pass `claude-code-004/005` tests to verdict-based assertions so the same class of regression cannot land green in the future.

## Changes

- Renamed `src/constants/pasteTimingConstants.ts` → `aiAssistantPasteConstants.ts` and added `AI_ASSISTANT_PASTE_COMMANDS = ['editor.action.clipboardPasteAction', 'execPaste', 'paste']` alongside the existing delay constants. The file now correctly reflects its scope (was a name-vs-content drift from #556 that removed the commands).
- `VscodeAdapter.pasteTextFromClipboard()` now iterates `AI_ASSISTANT_PASTE_COMMANDS` and stops on the first success. The `command` field is included in the `Clipboard paste succeeded` log so tests can assert which command delivered the paste. Per-command failures log at `debug` with `Paste command failed, trying next`; an all-failed path logs at `warn` with `allCommandsFailed: true`.
- Inline JSDoc on `pasteTextFromClipboard` explains WHY the fallback chain exists — non-obvious enough that the next refactor would otherwise repeat the same mistake.
- Added `assertPasteCommandLogged(lines, { command })` helper to `logBasedUiAssertions.ts` for integration-test assertions on which paste command succeeded.
- Updated `VscodeAdapter.test.ts` `pasteTextFromClipboard` describe block: 5 tests now cover the fallback chain (first-success, fall-through-to-second, all-fail, default post-paste delay, custom post-paste delay). All assertions include the `command` field per the test contract.
- Converted `claude-code-004` and `claude-code-005` from `waitForHuman` to `waitForHumanVerdict`. The silent-pass trap is closed for these specific TCs (broader audit tracked in #560). Setup is now programmatic (file open + selection via `vscode.workspace.openTextDocument` + `editor.selection`) and the send fires via `executeCommand(CMD_COPY_LINK_RELATIVE)` rather than asking the human to press the chord.
- Added the wave-1 Claude Code TCs from #483: `claude-code-001` (automated picker-inspection in `standardSuite`, uses `openAndDismiss` from #561), `claude-code-002` (assisted send-delivery with `waitForHumanVerdict`), and the deletion of `claude-code-003` (superseded by `claude-code-005`'s richer warm-path scenario). YAML flips for 001 → `automated: true` and 002 → `automated: assisted`.

## Key Discoveries

- The 547 refactor's "Test Plan" claimed `pnpm test:release passes` — technically true, but the with-extensions tests were not in that baseline AND `claude-code-004/005` used `waitForHuman` (Cancel-to-resolve) rather than `waitForHumanVerdict` (PASS/FAIL-to-resolve). Code-side log assertions stayed green even though the webview received nothing. The regression therefore landed undetected. The verdict-helper conversion in this PR closes that specific silent-pass trap; the broader audit lives in #560.
- The `dummy-ai-extension` fixture is already webview-based and exposes `dummyAi.getText` for reading what landed in the webview textareas — the missing piece for a fully automated webview paste-delivery test. Adding one is blocked on a new programmatic bind command for custom AI assistants (the current bind path requires a human picker selection); that command is out of scope here and will be filed as a follow-up issue.
- The 547 paste pipeline was audited beyond the bug-fix change: padding is correctly pre-applied at all 4 call sites (`LinkGenerator`, `TextSelectionPaster`, `FilePathPaster`, `TerminalSelectionService`); the two-delay model is used consistently in the single `pasteTextFromClipboard` chokepoint; the `focusCommands` fallback iteration in `AIAssistantFocusCapability` is intact. No additional regressions found.

## Test Plan

- [x] All existing unit tests pass — 107 suites, 228 tests in `VscodeAdapter.test.ts` (including 5 new fallback tests), 98.42% statement coverage.
- [x] `pnpm test:release:automated` — 165 of 175 passing. The 10 failures are pre-existing flakes (`langswitch-binding-*`, `smart-padding-*`, `stale-viewcolumn-001`) on non-AI-assistant code paths, same as noted in PR #561's description. My changes don't touch terminal/editor paste paths.
- [x] QA coverage validator passes — 121 automated / 115 assisted / 12 false markers all align with integration tests.
- [ ] **Assisted verification (definitive bug-fix proof):** `pnpm test:release:with-extensions --grep "claude-code-00[245]"` runs `claude-code-002` (cleanest reproduction), `claude-code-004` (cold panel paste), and `claude-code-005` (cold→warm sequence). Each prompts PASS/FAIL on whether the RangeLink actually arrived in Claude Code's chat input. All three must produce PASS verdicts.
- [ ] CI `test-with-extensions` job (from #564) runs green on the PR.

## Related

- Closes #559
- Resolves the regression introduced by #556
- Companion to #560 (silent-pass-trap audit) — this PR fixes the 547-introduced subset (`claude-code-004/005`); the broader audit remains open.
- Folds in the Wave-1 Claude Code work from #483 (`claude-code-001` / `claude-code-002` integration tests + the `claude-code-003` deletion).
- Complements #563 — this PR fixes the bug; #563 backfills coverage that would have caught it. They land independently; whichever merges first, the other rebases.
- Follow-up issue to be filed: a programmatic bind command for custom AI assistants (currently the bind path requires a human picker selection), which would unblock a fully automated webview paste-delivery test using the dummy-ai-extension's `dummyAi.getText` reader.
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.

Single clipboard write per R-* operation

1 participant