fix(auth): routed smart paste into focused prompts
- Intercepted the configured smart-paste shortcut while a paste-capable modal owns focus. - Added regression coverage for Ctrl+V API-key entry and documented the fix. Fixes #6057
This commit is contained in:
@@ -1,6 +1,10 @@
|
||||
# Changelog
|
||||
|
||||
## [Unreleased]
|
||||
### Fixed
|
||||
|
||||
- Fixed `Ctrl+V` clipboard paste being dropped while API-key and other modal prompts own focus ([#6057](https://github.com/can1357/oh-my-pi/issues/6057)).
|
||||
|
||||
|
||||
## [17.0.5] - 2026-07-18
|
||||
|
||||
|
||||
@@ -172,6 +172,7 @@ export class InputController {
|
||||
|
||||
#enhancedPaste?: EnhancedPasteController;
|
||||
#focusedLeftTapListenerInstalled = false;
|
||||
#focusedPasteListenerInstalled = false;
|
||||
#btwBranchListenerInstalled = false;
|
||||
#btwCopyListenerInstalled = false;
|
||||
// Tap counter for the double-← gesture; reset whenever a quiet gap
|
||||
@@ -265,6 +266,16 @@ export class InputController {
|
||||
return { consume: true };
|
||||
});
|
||||
}
|
||||
if (!this.#focusedPasteListenerInstalled) {
|
||||
this.#focusedPasteListenerInstalled = true;
|
||||
this.ctx.ui.addInputListener(data => {
|
||||
const focused = this.ctx.ui.getFocused();
|
||||
if (!focused || focused === this.ctx.editor || !hasPasteText(focused)) return undefined;
|
||||
if (!this.ctx.keybindings.matches(data, "app.clipboard.pasteImage")) return undefined;
|
||||
void this.handleImagePaste();
|
||||
return { consume: true };
|
||||
});
|
||||
}
|
||||
this.ctx.editor.onEscape = () => {
|
||||
// Side-channel panels are the topmost view. Esc dismisses them before
|
||||
// touching loop mode, maintenance, or the underlying main turn.
|
||||
|
||||
@@ -2,6 +2,7 @@ import { describe, expect, it, type Mock, vi } from "bun:test";
|
||||
import type { ImageContent } from "@oh-my-pi/pi-ai";
|
||||
import { InputController } from "@oh-my-pi/pi-coding-agent/modes/controllers/input-controller";
|
||||
import type { InteractiveModeContext } from "@oh-my-pi/pi-coding-agent/modes/types";
|
||||
import { type KeyId, matchesKey } from "@oh-my-pi/pi-tui";
|
||||
import manualContinuePrompt from "../src/prompts/system/manual-continue.md" with { type: "text" };
|
||||
|
||||
type FakeEditor = {
|
||||
@@ -55,11 +56,12 @@ function registeredInputListeners(addInputListener: Mock<(listener: InputListene
|
||||
|
||||
async function createContext() {
|
||||
let editorText = "";
|
||||
const keyMap: Record<string, string[]> = {
|
||||
const keyMap: Record<string, KeyId[]> = {
|
||||
"app.display.reset": ["ctrl+l"],
|
||||
"app.model.selectTemporary": ["ctrl+y"],
|
||||
"app.model.select": ["alt+m"],
|
||||
"app.retry": ["alt+r"],
|
||||
"app.clipboard.pasteImage": ["ctrl+v"],
|
||||
};
|
||||
const customHandlers = new Map<string, () => void>();
|
||||
const setActionKeys = vi.fn();
|
||||
@@ -149,6 +151,9 @@ async function createContext() {
|
||||
getKeys(action: string) {
|
||||
return keyMap[action] ? [...keyMap[action]] : [];
|
||||
},
|
||||
matches(data: string, action: string) {
|
||||
return keyMap[action]?.some(key => matchesKey(data, key)) ?? false;
|
||||
},
|
||||
} as InteractiveModeContext["keybindings"],
|
||||
locallySubmittedUserSignatures: new Set<string>(),
|
||||
isKnownSlashCommand: () => false,
|
||||
@@ -414,6 +419,26 @@ describe("InputController keybinding setup", () => {
|
||||
expect(spies.handleBtwBranchKey).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("routes the smart-paste shortcut to a focused login input", async () => {
|
||||
const { promise: pasted, resolve: resolvePaste } = Promise.withResolvers<string>();
|
||||
const focusedPasteText = vi.fn((text: string) => {
|
||||
resolvePaste(text);
|
||||
});
|
||||
const { InputController, ctx, setFocused, spies } = await createContext();
|
||||
setFocused({ pasteText: focusedPasteText });
|
||||
const controller = new InputController(ctx, {
|
||||
readImage: async () => null,
|
||||
readText: async () => "sk-test-key",
|
||||
});
|
||||
|
||||
controller.setupKeyHandlers();
|
||||
const result = dispatchInput(registeredInputListeners(spies.addInputListener), "\x16");
|
||||
|
||||
expect(result).toEqual({ consume: true });
|
||||
expect(await pasted).toBe("sk-test-key");
|
||||
expect(focusedPasteText).toHaveBeenCalledWith("sk-test-key");
|
||||
});
|
||||
|
||||
it("routes c to copy a copyable /btw panel when the editor is empty", async () => {
|
||||
const { InputController, ctx, spies } = await createContext();
|
||||
(ctx.canCopyBtw as unknown as { mockReturnValue(value: boolean): void }).mockReturnValue(true);
|
||||
|
||||
Reference in New Issue
Block a user