Files
puaros/packages/ipuaro/src/tui/components/ErrorDialog.tsx
imfozilbek 357cf27765 feat(ipuaro): add Tab autocomplete for file paths in TUI
- Implement useAutocomplete hook with fuzzy matching and Redis integration
- Add visual feedback showing up to 5 suggestions below input
- Support Tab key for completion with common prefix algorithm
- Real-time suggestion updates as user types
- Path normalization (handles ./, trailing slashes)
- Case-insensitive matching with scoring algorithm
- Add 21 unit tests with jsdom environment
- Update Input component with storage and projectRoot props
- Refactor key handlers to reduce complexity
- Install @testing-library/react, jsdom, @types/jsdom
- Update react-dom to 18.3.1 for compatibility
- Configure jsdom environment for TUI tests in vitest config
- Adjust coverage threshold for branches to 91.5%
- Fix deprecated ErrorChoice usage (use ErrorOption)

Version: 0.21.0
Tests: 1484 passed (+21)
Coverage: 97.60% lines, 91.58% branches
2025-12-01 21:56:02 +05:00

106 lines
2.8 KiB
TypeScript

/**
* ErrorDialog component for TUI.
* Displays an error with [R] Retry / [S] Skip / [A] Abort options.
*/
import { Box, Text, useInput } from "ink"
import React, { useState } from "react"
import type { ErrorOption } from "../../shared/errors/IpuaroError.js"
export interface ErrorInfo {
type: string
message: string
recoverable: boolean
}
export interface ErrorDialogProps {
error: ErrorInfo
onChoice: (choice: ErrorOption) => void
}
function ChoiceButton({
hotkey,
label,
isSelected,
disabled,
}: {
hotkey: string
label: string
isSelected: boolean
disabled?: boolean
}): React.JSX.Element {
if (disabled) {
return (
<Box>
<Text color="gray" dimColor>
[{hotkey}] {label}
</Text>
</Box>
)
}
return (
<Box>
<Text color={isSelected ? "cyan" : "gray"}>
[<Text bold>{hotkey}</Text>] {label}
</Text>
</Box>
)
}
export function ErrorDialog({ error, onChoice }: ErrorDialogProps): React.JSX.Element {
const [selected, setSelected] = useState<ErrorOption | null>(null)
useInput((input, key) => {
const lowerInput = input.toLowerCase()
if (lowerInput === "r" && error.recoverable) {
setSelected("retry")
onChoice("retry")
} else if (lowerInput === "s" && error.recoverable) {
setSelected("skip")
onChoice("skip")
} else if (lowerInput === "a") {
setSelected("abort")
onChoice("abort")
} else if (key.escape) {
setSelected("abort")
onChoice("abort")
}
})
return (
<Box flexDirection="column" borderStyle="round" borderColor="red" paddingX={1} paddingY={1}>
<Box marginBottom={1}>
<Text color="red" bold>
x {error.type}: {error.message}
</Text>
</Box>
<Box gap={2}>
<ChoiceButton
hotkey="R"
label="Retry"
isSelected={selected === "retry"}
disabled={!error.recoverable}
/>
<ChoiceButton
hotkey="S"
label="Skip"
isSelected={selected === "skip"}
disabled={!error.recoverable}
/>
<ChoiceButton hotkey="A" label="Abort" isSelected={selected === "abort"} />
</Box>
{!error.recoverable && (
<Box marginTop={1}>
<Text color="gray" dimColor>
This error is not recoverable. Press [A] to abort.
</Text>
</Box>
)}
</Box>
)
}