Files
locode/src/ui/ink/PermissionPrompt.tsx
T
kim ee695a64af feat(ui): color-coded diff rendering in the permission prompt
The pre-confirmation preview for mutating tools (edit_file/multi_edit/write_file/
bash/git_commit) was rendered as plain monochrome text, throwing away the most
useful signal in the thing the user evaluates before approving.

- src/utils/diff.ts: looksLikeDiff() — distinguishes a unified diff from a plain
  preview (bash command, 'Create new file ...', JSON args).
- src/ui/ink/DiffView.tsx: renders a unified diff with additions green, removals
  red, hunk/file headers dimmed. Optional side-by-side mode (pairHunk + clip,
  both exported for testing). Ported from origin/v0.6.0.
- src/ui/theme.ts: DIFF_ADD_HEX / DIFF_REMOVE_HEX.
- PermissionPrompt.tsx: use DiffView when the preview is a diff, keep the
  line-by-line render for non-diff previews.

Tests: diff.test.ts (4), DiffView.test.ts (8) for pairHunk/clip/looksLikeDiff.

Verified: typecheck clean, build 261.50 KB, 275 tests pass (+12).
2026-08-21 13:50:50 +09:00

66 lines
2.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { Box, Text } from "ink";
import SelectInput from "ink-select-input";
import type { PermissionDecision } from "../../permissions/types.js";
import { ACCENT_HEX } from "../theme.js";
import { DiffView } from "./DiffView.js";
import { looksLikeDiff } from "../../utils/diff.js";
interface Props {
toolName: string;
args: unknown;
preview?: string;
onSelect: (decision: PermissionDecision) => void;
}
const OPTIONS: Array<{ label: string; value: PermissionDecision }> = [
{ label: "Yes", value: "once" },
{ label: "Yes, and don't ask again this session", value: "session" },
{ label: "No, and tell it what to do differently", value: "deny" },
];
// ink-select-input's defaults highlight the selected row in plain `blue`, which reads as
// low-contrast/invisible on many terminal themes. Use the app's own accent color instead so the
// current selection is unmistakable, and bold the label too (color alone isn't enough for
// terminals with limited color support).
function PermissionIndicator({ isSelected }: { isSelected?: boolean }) {
return <Text color={ACCENT_HEX}>{isSelected ? "❯ " : " "}</Text>;
}
function PermissionItem({ isSelected, label }: { isSelected?: boolean; label?: string }) {
return (
<Text color={isSelected ? ACCENT_HEX : undefined} bold={isSelected}>
{label}
</Text>
);
}
export function PermissionPrompt({ toolName, args, preview, onSelect }: Props) {
// Color-code the preview when it is a real diff (additions green, removals red, hunk/file
// headers dimmed) - the diff is the single most important thing the user evaluates before
// approving a mutating tool, so rendering it as plain monochrome text throws away signal.
// Falls back to the old line-by-line render for non-diff previews (a bash command, "Create
// new file ...", JSON args, etc.).
const isDiff = looksLikeDiff(preview);
const previewLines = (preview ?? JSON.stringify(args)).split("\n");
return (
<Box borderStyle="round" borderColor={ACCENT_HEX} flexDirection="column" paddingX={1} width="100%">
<Text bold>{toolName}</Text>
<Text> </Text>
{isDiff && preview ? (
<DiffView diff={preview} />
) : (
previewLines.map((line, i) => <Text key={i}>{line}</Text>)
)}
<Text> </Text>
<Text>Do you want to proceed?</Text>
<SelectInput
items={OPTIONS}
indicatorComponent={PermissionIndicator}
itemComponent={PermissionItem}
onSelect={(item) => onSelect(item.value)}
/>
</Box>
);
}