feat: add agent skill menu dialog#469
Conversation
Greptile SummaryAdds 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.
Confidence Score: 4/5The 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
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)
%%{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)
Prompt To Fix All With AIFix 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 |
| 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."; |
There was a problem hiding this 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.
| 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.There was a problem hiding this comment.
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
| const promptRows = [ | ||
| "Load the Hunk skill and use it for this review.", | ||
| "Run `hunk skill path` to get the skill path.", | ||
| ]; |
There was a problem hiding this 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.
| 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!
There was a problem hiding this comment.
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
| { | ||
| kind: "item", | ||
| label: "Agent skill", | ||
| checked: showAgentSkill, | ||
| action: openAgentSkill, | ||
| }, |
There was a problem hiding this 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.
| { | |
| 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.There was a problem hiding this comment.
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
99ff43d to
9455ca0
Compare
Summary
Agent > Agent skillmenu item that opens setup guidance for the bundled Hunk review skillVerification
bun run typecheckbun test src/ui/lib/ui-lib.test.ts src/ui/AppHost.interactions.test.tsxbun run src/main.tsx -- diff, openedAgent > Agent skill, and confirmed the modal rendersThis PR description was generated by Pi using GPT-5 Codex