Skip to content
Open
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
5 changes: 5 additions & 0 deletions .changeset/copy-agent-prompt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"hunkdiff": patch
---

Replace the Agent skill dialog with a direct Copy agent prompt action when terminal clipboard support is available.
2 changes: 1 addition & 1 deletion .changeset/menus-from-command-table.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@ Menus and the controls help dialog now show the keys your commands are actually

Extensions get a menu of their own: registered commands are listed in a new **Extensions** menu with their titles and current keys, grouped per extension. A command whose chord was already taken — or that never declared one — is still reachable there with the mouse. The menu appears only when an extension registered a command.

Four actions that were previously menu-only are now named commands, so they can be bound to keys: `hunk.view.toggleCopyDecorations`, `hunk.app.openAgentSkill`, `hunk.review.nextAnnotatedFile`, and `hunk.review.previousAnnotatedFile`.
Four actions that were previously menu-only are now named commands, so they can be bound to keys: `hunk.view.toggleCopyDecorations`, `hunk.app.copyAgentPrompt`, `hunk.review.nextAnnotatedFile`, and `hunk.review.previousAnnotatedFile`.
2 changes: 1 addition & 1 deletion docs/keybindings.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ The built-in commands and the keys they ship with:
| `hunk.app.refresh` | Refresh the review | `r` |
| `hunk.app.toggleHelp` | Toggle help | `?` |
| `hunk.app.toggleFocusArea` | Switch focus between files and filter | `tab` |
| `hunk.app.openAgentSkill` | Show agent skill | _(none)_ |
| `hunk.app.copyAgentPrompt` | Copy agent prompt | _(none)_ |
| `hunk.review.focusFilter` | Focus the file filter | `/` |
| `hunk.review.startNote` | Add a review note | `c` |
| `hunk.review.editSelectedFile` | Open the selected file in your editor | `e` |
Expand Down
3 changes: 3 additions & 0 deletions src/hunk-review/agentPrompt.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
/** Prompt copied for coding agents that should join the current Hunk review. */
export const AGENT_SKILL_PROMPT =
"Load the Hunk skill and use it for this review. Run `hunk skill path` to get the skill path.";
53 changes: 14 additions & 39 deletions src/ui/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import type {
ReloadedSessionResult,
ReloadSessionOptions,
} from "../hunk-session/types";
import { AGENT_SKILL_PROMPT } from "../hunk-review/agentPrompt";
import { MenuBar } from "./components/chrome/MenuBar";
import { ConfirmDialog, confirmDialogHeight } from "./components/chrome/ConfirmDialog";
import { ExtensionToast } from "./components/chrome/ExtensionToast";
Expand Down Expand Up @@ -97,9 +98,6 @@ const FAST_CODE_HORIZONTAL_SCROLL_COLUMNS = 8;
*/
const SELECTION_CHANGED_DEBOUNCE_MS = 150;

const LazyAgentSkillDialog = lazy(async () => ({
default: (await import("./components/chrome/AgentSkillDialog")).AgentSkillDialog,
}));
const LazyHelpDialog = lazy(async () => ({
default: (await import("./components/chrome/HelpDialog")).HelpDialog,
}));
Expand Down Expand Up @@ -210,7 +208,6 @@ export function App({
const [sidebarVisible, setSidebarVisible] = useState(() => !pagerMode);
const [forceSidebarOpen, setForceSidebarOpen] = useState(false);
const [showHelp, setShowHelp] = useState(false);
const [showAgentSkill, setShowAgentSkill] = useState(false);
const [saveConfigPromptOpen, setSaveConfigPromptOpen] = useState(false);
const [focusArea, setFocusArea] = useState<FocusArea>("files");
const [activeAddNoteTarget, setActiveAddNoteTarget] = useState<ActiveAddNoteTarget | null>(null);
Expand Down Expand Up @@ -1136,28 +1133,20 @@ export function App({
setShowHelp(false);
}, []);

/** Close the agent skill setup overlay. */
const closeAgentSkill = useCallback(() => {
setShowAgentSkill(false);
}, []);

/** Open the agent skill setup overlay. */
const openAgentSkill = useCallback(() => {
setShowAgentSkill(true);
}, []);

/** Copy the agent skill prompt through the terminal clipboard integration. */
const copyAgentSkillPrompt = useCallback(async () => {
const { AGENT_SKILL_PROMPT } = await import("./components/chrome/AgentSkillDialog");
if (renderer.isOsc52Supported?.() && typeof renderer.copyToClipboardOSC52 === "function") {
renderer.copyToClipboardOSC52(AGENT_SKILL_PROMPT);
showTransientNotice("Copied agent skill prompt to clipboard");
return;
}

showTransientNotice("Clipboard copy unsupported in this terminal (enable OSC 52)");
const copyAgentPrompt = useCallback(() => {
renderer.copyToClipboardOSC52(AGENT_SKILL_PROMPT);
showTransientNotice("Copied agent prompt to clipboard");
}, [renderer, showTransientNotice]);

/** Query terminal clipboard support live because capability detection may finish after startup. */
const isAgentPromptCopySupported = useCallback(
() =>
(renderer.isOsc52Supported?.() ?? false) &&
typeof renderer.copyToClipboardOSC52 === "function",
[renderer],
);

/** Toggle the modal keyboard help overlay. */
const toggleHelp = useCallback(() => {
setShowHelp((current) => !current);
Expand Down Expand Up @@ -1228,7 +1217,8 @@ export function App({
moveToAnnotatedHunk,
moveToFile,
moveToHunk: review.moveToHunk,
openAgentSkill,
copyAgentPrompt,
isAgentPromptCopySupported,
openThemeSelector,
requestQuit,
resolvedKeys: resolvedCommandKeys,
Expand Down Expand Up @@ -1290,7 +1280,6 @@ export function App({
useAppKeyboardShortcuts({
activeMenuId,
activateCurrentMenuItem,
closeAgentSkill,
closeHelp,
closeMenu,
acceptThemeSelector,
Expand All @@ -1312,7 +1301,6 @@ export function App({
neverAskToSaveViewPreferencesAndQuit,
closeSaveConfigPrompt,
saveDraftNote,
showAgentSkill,
showHelp,
switchMenu,
toggleFocusArea,
Expand Down Expand Up @@ -1621,19 +1609,6 @@ export function App({
</Suspense>
) : null}

{!pagerMode && showAgentSkill ? (
<Suspense fallback={null}>
<LazyAgentSkillDialog
copySupported={renderer.isOsc52Supported?.() ?? false}
terminalHeight={terminal.height}
terminalWidth={terminal.width}
theme={baseTheme}
onClose={closeAgentSkill}
onCopyPrompt={copyAgentSkillPrompt}
/>
</Suspense>
) : null}

{!pagerMode && showHelp ? (
<Suspense fallback={null}>
<LazyHelpDialog
Expand Down
61 changes: 51 additions & 10 deletions src/ui/AppHost.interactions.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import type { AppBootstrap, LayoutMode } from "../core/types";
import { createTestVcsAppBootstrap } from "../../test/helpers/app-bootstrap";
import { capturedTestColorToHex } from "../../test/helpers/test-color-helpers";
import { createTestDiffFile as buildTestDiffFile, lines } from "../../test/helpers/diff-helpers";
import { AGENT_SKILL_COMMAND, AGENT_SKILL_PROMPT } from "./components/chrome/AgentSkillDialog";
import { AGENT_SKILL_PROMPT } from "../hunk-review/agentPrompt";
import { resolveTheme } from "./themes";

const { loadAppBootstrap } = await import("../core/loaders");
Expand Down Expand Up @@ -1793,12 +1793,18 @@ describe("App interactions", () => {
}
});

test("Agent menu opens copyable agent skill guidance", async () => {
test("Agent menu copies the agent prompt and confirms the write", async () => {
const bootstrap = createBootstrap();
const setup = await testRender(<AppHost bootstrap={bootstrap} />, {
width: 120,
height: 24,
});
let copiedText = "";
setup.renderer.isOsc52Supported = () => true;
setup.renderer.copyToClipboardOSC52 = (text: string) => {
copiedText = text;
return true;
};

try {
await flush(setup);
Expand All @@ -1818,10 +1824,11 @@ describe("App interactions", () => {
let frame = await waitForFrame(
setup,
(currentFrame) =>
currentFrame.includes("Agent skill") && currentFrame.includes("Next annotated file"),
currentFrame.includes("Copy agent prompt") &&
currentFrame.includes("Next annotated file"),
12,
);
expect(frame).toContain("Agent skill");
expect(frame).toContain("Copy agent prompt");
expect(frame).toContain("Next annotated file");

await act(async () => {
Expand All @@ -1834,17 +1841,51 @@ describe("App interactions", () => {

frame = await waitForFrame(
setup,
(currentFrame) => currentFrame.includes("Teach your agent"),
(currentFrame) => currentFrame.includes("Copied agent prompt to clipboard"),
12,
);
expect(frame).toContain("Load the Hunk skill and use it for this review");
expect(AGENT_SKILL_PROMPT).toContain(AGENT_SKILL_COMMAND);
expect(frame).toContain(AGENT_SKILL_COMMAND);
expect(frame).toContain("Copy");
expect(copiedText).toBe(AGENT_SKILL_PROMPT);
expect(frame).toContain("Copied agent prompt to clipboard");
expect(frame).not.toContain("Teach your agent");
} finally {
await act(async () => {
setup.renderer.destroy();
});
}
});

test("Agent menu omits copy prompt when OSC 52 is unsupported", async () => {
const bootstrap = createBootstrap();
const setup = await testRender(<AppHost bootstrap={bootstrap} />, {
width: 120,
height: 24,
});
setup.renderer.isOsc52Supported = () => false;

try {
await flush(setup);

await act(async () => {
await setup.mockInput.pressEscape();
await setup.mockInput.pressKey("F10");
});
await waitForFrame(setup, (frame) => frame.includes("Toggle files/filter focus"), 12);

for (let index = 0; index < 3; index += 1) {
await act(async () => {
await setup.mockInput.pressArrow("right");
});
await flush(setup);
}

const frame = await waitForFrame(
setup,
(currentFrame) =>
currentFrame.includes("Agent notes") && currentFrame.includes("Next annotated file"),
12,
);
expect(frame).not.toContain("Copy agent prompt");
expect(frame).toContain("Agent notes");
expect(frame).toContain("Next annotated file");
} finally {
await act(async () => {
setup.renderer.destroy();
Expand Down
96 changes: 0 additions & 96 deletions src/ui/components/chrome/AgentSkillDialog.tsx

This file was deleted.

11 changes: 0 additions & 11 deletions src/ui/hooks/useAppKeyboardShortcuts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ type FocusArea = "files" | "filter" | "note";
export interface UseAppKeyboardShortcutsOptions {
activeMenuId: MenuId | null;
activateCurrentMenuItem: () => void;
closeAgentSkill: () => void;
closeHelp: () => void;
closeMenu: () => void;
acceptThemeSelector: () => void;
Expand All @@ -36,7 +35,6 @@ export interface UseAppKeyboardShortcutsOptions {
neverAskToSaveViewPreferencesAndQuit: () => void;
closeSaveConfigPrompt: () => void;
saveDraftNote: () => void;
showAgentSkill: boolean;
showHelp: boolean;
switchMenu: (delta: number) => void;
toggleFocusArea: () => void;
Expand All @@ -56,7 +54,6 @@ export interface UseAppKeyboardShortcutsOptions {
export function useAppKeyboardShortcuts({
activeMenuId,
activateCurrentMenuItem,
closeAgentSkill,
closeHelp,
closeMenu,
acceptThemeSelector,
Expand All @@ -78,7 +75,6 @@ export function useAppKeyboardShortcuts({
neverAskToSaveViewPreferencesAndQuit,
closeSaveConfigPrompt,
saveDraftNote,
showAgentSkill,
showHelp,
switchMenu,
toggleFocusArea,
Expand All @@ -88,7 +84,6 @@ export function useAppKeyboardShortcuts({
const commandsRef = useRef(commands);
const focusAreaRef = useRef(focusArea);
const pagerModeRef = useRef(pagerMode);
const showAgentSkillRef = useRef(showAgentSkill);
const showHelpRef = useRef(showHelp);
const saveConfigPromptOpenRef = useRef(saveConfigPromptOpen);
const themeSelectorOpenRef = useRef(themeSelectorOpen);
Expand All @@ -98,7 +93,6 @@ export function useAppKeyboardShortcuts({
commandsRef.current = commands;
focusAreaRef.current = focusArea;
pagerModeRef.current = pagerMode;
showAgentSkillRef.current = showAgentSkill;
showHelpRef.current = showHelp;
saveConfigPromptOpenRef.current = saveConfigPromptOpen;
themeSelectorOpenRef.current = themeSelectorOpen;
Expand Down Expand Up @@ -132,11 +126,6 @@ export function useAppKeyboardShortcuts({
return false;
}

if (showAgentSkillRef.current) {
closeAgentSkill();
return true;
}

if (showHelpRef.current) {
closeHelp();
return true;
Expand Down
Loading