import { Box, useInput } from "ink"; import { memo, useState } from "react"; import { useProgressIndicator } from "../hooks/useProgressIndicator"; import { useTerminalWidth } from "../hooks/useTerminalWidth"; import { useTextInputCursor } from "../hooks/useTextInputCursor"; import { colors } from "./colors"; import { Text } from "./Text"; type Props = { onApprove: () => void; onApproveAndAcceptEdits: () => void; onKeepPlanning: (reason: string) => void; onCancel: () => void; // For CTRL-C to queue denial (like other approval screens) isFocused?: boolean; }; /** * StaticPlanApproval - Options-only plan approval component * * This component renders ONLY the approval options (no plan preview). * The plan preview is committed separately to the Static area via the * eager commit pattern, which keeps this component small (~8 lines) * and flicker-free. * * The plan prop was removed because the plan is rendered in the Static * area by ApprovalPreview, not here. */ export const StaticPlanApproval = memo( ({ onApprove, onApproveAndAcceptEdits, onKeepPlanning, onCancel, isFocused = true, }: Props) => { const [selectedOption, setSelectedOption] = useState(0); const { text: customReason, cursorPos, handleKey, clear, } = useTextInputCursor(); const columns = useTerminalWidth(); useProgressIndicator(); const customOptionIndex = 2; const maxOptionIndex = customOptionIndex; const isOnCustomOption = selectedOption === customOptionIndex; const customOptionPlaceholder = "Type here to tell Letta Code what to change"; useInput( (input, key) => { if (!isFocused) return; // CTRL-C: cancel and queue denial (like other approval screens) if (key.ctrl && input === "c") { onCancel(); return; } // Arrow navigation always works if (key.upArrow) { setSelectedOption((prev) => Math.max(0, prev - 1)); return; } if (key.downArrow) { setSelectedOption((prev) => Math.min(maxOptionIndex, prev + 1)); return; } // When on custom input option if (isOnCustomOption) { if (key.return) { if (customReason.trim()) { onKeepPlanning(customReason.trim()); } return; } if (key.escape) { if (customReason) { clear(); } else { onKeepPlanning("User cancelled"); } return; } // Handle text input (arrows, backspace, typing) if (handleKey(input, key)) return; } // When on regular options if (key.return) { if (selectedOption === 0) { onApproveAndAcceptEdits(); } else if (selectedOption === 1) { onApprove(); } return; } if (key.escape) { onKeepPlanning("User cancelled"); return; } // Number keys for quick selection (only for fixed options, not custom text input) if (input === "1") { onApproveAndAcceptEdits(); return; } if (input === "2") { onApprove(); return; } }, { isActive: isFocused }, ); // Hint text based on state const hintText = isOnCustomOption ? customReason ? "Enter to submit · Esc to clear" : "Type feedback · Esc to cancel" : "Enter to select · Esc to cancel"; return ( {/* Question */} Would you like to proceed? {/* Options */} {/* Option 1: Yes, and auto-accept edits */} {selectedOption === 0 ? "❯" : " "} 1. Yes, and auto-accept edits {/* Option 2: Yes, and manually approve edits */} {selectedOption === 1 ? "❯" : " "} 2. Yes, and manually approve edits {/* Option 3: Custom input */} {isOnCustomOption ? "❯" : " "} 3. {customReason ? ( {customReason.slice(0, cursorPos)} {isOnCustomOption && "█"} {customReason.slice(cursorPos)} ) : ( {customOptionPlaceholder} {isOnCustomOption && "█"} )} {/* Hint */} {hintText} ); }, ); StaticPlanApproval.displayName = "StaticPlanApproval";