Skip to content

feat: add agent skill menu dialog#469

Open
benvinegar wants to merge 2 commits into
mainfrom
feat/agent-skill-menu
Open

feat: add agent skill menu dialog#469
benvinegar wants to merge 2 commits into
mainfrom
feat/agent-skill-menu

Conversation

@benvinegar

Copy link
Copy Markdown
Member

Summary

  • Add an Agent > Agent skill menu item that opens setup guidance for the bundled Hunk review skill
  • Add a shared-frame modal with a prompt card and OSC52 copy button
  • Cover the new menu/modal flow and menu model in UI tests

Verification

  • bun run typecheck
  • bun test src/ui/lib/ui-lib.test.ts src/ui/AppHost.interactions.test.tsx
  • Manual tmux smoke: launched bun run src/main.tsx -- diff, opened Agent > Agent skill, and confirmed the modal renders

This PR description was generated by Pi using GPT-5 Codex

@greptile-apps

greptile-apps Bot commented Jun 20, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

Adds an "Agent skill" entry to the Agent menu that opens a new modal dialog showing the bundled Hunk skill setup prompt with an OSC52 copy button. The keyboard shortcut handler is generalised from a help-only escape handler to a prioritised multi-dialog escape handler.

  • AgentSkillDialog.tsx: New lazy-loaded component renders a framed prompt card and a conditional Copy button; the prompt text is defined twice (as an exported constant and as a promptRows array), which can silently diverge if either is edited independently.
  • appMenus.ts: "Agent skill" item is added to the agent menu with checked: showAgentSkill; because the dialog cannot be open while the menu is rendered, this checkmark is always invisible in real use and misrepresents a dialog launcher as a toggle.
  • useAppKeyboardShortcuts.ts / App.tsx: The generalised handleDialogShortcut correctly prioritises closing the agent skill dialog before the help dialog on Escape, and the OSC52 availability check plus fallback notice are handled cleanly.

Confidence Score: 4/5

The change is additive and self-contained; the main risk is that the dialog's displayed text and the clipboard text are defined independently and can drift.

The duplicate definition of the prompt text (once in AGENT_SKILL_PROMPT, once in promptRows) means a future edit to one site leaves the clipboard content and the rendered dialog silently out of step. Everything else — state wiring, lazy loading, OSC52 fallback, Escape handling — looks correct and is covered by the new tests.

src/ui/components/chrome/AgentSkillDialog.tsx — the duplicated prompt text is the one site worth revisiting before merge.

Important Files Changed

Filename Overview
src/ui/components/chrome/AgentSkillDialog.tsx New dialog component rendering a copyable agent skill prompt; prompt text is independently defined from the exported AGENT_SKILL_PROMPT constant, creating a latent divergence risk.
src/ui/lib/appMenus.ts Adds 'Agent skill' menu item to the agent menu; uses checked: showAgentSkill on a dialog-launcher item, a semantic mismatch since the dialog can't be open while the menu is rendered.
src/ui/App.tsx Wires showAgentSkill state, openAgentSkill/closeAgentSkill callbacks, and lazy-loaded dialog into the app; OSC52 check and fallback notice are correctly handled.
src/ui/hooks/useAppKeyboardShortcuts.ts Refactors handleHelpShortcut into a general handleDialogShortcut that prioritises closing AgentSkillDialog before HelpDialog on Escape; logic is correct and preserves prior behaviour.
src/ui/AppHost.interactions.test.tsx Adds an integration test covering the full open/render/close flow for the Agent skill dialog; covers the key navigation path and Escape dismissal.
src/ui/lib/ui-lib.test.ts Updates menu-model unit tests for showAgentSkill; tests the checked:true state when showAgentSkill is true, which is a state that cannot occur in real UI flow.
.changeset/agent-skill-menu.md Patch changeset entry correctly categorising the new Agent menu dialog feature.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant User
    participant Menu as Agent Menu
    participant App as App.tsx
    participant Dialog as AgentSkillDialog
    participant OSC52 as Terminal OSC52

    User->>Menu: F10 → navigate to Agent → Agent skill → Enter
    Menu->>App: openAgentSkill()
    App->>App: setShowAgentSkill(true)
    App->>Dialog: render LazyAgentSkillDialog

    User->>Dialog: click "Copy prompt"
    Dialog->>App: onCopyPrompt()
    App->>App: dynamic import(AgentSkillDialog) for AGENT_SKILL_PROMPT
    App->>OSC52: renderer.copyToClipboardOSC52(AGENT_SKILL_PROMPT)
    OSC52-->>User: clipboard updated
    App->>App: showTransientNotice("Copied...")

    User->>Dialog: Escape
    Dialog->>App: handleDialogShortcut → closeAgentSkill()
    App->>App: setShowAgentSkill(false)
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant User
    participant Menu as Agent Menu
    participant App as App.tsx
    participant Dialog as AgentSkillDialog
    participant OSC52 as Terminal OSC52

    User->>Menu: F10 → navigate to Agent → Agent skill → Enter
    Menu->>App: openAgentSkill()
    App->>App: setShowAgentSkill(true)
    App->>Dialog: render LazyAgentSkillDialog

    User->>Dialog: click "Copy prompt"
    Dialog->>App: onCopyPrompt()
    App->>App: dynamic import(AgentSkillDialog) for AGENT_SKILL_PROMPT
    App->>OSC52: renderer.copyToClipboardOSC52(AGENT_SKILL_PROMPT)
    OSC52-->>User: clipboard updated
    App->>App: showTransientNotice("Copied...")

    User->>Dialog: Escape
    Dialog->>App: handleDialogShortcut → closeAgentSkill()
    App->>App: setShowAgentSkill(false)
Loading
Prompt To Fix All With AI
Fix the following 3 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 3
src/ui/components/chrome/AgentSkillDialog.tsx:6-8
The `promptRows` array and the exported `AGENT_SKILL_PROMPT` constant contain the same text written out twice independently. If either is updated without the other, the string displayed in the dialog and the string written to the clipboard will silently diverge — a user would see one sentence and paste another. Deriving one from the other eliminates the duplication.

```suggestion
export const AGENT_SKILL_COMMAND = "hunk skill path";
export const AGENT_SKILL_PROMPT_ROWS = [
  "Load the Hunk skill and use it for this review.",
  "Run `hunk skill path` to get the skill path.",
];
export const AGENT_SKILL_PROMPT = AGENT_SKILL_PROMPT_ROWS.join(" ");
```

### Issue 2 of 3
src/ui/components/chrome/AgentSkillDialog.tsx:29-32
If `AGENT_SKILL_PROMPT_ROWS` is exported (as suggested above), `promptRows` can simply reference it, keeping the rendered text and the clipboard text in sync.

```suggestion
  const promptRows = AGENT_SKILL_PROMPT_ROWS;
```

### Issue 3 of 3
src/ui/lib/appMenus.ts:229-234
The `checked` property is used on the "Agent skill" item, but "Agent skill" is a dialog launcher, not a persistent toggle. In practice the menu is never rendered while the dialog is open, so `showAgentSkill` is always `false` here — the checkmark is dead UI. Action-only items like "Next annotated file" above carry no `checked` field; "Agent skill" should follow the same pattern.

```suggestion
      {
        kind: "item",
        label: "Agent skill",
        action: openAgentSkill,
      },
```

Reviews (1): Last reviewed commit: "feat: add agent skill menu dialog" | Re-trigger Greptile

Comment on lines +6 to +8
export const AGENT_SKILL_COMMAND = "hunk skill path";
export const AGENT_SKILL_PROMPT =
"Load the Hunk skill and use it for this review. Run `hunk skill path` to get the skill path.";

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.

P1 The promptRows array and the exported AGENT_SKILL_PROMPT constant contain the same text written out twice independently. If either is updated without the other, the string displayed in the dialog and the string written to the clipboard will silently diverge — a user would see one sentence and paste another. Deriving one from the other eliminates the duplication.

Suggested change
export const AGENT_SKILL_COMMAND = "hunk skill path";
export const AGENT_SKILL_PROMPT =
"Load the Hunk skill and use it for this review. Run `hunk skill path` to get the skill path.";
export const AGENT_SKILL_COMMAND = "hunk skill path";
export const AGENT_SKILL_PROMPT_ROWS = [
"Load the Hunk skill and use it for this review.",
"Run `hunk skill path` to get the skill path.",
];
export const AGENT_SKILL_PROMPT = AGENT_SKILL_PROMPT_ROWS.join(" ");
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/ui/components/chrome/AgentSkillDialog.tsx
Line: 6-8

Comment:
The `promptRows` array and the exported `AGENT_SKILL_PROMPT` constant contain the same text written out twice independently. If either is updated without the other, the string displayed in the dialog and the string written to the clipboard will silently diverge — a user would see one sentence and paste another. Deriving one from the other eliminates the duplication.

```suggestion
export const AGENT_SKILL_COMMAND = "hunk skill path";
export const AGENT_SKILL_PROMPT_ROWS = [
  "Load the Hunk skill and use it for this review.",
  "Run `hunk skill path` to get the skill path.",
];
export const AGENT_SKILL_PROMPT = AGENT_SKILL_PROMPT_ROWS.join(" ");
```

How can I resolve this? If you propose a fix, please make it concise.

@benvinegar benvinegar Jun 21, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed by introducing AGENT_SKILL_PROMPT_ROWS and deriving AGENT_SKILL_PROMPT from it, so the rendered card and copied prompt stay in sync.

This comment was generated by Pi using GPT-5 Codex

Comment on lines +29 to +32
const promptRows = [
"Load the Hunk skill and use it for this review.",
"Run `hunk skill path` to get the skill path.",
];

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.

P1 If AGENT_SKILL_PROMPT_ROWS is exported (as suggested above), promptRows can simply reference it, keeping the rendered text and the clipboard text in sync.

Suggested change
const promptRows = [
"Load the Hunk skill and use it for this review.",
"Run `hunk skill path` to get the skill path.",
];
const promptRows = AGENT_SKILL_PROMPT_ROWS;
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/ui/components/chrome/AgentSkillDialog.tsx
Line: 29-32

Comment:
If `AGENT_SKILL_PROMPT_ROWS` is exported (as suggested above), `promptRows` can simply reference it, keeping the rendered text and the clipboard text in sync.

```suggestion
  const promptRows = AGENT_SKILL_PROMPT_ROWS;
```

How can I resolve this? If you propose a fix, please make it concise.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed — the dialog now renders from AGENT_SKILL_PROMPT_ROWS, the same source used to derive the clipboard prompt.

This comment was generated by Pi using GPT-5 Codex

Comment thread src/ui/lib/appMenus.ts
Comment on lines +229 to +234
{
kind: "item",
label: "Agent skill",
checked: showAgentSkill,
action: openAgentSkill,
},

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.

P2 The checked property is used on the "Agent skill" item, but "Agent skill" is a dialog launcher, not a persistent toggle. In practice the menu is never rendered while the dialog is open, so showAgentSkill is always false here — the checkmark is dead UI. Action-only items like "Next annotated file" above carry no checked field; "Agent skill" should follow the same pattern.

Suggested change
{
kind: "item",
label: "Agent skill",
checked: showAgentSkill,
action: openAgentSkill,
},
{
kind: "item",
label: "Agent skill",
action: openAgentSkill,
},
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/ui/lib/appMenus.ts
Line: 229-234

Comment:
The `checked` property is used on the "Agent skill" item, but "Agent skill" is a dialog launcher, not a persistent toggle. In practice the menu is never rendered while the dialog is open, so `showAgentSkill` is always `false` here — the checkmark is dead UI. Action-only items like "Next annotated file" above carry no `checked` field; "Agent skill" should follow the same pattern.

```suggestion
      {
        kind: "item",
        label: "Agent skill",
        action: openAgentSkill,
      },
```

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed — Agent skill is now an action-only menu item without a checked state, and the menu test was updated accordingly.

This comment was generated by Pi using GPT-5 Codex

@benvinegar benvinegar force-pushed the feat/agent-skill-menu branch from 99ff43d to 9455ca0 Compare June 21, 2026 00:00
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.

1 participant