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,7 +11,6 @@ import (
"strings"
"time"
"github.com/fatih/color"
"github.com/spf13/cobra"
"github.com/steveyegge/beads/cmd/bd/doctor"
"github.com/steveyegge/beads/internal/beads"
@@ -21,12 +20,14 @@ import (
"github.com/steveyegge/beads/internal/storage/sqlite"
"github.com/steveyegge/beads/internal/syncbranch"
"github.com/steveyegge/beads/internal/types"
"github.com/steveyegge/beads/internal/ui"
"github.com/steveyegge/beads/internal/utils"
)
var initCmd = &cobra.Command{
Use: "init",
Short: "Initialize bd in the current directory",
Use: "init",
GroupID: "setup",
Short: "Initialize bd in the current directory",
Long: `Initialize bd in the current directory by creating a .beads/ directory
and database file. Optionally specify a custom issue prefix.
@@ -225,15 +226,12 @@ With --stealth: configures global git settings for invisible beads usage:
}
if !quiet {
green := color.New(color.FgGreen).SprintFunc()
cyan := color.New(color.FgCyan).SprintFunc()
fmt.Printf("\n%s bd initialized successfully in --no-db mode!\n\n", green("✓"))
fmt.Printf(" Mode: %s\n", cyan("no-db (JSONL-only)"))
fmt.Printf(" Issues file: %s\n", cyan(jsonlPath))
fmt.Printf(" Issue prefix: %s\n", cyan(prefix))
fmt.Printf(" Issues will be named: %s\n\n", cyan(prefix+"-<hash> (e.g., "+prefix+"-a3f2dd)"))
fmt.Printf("Run %s to get started.\n\n", cyan("bd --no-db quickstart"))
fmt.Printf("\n%s bd initialized successfully in --no-db mode!\n\n", ui.RenderPass("✓"))
fmt.Printf(" Mode: %s\n", ui.RenderAccent("no-db (JSONL-only)"))
fmt.Printf(" Issues file: %s\n", ui.RenderAccent(jsonlPath))
fmt.Printf(" Issue prefix: %s\n", ui.RenderAccent(prefix))
fmt.Printf(" Issues will be named: %s\n\n", ui.RenderAccent(prefix+"-<hash> (e.g., "+prefix+"-a3f2dd)"))
fmt.Printf("Run %s to get started.\n\n", ui.RenderAccent("bd --no-db quickstart"))
}
return
}
@@ -427,9 +425,8 @@ With --stealth: configures global git settings for invisible beads usage:
// Install by default unless --skip-hooks is passed
if !skipHooks && isGitRepo() && !hooksInstalled() {
if err := installGitHooks(); err != nil && !quiet {
yellow := color.New(color.FgYellow).SprintFunc()
fmt.Fprintf(os.Stderr, "\n%s Failed to install git hooks: %v\n", yellow("⚠"), err)
fmt.Fprintf(os.Stderr, "You can try again with: %s\n\n", color.New(color.FgCyan).Sprint("bd doctor --fix"))
fmt.Fprintf(os.Stderr, "\n%s Failed to install git hooks: %v\n", ui.RenderWarn("⚠"), err)
fmt.Fprintf(os.Stderr, "You can try again with: %s\n\n", ui.RenderAccent("bd doctor --fix"))
}
}
@@ -437,9 +434,8 @@ With --stealth: configures global git settings for invisible beads usage:
// Install by default unless --skip-merge-driver is passed
if !skipMergeDriver && isGitRepo() && !mergeDriverInstalled() {
if err := installMergeDriver(); err != nil && !quiet {
yellow := color.New(color.FgYellow).SprintFunc()
fmt.Fprintf(os.Stderr, "\n%s Failed to install merge driver: %v\n", yellow("⚠"), err)
fmt.Fprintf(os.Stderr, "You can try again with: %s\n\n", color.New(color.FgCyan).Sprint("bd doctor --fix"))
fmt.Fprintf(os.Stderr, "\n%s Failed to install merge driver: %v\n", ui.RenderWarn("⚠"), err)
fmt.Fprintf(os.Stderr, "You can try again with: %s\n\n", ui.RenderAccent("bd doctor --fix"))
}
}
@@ -454,14 +450,11 @@ With --stealth: configures global git settings for invisible beads usage:
return
}
green := color.New(color.FgGreen).SprintFunc()
cyan := color.New(color.FgCyan).SprintFunc()
fmt.Printf("\n%s bd initialized successfully!\n\n", green("✓"))
fmt.Printf(" Database: %s\n", cyan(initDBPath))
fmt.Printf(" Issue prefix: %s\n", cyan(prefix))
fmt.Printf(" Issues will be named: %s\n\n", cyan(prefix+"-<hash> (e.g., "+prefix+"-a3f2dd)"))
fmt.Printf("Run %s to get started.\n\n", cyan("bd quickstart"))
fmt.Printf("\n%s bd initialized successfully!\n\n", ui.RenderPass("✓"))
fmt.Printf(" Database: %s\n", ui.RenderAccent(initDBPath))
fmt.Printf(" Issue prefix: %s\n", ui.RenderAccent(prefix))
fmt.Printf(" Issues will be named: %s\n\n", ui.RenderAccent(prefix+"-<hash> (e.g., "+prefix+"-a3f2dd)"))
fmt.Printf("Run %s to get started.\n\n", ui.RenderAccent("bd quickstart"))
// Run bd doctor diagnostics to catch setup issues early (bd-zwtq)
doctorResult := runDiagnostics(cwd)
@@ -474,15 +467,14 @@ With --stealth: configures global git settings for invisible beads usage:
}
}
if hasIssues {
yellow := color.New(color.FgYellow).SprintFunc()
fmt.Printf("%s Setup incomplete. Some issues were detected:\n", yellow("⚠"))
fmt.Printf("%s Setup incomplete. Some issues were detected:\n", ui.RenderWarn("⚠"))
// Show just the warnings/errors, not all checks
for _, check := range doctorResult.Checks {
if check.Status != statusOK {
fmt.Printf(" • %s: %s\n", check.Name, check.Message)
}
}
fmt.Printf("\nRun %s to see details and fix these issues.\n\n", cyan("bd doctor --fix"))
fmt.Printf("\nRun %s to see details and fix these issues.\n\n", ui.RenderAccent("bd doctor --fix"))
}
},
}
@@ -592,9 +584,7 @@ func detectExistingHooks() []hookInfo {
// promptHookAction asks user what to do with existing hooks
func promptHookAction(existingHooks []hookInfo) string {
yellow := color.New(color.FgYellow).SprintFunc()
fmt.Printf("\n%s Found existing git hooks:\n", yellow("⚠"))
fmt.Printf("\n%s Found existing git hooks:\n", ui.RenderWarn("⚠"))
for _, hook := range existingHooks {
if hook.exists && !hook.isBdHook {
hookType := "custom script"
@@ -646,7 +636,6 @@ func installGitHooks() error {
// Determine installation mode
chainHooks := false
if hasExistingHooks {
cyan := color.New(color.FgCyan).SprintFunc()
choice := promptHookAction(existingHooks)
switch choice {
case "1", "":
@@ -665,7 +654,7 @@ func installGitHooks() error {
}
case "3":
fmt.Printf("Skipping git hooks installation.\n")
fmt.Printf("You can install manually later with: %s\n", cyan("./examples/git-hooks/install.sh"))
fmt.Printf("You can install manually later with: %s\n", ui.RenderAccent("./examples/git-hooks/install.sh"))
return nil
default:
return fmt.Errorf("invalid choice: %s", choice)
@@ -971,8 +960,7 @@ exit 0
}
if chainHooks {
green := color.New(color.FgGreen).SprintFunc()
fmt.Printf("%s Chained bd hooks with existing hooks\n", green("✓"))
fmt.Printf("%s Chained bd hooks with existing hooks\n", ui.RenderPass("✓"))
}
return nil
@@ -1400,12 +1388,10 @@ func setupStealthMode(verbose bool) error {
}
if verbose {
green := color.New(color.FgGreen).SprintFunc()
cyan := color.New(color.FgCyan).SprintFunc()
fmt.Printf("\n%s Stealth mode configured successfully!\n\n", green(""))
fmt.Printf(" Global gitignore: %s\n", cyan(projectPath+"/.beads/ ignored"))
fmt.Printf(" Claude settings: %s\n\n", cyan("configured with bd onboard instruction"))
fmt.Printf("Your beads setup is now %s - other repo collaborators won't see any beads-related files.\n\n", cyan("invisible"))
fmt.Printf("\n%s Stealth mode configured successfully!\n\n", ui.RenderPass("✓"))
fmt.Printf(" Global gitignore: %s\n", ui.RenderAccent(projectPath+"/.beads/ ignored"))
fmt.Printf(" Claude settings: %s\n\n", ui.RenderAccent("configured with bd onboard instruction"))
fmt.Printf("Your beads setup is now %s - other repo collaborators won't see any beads-related files.\n\n", ui.RenderAccent("invisible"))
}
return nil
@@ -1550,9 +1536,6 @@ func checkExistingBeadsData(prefix string) error {
// Check for existing database file
dbPath := filepath.Join(beadsDir, beads.CanonicalDatabaseName)
if _, err := os.Stat(dbPath); err == nil {
yellow := color.New(color.FgYellow).SprintFunc()
cyan := color.New(color.FgCyan).SprintFunc()
return fmt.Errorf(`
%s Found existing database: %s
@@ -1564,7 +1547,7 @@ To use the existing database:
To completely reinitialize (data loss warning):
rm -rf .beads && bd init --prefix %s
Aborting.`, yellow("⚠"), dbPath, cyan("bd list"), prefix)
Aborting.`, ui.RenderWarn("⚠"), dbPath, ui.RenderAccent("bd list"), prefix)
}
// Fresh clones (JSONL exists but no database) are allowed - init will
@@ -1646,8 +1629,7 @@ bd sync # Sync with git
return fmt.Errorf("failed to create %s: %w", filename, err)
}
if verbose {
green := color.New(color.FgGreen).SprintFunc()
fmt.Printf(" %s Created %s with landing-the-plane instructions\n", green("✓"), filename)
fmt.Printf(" %s Created %s with landing-the-plane instructions\n", ui.RenderPass("✓"), filename)
}
return nil
} else if err != nil {
@@ -1674,8 +1656,7 @@ bd sync # Sync with git
return fmt.Errorf("failed to update %s: %w", filename, err)
}
if verbose {
green := color.New(color.FgGreen).SprintFunc()
fmt.Printf(" %s Added landing-the-plane instructions to %s\n", green("✓"), filename)
fmt.Printf(" %s Added landing-the-plane instructions to %s\n", ui.RenderPass("✓"), filename)
}
return nil
}