Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/rangelink-vscode-extension/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Changed

- **Single status bar message when binding and sending in one step via the destination picker**, instead of two back-to-back messages. The merged message reads "Bound to <destination> — <link> sent". (#621)

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

Potential QA coverage gap for the merged bind+send UX

This PR changes user-visible notification behavior, but no QA YAML updates are present in the reviewed changes. Please add/confirm QA cases in packages/rangelink-vscode-extension/qa/qa-test-cases-*.yaml for:

  • unbound picker send (R-V/R-L/R-F) → exactly one merged status bar message,
  • already-bound send → existing send-only behavior preserved,
  • bind-only action → bind-only message preserved,
  • rebind paths → no merged send text leakage.

Suggested schema examples:

  • id: qa-bind-send-merged-001, feature: status_bar_feedback, scenario: unbound picker send merges bind+send, preconditions, steps, expected_result, automated.
  • id: qa-bind-send-merged-002, feature: status_bar_feedback, scenario: already-bound send remains send-only, ...

As per coding guidelines, "When reviewing changes in the VS Code extension source code... Flag a potential QA coverage gap if ... user-visible behavior changes and QA YAML is not updated."

🤖 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/CHANGELOG.md` at line 14, Add QA test
cases to the qa-test-cases-*.yaml files to cover the merged bind+send status bar
notification behavior. Create test cases with schema fields (id, feature:
status_bar_feedback, scenario, preconditions, steps, expected_result, automated)
for four scenarios: unbound picker send (R-V/R-L/R-F) producing exactly one
merged message in the format "Bound to <destination> — <link> sent",
already-bound send preserving the existing send-only message behavior, bind-only
action preserving the bind-only message, and rebind paths ensuring no send text
appears in bind-only contexts. Use id naming like qa-bind-send-merged-001,
qa-bind-send-merged-002, etc. for each test case.

Source: Coding guidelines


### Fixed

- **R-F (paste current file path) now works for any active tab that maps to a file** — image previews, notebooks, and diff views — not just text editors. (#643)
Expand Down
32 changes: 32 additions & 0 deletions packages/rangelink-vscode-extension/qa/qa-test-cases.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -759,6 +759,22 @@ test_cases:
expected_result: "'RangeLink: Unbind' is not visible in the menu (or is disabled)."
automated: assisted

- id: context-menus-explorer-006
labels:
- explorer
feature: 'context-menus'
scenario: 'Explorer (unbound): "Send File Path" opens picker, binds selected terminal and sends absolute path in one step'
expected_result: 'A single merged status bar message appears: "Bound to Terminal (...) — File path sent". Terminal receives the absolute path.'
automated: assisted

- id: context-menus-explorer-007
labels:
- explorer
feature: 'context-menus'
scenario: 'Explorer (unbound): "Send Relative File Path" opens picker, binds selected terminal and sends relative path in one step'
expected_result: 'A single merged status bar message appears: "Bound to Terminal (...) — File path sent". Terminal receives the workspace-relative path.'
automated: assisted

- id: context-menus-editor-tab-001
labels:
- editor-tab
Expand Down Expand Up @@ -791,6 +807,22 @@ test_cases:
expected_result: 'Destination is unbound. Confirmation notification.'
automated: assisted

- id: context-menus-editor-tab-005
labels:
- editor-tab
feature: 'context-menus'
scenario: 'Editor tab (unbound): "Send File Path" opens picker, binds selected terminal and sends absolute path in one step'
expected_result: 'A single merged status bar message appears: "Bound to Terminal (...) — File path sent". Terminal receives the absolute path.'
automated: assisted

- id: context-menus-editor-tab-006
labels:
- editor-tab
feature: 'context-menus'
scenario: 'Editor tab (unbound): "Send Relative File Path" opens picker, binds selected terminal and sends relative path in one step'
expected_result: 'A single merged status bar message appears: "Bound to Terminal (...) — File path sent". Terminal receives the workspace-relative path.'
automated: assisted

- id: context-menus-editor-content-001
labels:
- clipboard
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -168,4 +168,99 @@ standardSuite('Context Menus — Editor Tab', (ss) => {

ss.log('✓ Editor-tab "Unbind" fired the unbind path; context key flipped to false');
});

test('[assisted] context-menus-editor-tab-005: Editor tab "Send File Path" (unbound) opens picker and sends absolute path to selected terminal', async () => {
const uri = await ss.createAndOpenFile('ctxmenu-tab-005', FILE_CONTENT);
const fn = path.basename(uri.fsPath);

const terminalName = 'rl-ctxmenu-tab-005';
const capturing = await ss.createCapturingTerminal(terminalName);

ss.expectStatusBarMessages([
'✓ RangeLink: Bound to Terminal ("rl-ctxmenu-tab-005") — File path sent',
]);
ss.expectContextKeys({
'rangelink.isActiveTerminalBindable': true,
'rangelink.isActiveTerminalPasteDestination': true,
'rangelink.isBound': true,
});

const logCapture = getLogCapture();
logCapture.mark('before-ctxmenu-tab-005');
capturing.clearCaptured();

await waitForHuman(
'context-menus-editor-tab-005',
`Right-click tab "${fn}" → "RangeLink: Send File Path" → select "${terminalName}" from the destination picker`,
[
`1. Locate the "${fn}" tab in the editor tab bar`,
'2. Right-click the tab',
'3. Select "RangeLink: Send File Path"',
`4. In the destination picker, select "${terminalName}"`,
],
);

const lines = logCapture.getLinesSince('before-ctxmenu-tab-005');

assertFilePathLogged(lines, {
pathFormat: 'absolute',
uriSource: 'context-menu',
filePath: uri.fsPath,
});
const expectedPath = ` ${uri.fsPath} `;
assertClipboardWriteLogged(lines, { textLength: expectedPath.length });
assertTerminalBufferEquals(capturing.getCapturedText(), expectedPath);

ss.log(
'✓ Unbound editor-tab absolute path → picker → bind+send (merged message, pty capture verified content)',
);
});

test('[assisted] context-menus-editor-tab-006: Editor tab "Send Relative File Path" (unbound) opens picker and sends relative path to selected terminal', async () => {
const uri = await ss.createAndOpenFile('ctxmenu-tab-006', FILE_CONTENT);
const fn = path.basename(uri.fsPath);
const relativePath = vscode.workspace.asRelativePath(uri, false);

const terminalName = 'rl-ctxmenu-tab-006';
const capturing = await ss.createCapturingTerminal(terminalName);

ss.expectStatusBarMessages([
'✓ RangeLink: Bound to Terminal ("rl-ctxmenu-tab-006") — File path sent',
]);
ss.expectContextKeys({
'rangelink.isActiveTerminalBindable': true,
'rangelink.isActiveTerminalPasteDestination': true,
'rangelink.isBound': true,
});

const logCapture = getLogCapture();
logCapture.mark('before-ctxmenu-tab-006');
capturing.clearCaptured();

await waitForHuman(
'context-menus-editor-tab-006',
`Right-click tab "${fn}" → "RangeLink: Send Relative File Path" → select "${terminalName}" from the destination picker`,
[
`1. Locate the "${fn}" tab in the editor tab bar`,
'2. Right-click the tab',
'3. Select "RangeLink: Send Relative File Path"',
`4. In the destination picker, select "${terminalName}"`,
],
);

const lines = logCapture.getLinesSince('before-ctxmenu-tab-006');

assertFilePathLogged(lines, {
pathFormat: 'workspace-relative',
uriSource: 'context-menu',
filePath: relativePath,
});
const expectedPath = ` ${relativePath} `;
assertClipboardWriteLogged(lines, { textLength: expectedPath.length });
assertTerminalBufferEquals(capturing.getCapturedText(), expectedPath);

ss.log(
'✓ Unbound editor-tab relative path → picker → bind+send (merged message, pty capture verified content)',
);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -190,4 +190,99 @@ standardSuite('Context Menus — Explorer', (ss) => {
'✓ Unbound state: "Unbind" absent from Explorer context menu (human verdict + state invariant)',
);
});

test('[assisted] context-menus-explorer-006: Explorer "Send File Path" (unbound) opens picker and sends absolute path to selected terminal', async () => {
const uri = await ss.createAndOpenFile('ctxmenu-exp-006', FILE_CONTENT);
const fn = path.basename(uri.fsPath);

const terminalName = 'rl-ctxmenu-exp-006';
const capturing = await ss.createCapturingTerminal(terminalName);

ss.expectStatusBarMessages([
'✓ RangeLink: Bound to Terminal ("rl-ctxmenu-exp-006") — File path sent',
]);
ss.expectContextKeys({
'rangelink.isActiveTerminalBindable': true,
'rangelink.isActiveTerminalPasteDestination': true,
'rangelink.isBound': true,
});

const logCapture = getLogCapture();
logCapture.mark('before-ctxmenu-exp-006');
capturing.clearCaptured();

await waitForHuman(
'context-menus-explorer-006',
`Right-click "${fn}" in Explorer → "RangeLink: Send File Path" → select "${terminalName}" from the destination picker`,
[
`1. Locate "${fn}" in the Explorer panel`,
'2. Right-click it',
'3. Select "RangeLink: Send File Path"',
`4. In the destination picker, select "${terminalName}"`,
],
);

const lines = logCapture.getLinesSince('before-ctxmenu-exp-006');

assertFilePathLogged(lines, {
pathFormat: 'absolute',
uriSource: 'context-menu',
filePath: uri.fsPath,
});
const expectedPath = ` ${uri.fsPath} `;
assertClipboardWriteLogged(lines, { textLength: expectedPath.length });
assertTerminalBufferEquals(capturing.getCapturedText(), expectedPath);

ss.log(
'✓ Unbound explorer absolute path → picker → bind+send (merged message, pty capture verified content)',
);
});

test('[assisted] context-menus-explorer-007: Explorer "Send Relative File Path" (unbound) opens picker and sends relative path to selected terminal', async () => {
const uri = await ss.createAndOpenFile('ctxmenu-exp-007', FILE_CONTENT);
const fn = path.basename(uri.fsPath);
const relativePath = vscode.workspace.asRelativePath(uri, false);

const terminalName = 'rl-ctxmenu-exp-007';
const capturing = await ss.createCapturingTerminal(terminalName);

ss.expectStatusBarMessages([
'✓ RangeLink: Bound to Terminal ("rl-ctxmenu-exp-007") — File path sent',
]);
ss.expectContextKeys({
'rangelink.isActiveTerminalBindable': true,
'rangelink.isActiveTerminalPasteDestination': true,
'rangelink.isBound': true,
});

const logCapture = getLogCapture();
logCapture.mark('before-ctxmenu-exp-007');
capturing.clearCaptured();

await waitForHuman(
'context-menus-explorer-007',
`Right-click "${fn}" in Explorer → "RangeLink: Send Relative File Path" → select "${terminalName}" from the destination picker`,
[
`1. Locate "${fn}" in the Explorer panel`,
'2. Right-click it',
'3. Select "RangeLink: Send Relative File Path"',
`4. In the destination picker, select "${terminalName}"`,
],
);

const lines = logCapture.getLinesSince('before-ctxmenu-exp-007');

assertFilePathLogged(lines, {
pathFormat: 'workspace-relative',
uriSource: 'context-menu',
filePath: relativePath,
});
const expectedPath = ` ${relativePath} `;
assertClipboardWriteLogged(lines, { textLength: expectedPath.length });
assertTerminalBufferEquals(capturing.getCapturedText(), expectedPath);

ss.log(
'✓ Unbound explorer relative path → picker → bind+send (merged message, pty capture verified content)',
);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -325,8 +325,7 @@ standardSuite('Context Menus — Terminal', (ss) => {
await ss.settle(TERMINAL_READY_MS);

ss.expectStatusBarMessages([
`✓ RangeLink: Bound to Terminal ("${destName}")`,
`✓ RangeLink: Selected text sent to Terminal ("${destName}")`,
`✓ RangeLink: Bound to Terminal ("${destName}") — Selected text sent`,
]);
ss.expectContextKeys({
'rangelink.isActiveTerminalBindable': true,
Expand Down Expand Up @@ -600,8 +599,7 @@ standardSuite('Context Menus — Terminal', (ss) => {
await ss.settle(TERMINAL_READY_MS);

ss.expectStatusBarMessages([
'✓ RangeLink: Bound to Terminal ("rl-sts-011-DEST")',
'✓ RangeLink: Selected text sent to Terminal ("rl-sts-011-DEST")',
'✓ RangeLink: Bound to Terminal ("rl-sts-011-DEST") — Selected text sent',
]);
ss.expectContextKeys({
'rangelink.isActiveTerminalBindable': true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,7 @@ standardSuite('Dirty Buffer Warning', (ss) => {
const capturing = await ss.createCapturingTerminal('dirty-buffer-test');

ss.expectStatusBarMessages([
'✓ RangeLink: Bound to Terminal ("dirty-buffer-test")',
'✓ RangeLink: File path sent to Terminal ("dirty-buffer-test")',
'✓ RangeLink: Bound to Terminal ("dirty-buffer-test") — File path sent',
]);
ss.expectContextKeys({
'rangelink.isBound': true,
Expand Down Expand Up @@ -122,8 +121,7 @@ standardSuite('Dirty Buffer Warning', (ss) => {
const capturing = await ss.createCapturingTerminal('dirty-buffer-test');

ss.expectStatusBarMessages([
'✓ RangeLink: Bound to Terminal ("dirty-buffer-test")',
'✓ RangeLink: File path sent to Terminal ("dirty-buffer-test")',
'✓ RangeLink: Bound to Terminal ("dirty-buffer-test") — File path sent',
]);
ss.expectContextKeys({
'rangelink.isBound': true,
Expand Down Expand Up @@ -630,8 +628,7 @@ standardSuite('Dirty Buffer Warning — Dialog Interaction', (ss) => {

test('[assisted] dirty-buffer-warning-013: R-F Save & Send saves file and sends path', async () => {
ss.expectStatusBarMessages([
'✓ RangeLink: Bound to Terminal ("dirty-buffer-test")',
'✓ RangeLink: File path sent to Terminal ("dirty-buffer-test")',
'✓ RangeLink: Bound to Terminal ("dirty-buffer-test") — File path sent',
]);
ss.expectContextKeys({
'rangelink.isBound': true,
Expand Down Expand Up @@ -681,8 +678,7 @@ standardSuite('Dirty Buffer Warning — Dialog Interaction', (ss) => {

test('[assisted] dirty-buffer-warning-014: R-F Send Anyway sends path without saving', async () => {
ss.expectStatusBarMessages([
'✓ RangeLink: Bound to Terminal ("dirty-buffer-test")',
'✓ RangeLink: File path sent to Terminal ("dirty-buffer-test")',
'✓ RangeLink: Bound to Terminal ("dirty-buffer-test") — File path sent',
]);
ss.expectContextKeys({
'rangelink.isBound': true,
Expand Down
Loading