diff --git a/CLAUDE.md b/CLAUDE.md index df878195..39d2b07f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -857,3 +857,8 @@ For more details, see README.md and docs/QUICKSTART.md. - NEVER stop before pushing - that leaves work stranded locally - NEVER say "ready to push when you are" - YOU must push - If push fails, resolve and retry until it succeeds + + +## Pull Requests (PR) + +- Make sure that .beads/issues.jsonl is never submitted during PRs; revert the changes to .beads/issues.jsonl on the branch. diff --git a/cmd/bd/main.go b/cmd/bd/main.go index 3f06fb0d..8b17b3e9 100644 --- a/cmd/bd/main.go +++ b/cmd/bd/main.go @@ -6,6 +6,7 @@ import ( "os" "os/signal" "path/filepath" + "regexp" "runtime/pprof" "runtime/trace" "slices" @@ -24,6 +25,7 @@ import ( "github.com/steveyegge/beads/internal/storage" "github.com/steveyegge/beads/internal/storage/memory" "github.com/steveyegge/beads/internal/storage/sqlite" + "github.com/steveyegge/beads/internal/ui" "github.com/steveyegge/beads/internal/utils" ) @@ -140,6 +142,148 @@ func init() { // Add --version flag to root command (same behavior as version subcommand) rootCmd.Flags().BoolP("version", "V", false, "Print version information") + + // Command groups for organized help output (Tufte-inspired) + rootCmd.AddGroup(&cobra.Group{ID: "issues", Title: "Working With Issues:"}) + rootCmd.AddGroup(&cobra.Group{ID: "views", Title: "Views & Reports:"}) + rootCmd.AddGroup(&cobra.Group{ID: "deps", Title: "Dependencies & Structure:"}) + rootCmd.AddGroup(&cobra.Group{ID: "sync", Title: "Sync & Data:"}) + rootCmd.AddGroup(&cobra.Group{ID: "setup", Title: "Setup & Configuration:"}) + // NOTE: Many maintenance commands (clean, cleanup, compact, validate, repair-deps) + // should eventually be consolidated into 'bd doctor' and 'bd doctor --fix' to simplify + // the user experience. The doctor command can detect issues and offer fixes interactively. + rootCmd.AddGroup(&cobra.Group{ID: "maint", Title: "Maintenance:"}) + rootCmd.AddGroup(&cobra.Group{ID: "advanced", Title: "Integrations & Advanced:"}) + + // Custom help function with semantic coloring (Tufte-inspired) + // Note: Usage output (shown on errors) is not styled to avoid recursion issues + rootCmd.SetHelpFunc(colorizedHelpFunc) +} + +// colorizedHelpFunc wraps Cobra's default help with semantic coloring +// Applies subtle accent color to group headers for visual hierarchy +func colorizedHelpFunc(cmd *cobra.Command, args []string) { + // Build full help output: Long description + Usage + var output strings.Builder + + // Include Long description first (like Cobra's default help) + if cmd.Long != "" { + output.WriteString(cmd.Long) + output.WriteString("\n\n") + } else if cmd.Short != "" { + output.WriteString(cmd.Short) + output.WriteString("\n\n") + } + + // Add the usage string which contains commands, flags, etc. + output.WriteString(cmd.UsageString()) + + // Apply semantic coloring + result := colorizeHelpOutput(output.String()) + fmt.Print(result) +} + +// colorizeHelpOutput applies semantic colors to help text +// - Group headers get accent color for visual hierarchy +// - Section headers (Examples:, Flags:) get accent color +// - Command names get subtle styling for scanability +// - Flag names get bold styling, types get muted +// - Default values get muted styling +func colorizeHelpOutput(help string) string { + // Match group header lines (e.g., "Working With Issues:") + // These are standalone lines ending with ":" and followed by commands + groupHeaderRE := regexp.MustCompile(`(?m)^([A-Z][A-Za-z &]+:)\s*$`) + + result := groupHeaderRE.ReplaceAllStringFunc(help, func(match string) string { + // Trim whitespace, colorize, then restore + trimmed := strings.TrimSpace(match) + return ui.RenderAccent(trimmed) + }) + + // Match section headers in subcommand help (Examples:, Flags:, etc.) + sectionHeaderRE := regexp.MustCompile(`(?m)^(Examples|Flags|Usage|Global Flags|Aliases|Available Commands):`) + result = sectionHeaderRE.ReplaceAllStringFunc(result, func(match string) string { + return ui.RenderAccent(match) + }) + + // Match command lines: " command Description text" + // Commands are indented with 2 spaces, followed by spaces, then description + // Pattern matches: indent + command-name (with hyphens) + spacing + description + cmdLineRE := regexp.MustCompile(`(?m)^( )([a-z][a-z0-9]*(?:-[a-z0-9]+)*)(\s{2,})(.*)$`) + + result = cmdLineRE.ReplaceAllStringFunc(result, func(match string) string { + parts := cmdLineRE.FindStringSubmatch(match) + if len(parts) != 5 { + return match + } + indent := parts[1] + cmdName := parts[2] + spacing := parts[3] + description := parts[4] + + // Colorize command references in description (e.g., 'comments add') + description = colorizeCommandRefs(description) + + // Highlight entry point hints (e.g., "(start here)") + description = highlightEntryPoints(description) + + // Subtle styling on command name for scanability + return indent + ui.RenderCommand(cmdName) + spacing + description + }) + + // Match flag lines: " -f, --file string Description" + // Pattern: indent + flags + spacing + optional type + description + flagLineRE := regexp.MustCompile(`(?m)^(\s+)(-\w,\s+--[\w-]+|--[\w-]+)(\s+)(string|int|duration|bool)?(\s*.*)$`) + result = flagLineRE.ReplaceAllStringFunc(result, func(match string) string { + parts := flagLineRE.FindStringSubmatch(match) + if len(parts) < 6 { + return match + } + indent := parts[1] + flags := parts[2] + spacing := parts[3] + typeStr := parts[4] + desc := parts[5] + + // Mute default values in description + desc = muteDefaults(desc) + + if typeStr != "" { + return indent + ui.RenderCommand(flags) + spacing + ui.RenderMuted(typeStr) + desc + } + return indent + ui.RenderCommand(flags) + spacing + desc + }) + + return result +} + +// muteDefaults applies muted styling to default value annotations +func muteDefaults(text string) string { + defaultRE := regexp.MustCompile(`(\(default[^)]*\))`) + return defaultRE.ReplaceAllStringFunc(text, func(match string) string { + return ui.RenderMuted(match) + }) +} + +// highlightEntryPoints applies accent styling to entry point hints like "(start here)" +func highlightEntryPoints(text string) string { + entryRE := regexp.MustCompile(`(\(start here\))`) + return entryRE.ReplaceAllStringFunc(text, func(match string) string { + return ui.RenderAccent(match) + }) +} + +// colorizeCommandRefs applies command styling to references in text +// Matches patterns like 'command name' or 'bd command' +func colorizeCommandRefs(text string) string { + // Match 'command words' in single quotes (e.g., 'comments add') + cmdRefRE := regexp.MustCompile(`'([a-z][a-z0-9 -]+)'`) + + return cmdRefRE.ReplaceAllStringFunc(text, func(match string) string { + // Extract the command name without quotes + inner := match[1 : len(match)-1] + return "'" + ui.RenderCommand(inner) + "'" + }) } var rootCmd = &cobra.Command{ diff --git a/docs/UI_PHILOSOPHY.md b/docs/UI_PHILOSOPHY.md new file mode 100644 index 00000000..a2ea416e --- /dev/null +++ b/docs/UI_PHILOSOPHY.md @@ -0,0 +1,57 @@ +# UI/UX Philosophy + +Beads CLI follows Tufte-inspired design principles for terminal output. + +## Core Principles + +1. **Maximize data-ink ratio**: Only color what demands attention +2. **Respect cognitive load**: Let whitespace and position do most of the work +3. **Create scannable hierarchy**: Headers mark territory, bold creates scan targets + +## Color Usage + +| Purpose | Style | When to Use | +|---------|-------|-------------| +| Navigation landmarks | Accent (blue) | Section headers, group titles | +| Scan targets | Bold | Command names, flag names | +| De-emphasized | Muted (gray) | Types, defaults, closed items | +| Semantic states | Pass/Warn/Fail | P0/P1 priority, bugs, blocked | +| Standard text | Plain | Descriptions, prose, examples | + +## Anti-Patterns + +- Don't highlight everything (defeats the purpose) +- Don't use color for decoration +- Don't style closed/completed items (they're done, users don't care) +- Keep examples plain (copy-paste friendly) + +## Help Output Styling + +### Main Help (`bd help`) + +- **Group headers** (Working With Issues:, Views & Reports:, etc.): Accent color +- **Command names** in listings: Bold +- **Command references** in descriptions ('comments add'): Bold + +### Subcommand Help (`bd --help`) + +- **Section headers** (Usage:, Flags:, Examples:): Accent color +- **Flag names** (-f, --file): Bold +- **Type annotations** (string, int, duration): Muted +- **Default values** (default: ...): Muted +- **Descriptions**: Plain +- **Examples**: Plain (copy-paste friendly) + +## Ayu Theme + +All colors use the Ayu theme with adaptive light/dark mode support. +See `internal/ui/styles.go` for implementation. + +| Color | Light Mode | Dark Mode | Usage | +|-------|------------|-----------|-------| +| Accent | #399ee6 | #59c2ff | Headers, links | +| Command | Bold white | Bold white | Command/flag names | +| Muted | #828c99 | #6c7680 | Types, defaults | +| Pass | #6cbf43 | #7fd962 | Success states | +| Warn | #e6ba7e | #ffb454 | Warnings, P1 | +| Fail | #f07171 | #f26d78 | Errors, bugs, P0 |