feat: add Create New Hook button and fix UI styling

- Add 'Create New Hook' button above 'Open Global Folder' button
- Remove borders from hook items to match flat MCP styling
- Implement hooksCreateNew message handler to create example.yaml
- Fix YAML template indentation to use standard 2-space format
- Add translation strings for new UI elements
This commit is contained in:
Toray Altas 2026-01-17 11:13:08 -05:00
parent f8bd3ba870
commit 36ca0ccc27
4 changed files with 72 additions and 4 deletions

View file

@ -623,6 +623,7 @@ export interface WebviewMessage {
| "hooksOpenConfigFolder"
| "hooksDeleteHook"
| "hooksOpenHookFile"
| "hooksCreateNew"
text?: string
editedMessageContent?: string
tab?: "settings" | "history" | "mcp" | "modes" | "chat" | "marketplace" | "cloud"

View file

@ -3601,6 +3601,62 @@ export const webviewMessageHandler = async (
break
}
case "hooksCreateNew": {
// Check if hooks experiment is enabled
const hooksExperimentsState = getGlobalState("experiments") ?? experimentDefault
if (!experiments.isEnabled(hooksExperimentsState, EXPERIMENT_IDS.HOOKS)) {
break
}
try {
const cwd = provider.cwd
const hooksPath = path.join(cwd, ".roo", "hooks")
// Create directory if it doesn't exist
await fs.mkdir(hooksPath, { recursive: true })
const exampleFilePath = path.join(hooksPath, "example.yaml")
// Check if file already exists
const exists = await fileExistsAtPath(exampleFilePath)
if (exists) {
// Open existing file instead of overwriting
const uri = vscode.Uri.file(exampleFilePath)
const doc = await vscode.workspace.openTextDocument(uri)
await vscode.window.showTextDocument(doc)
break
}
// Create the example hook file
const exampleContent = `version: "1"
hooks:
PreToolUse:
- id: example-hook
matcher: "write_to_file|edit_file|apply_diff|apply_patch"
enabled: true
command: 'echo "Verification hook triggered"'
timeout: 5
`
await safeWriteText(exampleFilePath, exampleContent)
// Open the file in the editor
const uri = vscode.Uri.file(exampleFilePath)
const doc = await vscode.workspace.openTextDocument(uri)
await vscode.window.showTextDocument(doc)
// Reload hooks config to pick up the new file
const hookManager = provider.getHookManager()
if (hookManager) {
await hookManager.reloadHooksConfig()
await provider.postStateToWebview()
}
} catch (error) {
provider.log(`Failed to create hook file: ${error instanceof Error ? error.message : String(error)}`)
vscode.window.showErrorMessage("Failed to create hook configuration file")
}
break
}
default: {
// console.log(`Unhandled message type: ${message.type}`)
//

View file

@ -1,5 +1,5 @@
import React, { useCallback, useEffect, useState } from "react"
import { RefreshCw, FolderOpen, AlertTriangle, Clock, Zap, X } from "lucide-react"
import { RefreshCw, FolderOpen, AlertTriangle, Clock, Zap, X, Plus } from "lucide-react"
import { VSCodePanels, VSCodePanelTab, VSCodePanelView } from "@vscode/webview-ui-toolkit/react"
import { useAppTranslation } from "@src/i18n/TranslationContext"
import { useExtensionState } from "@src/context/ExtensionStateContext"
@ -73,6 +73,10 @@ export const HooksSettings: React.FC = () => {
setTimeout(() => setIsUpdatingAllEnabled(false), 500)
}, [])
const handleCreateNewHook = useCallback(() => {
vscode.postMessage({ type: "hooksCreateNew" })
}, [])
const enabledHooks = hooks?.enabledHooks || []
const hasProjectHooks = hooks?.hasProjectHooks || false
const snapshotTimestamp = hooks?.snapshotTimestamp
@ -179,7 +183,7 @@ export const HooksSettings: React.FC = () => {
{/* Hook Activity Log */}
<HookActivityLog executionHistory={executionHistory} />
{/* Bottom Action Buttons - mirroring MCP settings order: global, project, refresh */}
{/* Bottom Action Buttons - mirroring MCP settings order: create, global, project, refresh */}
<div
style={{
marginTop: "10px",
@ -188,6 +192,10 @@ export const HooksSettings: React.FC = () => {
gridTemplateColumns: "repeat(auto-fit, minmax(200px, 1fr))",
gap: "10px",
}}>
<Button variant="secondary" style={{ width: "100%" }} onClick={handleCreateNewHook}>
<Plus className="w-4 h-4" />
<span className="ml-2">{t("settings:hooks.createNewHook")}</span>
</Button>
<Button
variant="secondary"
style={{ width: "100%" }}
@ -290,7 +298,7 @@ const HookItem: React.FC<HookItemProps> = ({ hook, onToggle }) => {
}
return (
<div className="rounded border border-vscode-input-border bg-vscode-input-background">
<div className="rounded bg-vscode-input-background">
{/* Collapsed Header */}
<div
className="flex items-center gap-3 p-3 cursor-pointer hover:bg-vscode-list-hoverBackground"
@ -362,7 +370,7 @@ const HookItem: React.FC<HookItemProps> = ({ hook, onToggle }) => {
{/* Expanded Content */}
{isExpanded && (
<div className="border-t border-vscode-input-border p-3">
<div className="p-3">
<VSCodePanels>
<VSCodePanelTab id="config">{t("settings:hooks.tabs.config")}</VSCodePanelTab>
<VSCodePanelTab id="command">{t("settings:hooks.tabs.command")}</VSCodePanelTab>

View file

@ -53,6 +53,9 @@
"openGlobalFolderTooltip": "Open global hooks configuration folder",
"openProjectFolder": "Open Project Folder",
"openGlobalFolder": "Open Global Folder",
"createNewHook": "Create New Hook",
"deleteHook": "Delete hook",
"openHookFile": "Open hook file",
"projectHooksWarningTitle": "Project-level hooks detected",
"projectHooksWarningMessage": "This project includes hook configurations that will execute shell commands. Only enable hooks from sources you trust.",
"reloadNote": "Changes to hook configuration files require clicking Reload to take effect.",