refactor(ui): standardize on lipgloss semantic color system

Replace all fatih/color usages with internal/ui package that provides:
- Semantic color tokens (Pass, Warn, Fail, Accent, Muted)
- Adaptive light/dark mode support via Lipgloss AdaptiveColor
- Ayu theme colors for consistent, accessible output
- Tufte-inspired data-ink ratio principles

Files migrated: 35 command files in cmd/bd/

Add docs/ui-philosophy.md documenting:
- Semantic token usage guidelines
- Light/dark terminal optimization rationale
- Tufte and perceptual UI/UX theory application
- When to use (and not use) color in CLI output
This commit is contained in:
Ryan Snodgrass
2025-12-20 12:59:17 -08:00
parent fb1dff4f56
commit 6ca141712c
40 changed files with 887 additions and 646 deletions

View File

@@ -11,17 +11,18 @@ import (
"strings"
"time"
"github.com/fatih/color"
"github.com/spf13/cobra"
"github.com/steveyegge/beads/internal/storage"
"github.com/steveyegge/beads/internal/storage/sqlite"
"github.com/steveyegge/beads/internal/types"
"github.com/steveyegge/beads/internal/ui"
"github.com/steveyegge/beads/internal/utils"
)
var renamePrefixCmd = &cobra.Command{
Use: "rename-prefix <new-prefix>",
Short: "Rename the issue prefix for all issues in the database",
Use: "rename-prefix <new-prefix>",
GroupID: "advanced",
Short: "Rename the issue prefix for all issues in the database",
Long: `Rename the issue prefix for all issues in the database.
This will update all issue IDs and all text references across all fields.
@@ -99,12 +100,10 @@ NOTE: This is a rare operation. Most users never need this command.`,
if len(prefixes) > 1 {
// Multiple prefixes detected - requires repair mode
red := color.New(color.FgRed).SprintFunc()
yellow := color.New(color.FgYellow).SprintFunc()
fmt.Fprintf(os.Stderr, "%s Multiple prefixes detected in database:\n", red("✗"))
fmt.Fprintf(os.Stderr, "%s Multiple prefixes detected in database:\n", ui.RenderFail("✗"))
for prefix, count := range prefixes {
fmt.Fprintf(os.Stderr, " - %s: %d issues\n", yellow(prefix), count)
fmt.Fprintf(os.Stderr, " - %s: %d issues\n", ui.RenderWarn(prefix), count)
}
fmt.Fprintf(os.Stderr, "\n")
@@ -141,8 +140,7 @@ NOTE: This is a rare operation. Most users never need this command.`,
}
if dryRun {
cyan := color.New(color.FgCyan).SprintFunc()
fmt.Printf("DRY RUN: Would rename %d issues from prefix '%s' to '%s'\n\n", len(issues), oldPrefix, newPrefix)
fmt.Printf("DRY RUN: Would rename %d issues from prefix '%s' to '%s'\n\n", len(issues), oldPrefix, newPrefix)
fmt.Printf("Sample changes:\n")
for i, issue := range issues {
if i >= 5 {
@@ -151,13 +149,11 @@ NOTE: This is a rare operation. Most users never need this command.`,
}
oldID := fmt.Sprintf("%s-%s", oldPrefix, strings.TrimPrefix(issue.ID, oldPrefix+"-"))
newID := fmt.Sprintf("%s-%s", newPrefix, strings.TrimPrefix(issue.ID, oldPrefix+"-"))
fmt.Printf(" %s -> %s\n", cyan(oldID), cyan(newID))
fmt.Printf(" %s -> %s\n", ui.RenderAccent(oldID), ui.RenderAccent(newID))
}
return
}
green := color.New(color.FgGreen).SprintFunc()
cyan := color.New(color.FgCyan).SprintFunc()
fmt.Printf("Renaming %d issues from prefix '%s' to '%s'...\n", len(issues), oldPrefix, newPrefix)
@@ -169,7 +165,7 @@ NOTE: This is a rare operation. Most users never need this command.`,
// Schedule full export (IDs changed, incremental won't work)
markDirtyAndScheduleFullExport()
fmt.Printf("%s Successfully renamed prefix from %s to %s\n", green("✓"), cyan(oldPrefix), cyan(newPrefix))
fmt.Printf("%s Successfully renamed prefix from %s to %s\n", ui.RenderPass("✓"), ui.RenderAccent(oldPrefix), ui.RenderAccent(newPrefix))
if jsonOutput {
result := map[string]interface{}{
@@ -230,9 +226,6 @@ type issueSort struct {
// Issues with the correct prefix are left unchanged.
// Issues with incorrect prefixes get new hash-based IDs.
func repairPrefixes(ctx context.Context, st storage.Storage, actorName string, targetPrefix string, issues []*types.Issue, prefixes map[string]int, dryRun bool) error {
green := color.New(color.FgGreen).SprintFunc()
cyan := color.New(color.FgCyan).SprintFunc()
yellow := color.New(color.FgYellow).SprintFunc()
// Separate issues into correct and incorrect prefix groups
var correctIssues []*types.Issue
@@ -290,7 +283,7 @@ func repairPrefixes(ctx context.Context, st storage.Storage, actorName string, t
if dryRun {
fmt.Printf("DRY RUN: Would repair %d issues with incorrect prefixes\n\n", len(incorrectIssues))
fmt.Printf("Issues with correct prefix (%s): %d\n", cyan(targetPrefix), len(correctIssues))
fmt.Printf("Issues with correct prefix (%s): %d\n", ui.RenderAccent(targetPrefix), len(correctIssues))
fmt.Printf("Issues to repair: %d\n\n", len(incorrectIssues))
fmt.Printf("Planned renames (showing first 10):\n")
@@ -301,14 +294,14 @@ func repairPrefixes(ctx context.Context, st storage.Storage, actorName string, t
}
oldID := is.issue.ID
newID := renameMap[oldID]
fmt.Printf(" %s -> %s\n", yellow(oldID), cyan(newID))
fmt.Printf(" %s -> %s\n", ui.RenderWarn(oldID), ui.RenderAccent(newID))
}
return nil
}
// Perform the repairs
fmt.Printf("Repairing database with multiple prefixes...\n")
fmt.Printf(" Issues with correct prefix (%s): %d\n", cyan(targetPrefix), len(correctIssues))
fmt.Printf(" Issues with correct prefix (%s): %d\n", ui.RenderAccent(targetPrefix), len(correctIssues))
fmt.Printf(" Issues to repair: %d\n\n", len(incorrectIssues))
// Pattern to match any issue ID reference in text (both hash and sequential IDs)
@@ -348,7 +341,7 @@ func repairPrefixes(ctx context.Context, st storage.Storage, actorName string, t
return fmt.Errorf("failed to update issue %s -> %s: %w", oldID, newID, err)
}
fmt.Printf(" Renamed %s -> %s\n", yellow(oldID), cyan(newID))
fmt.Printf(" Renamed %s -> %s\n", ui.RenderWarn(oldID), ui.RenderAccent(newID))
}
// Update all dependencies to use new prefix
@@ -378,7 +371,7 @@ func repairPrefixes(ctx context.Context, st storage.Storage, actorName string, t
markDirtyAndScheduleFullExport()
fmt.Printf("\n%s Successfully consolidated %d prefixes into %s\n",
green("✓"), len(prefixes), cyan(targetPrefix))
ui.RenderPass("✓"), len(prefixes), ui.RenderAccent(targetPrefix))
fmt.Printf(" %d issues repaired, %d issues unchanged\n", len(incorrectIssues), len(correctIssues))
if jsonOutput {