import { Box, Text, useInput } from "ink"; import { memo, useState } from "react"; import { resolvePlaceholders } from "../helpers/pasteRegistry"; import { useProgressIndicator } from "../hooks/useProgressIndicator"; import { colors } from "./colors"; import { MarkdownDisplay } from "./MarkdownDisplay"; import { PasteAwareTextInput } from "./PasteAwareTextInput"; type Props = { plan: string; onApprove: () => void; onApproveAndAcceptEdits: () => void; onKeepPlanning: (reason: string) => void; }; const OptionsRenderer = memo( ({ options, selectedOption, }: { options: Array<{ label: string }>; selectedOption: number; }) => { return ( {options.map((option, index) => { const isSelected = index === selectedOption; const color = isSelected ? colors.approval.header : undefined; return ( {isSelected ? "❯" : " "} {index + 1}. {option.label} ); })} ); }, ); OptionsRenderer.displayName = "OptionsRenderer"; export const PlanModeDialog = memo( ({ plan, onApprove, onApproveAndAcceptEdits, onKeepPlanning }: Props) => { const [selectedOption, setSelectedOption] = useState(0); const [isEnteringReason, setIsEnteringReason] = useState(false); const [denyReason, setDenyReason] = useState(""); useProgressIndicator(); const options = [ { label: "Yes, and auto-accept edits", action: onApproveAndAcceptEdits }, { label: "Yes, and manually approve edits", action: onApprove }, { label: "No, keep planning", action: () => {} }, // Handled via setIsEnteringReason ]; useInput((_input, key) => { // CTRL-C: immediately exit plan approval (closest to cancel) if (key.ctrl && _input === "c") { onKeepPlanning("User pressed CTRL-C to cancel"); return; } if (isEnteringReason) { // When entering reason, only handle enter/escape if (key.return) { // Resolve placeholders before sending reason const resolvedReason = resolvePlaceholders(denyReason); onKeepPlanning(resolvedReason); setIsEnteringReason(false); setDenyReason(""); } else if (key.escape) { setIsEnteringReason(false); setDenyReason(""); } return; } if (key.upArrow) { setSelectedOption((prev) => Math.max(0, prev - 1)); } else if (key.downArrow) { setSelectedOption((prev) => Math.min(options.length - 1, prev + 1)); } else if (key.return) { // Check if this is the "keep planning" option (last option) if (selectedOption === options.length - 1) { setIsEnteringReason(true); } else { options[selectedOption]?.action(); } } else if (key.escape) { setIsEnteringReason(true); // ESC also goes to denial input } }); // Show denial input screen if entering reason if (isEnteringReason) { return ( Enter feedback to continue planning (ESC to cancel): {"> "} ); } return ( Ready to code? Here's the proposed plan: {/* Nested box for plan content */} Would you like to proceed? ); }, ); PlanModeDialog.displayName = "PlanModeDialog";