refactor: move V1 code into v1/ subdirectory

- git mv cmd/ internal/ pkg/ test/ go.mod go.sum Makefile build.sh docs/ v1/
- Create v1/README.md with V1 documentation
- Update root README for V1 + V2 structure
- V1 still builds (cd v1 && go build ./cmd/hostkeeper) and 105 tests pass
- Root is now clean for V2 development
This commit is contained in:
swanadiva
2026-07-07 11:56:27 +07:00
parent 8ebdebedc8
commit 847989df75
68 changed files with 58 additions and 116 deletions
+137
View File
@@ -0,0 +1,137 @@
package tui
import (
"fmt"
"strings"
"time"
"github.com/charmbracelet/lipgloss"
)
// ErrorSeverity indicates the level of an error message
type ErrorSeverity int
const (
SevError ErrorSeverity = iota
SevWarning
SevInfo
)
// ErrorBanner displays a structured error message with title, details, and hints
type ErrorBanner struct {
Title string
Detail string
Hints []string
Severity ErrorSeverity
AutoDismiss bool
DismissAfter time.Duration
createdAt time.Time
visible bool
}
// NewErrorBanner creates a new error banner with the given severity
func NewErrorBanner(severity ErrorSeverity) *ErrorBanner {
return &ErrorBanner{
Severity: severity,
AutoDismiss: true,
DismissAfter: 5 * time.Second,
visible: false,
}
}
// Show displays the error banner with the given message
func (b *ErrorBanner) Show(title, detail string, hints ...string) {
b.Title = title
b.Detail = detail
b.Hints = hints
b.visible = true
b.createdAt = time.Now()
}
// Hide hides the error banner
func (b *ErrorBanner) Hide() {
b.visible = false
}
// IsVisible returns whether the banner is currently visible
func (b *ErrorBanner) IsVisible() bool {
return b.visible
}
// Update checks if auto-dismiss time has elapsed
func (b *ErrorBanner) Update() {
if b.visible && b.AutoDismiss && time.Since(b.createdAt) > b.DismissAfter {
b.visible = false
}
}
// View renders the error banner
func (b *ErrorBanner) View(width int) string {
if !b.visible {
return ""
}
var (
titleStyle lipgloss.Style
borderColor lipgloss.Color
prefix string
)
switch b.Severity {
case SevError:
titleStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#ea6962")).Bold(true)
borderColor = lipgloss.Color("#ea6962")
prefix = "✖"
case SevWarning:
titleStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#d8a657")).Bold(true)
borderColor = lipgloss.Color("#d8a657")
prefix = "⚠"
case SevInfo:
titleStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#7daea3")).Bold(true)
borderColor = lipgloss.Color("#7daea3")
prefix = ""
}
var sb strings.Builder
// Title line with prefix
sb.WriteString(titleStyle.Render(fmt.Sprintf("%s %s", prefix, b.Title)))
// Detail line
if b.Detail != "" {
sb.WriteString("\n")
sb.WriteString(strings.Repeat(" ", len(prefix)+1))
detailStyle := lipgloss.NewStyle().Foreground(activeTheme.Fg)
sb.WriteString(detailStyle.Render(b.Detail))
}
// Hints
if len(b.Hints) > 0 {
sb.WriteString("\n")
sb.WriteString(strings.Repeat(" ", len(prefix)+1))
hintStyle := lipgloss.NewStyle().Foreground(activeTheme.FgMute)
sb.WriteString(hintStyle.Render("Hints:"))
for i, hint := range b.Hints {
sb.WriteString("\n")
sb.WriteString(strings.Repeat(" ", len(prefix)+2))
sb.WriteString(hintStyle.Render(fmt.Sprintf("%d. %s", i+1, hint)))
}
}
// Wrap in a styled box
borderStyle := lipgloss.NewStyle().
Border(lipgloss.RoundedBorder()).
BorderForeground(borderColor).
Padding(0, 1).
Width(min(width-2, 80))
return borderStyle.Render(sb.String())
}
// min returns the smaller of two integers
func min(a, b int) int {
if a < b {
return a
}
return b
}