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";