feat(ux): visual improvements for list tree, graph, and show commands

bd list --tree:
- Use actual parent-child dependencies instead of dotted ID hierarchy
- Treat epic dependencies as parent-child relationships
- Sort children by priority (P0 first)
- Fix tree display in daemon mode with read-only store access

bd graph:
- Add --all flag to show dependency graph of all open issues
- Add --compact flag for tree-style rendering (reduces 44+ lines to 13)
- Fix "needs:N" cognitive noise by using semantic colors
- Add blocks:N indicator with semantic red coloring

bd show:
- Tufte-aligned header with status icon, priority, and type badges
- Add glamour markdown rendering with auto light/dark mode detection
- Cap markdown line width at 100 chars for readability
- Mute entire row for closed dependencies (work done, no attention needed)

Design system:
- Add shared status icons (○ ◐ ● ✓ ❄) with semantic colors
- Implement priority colors: P0 red, P1 orange, P2 muted gold, P3-P4 neutral
- Add TrueColor profile for distinct hex color rendering
- Type badges for epic (purple) and bug (red)

Design principles:
- Semantic colors only for actionable items
- Closed items fade (muted gray)
- Icons > text labels for better scanability

Co-Authored-By: SageOx <ox@sageox.ai>
This commit is contained in:
Ryan Snodgrass
2026-01-08 20:49:09 -08:00
parent 7e70de1f6d
commit cfd1f39e1e
11 changed files with 1064 additions and 319 deletions

View File

@@ -131,14 +131,14 @@ var showCmd = &cobra.Command{
allDetails = append(allDetails, details)
} else {
if displayIdx > 0 {
fmt.Println("\n" + strings.Repeat("─", 60))
fmt.Println("\n" + ui.RenderMuted(strings.Repeat("─", 60)))
}
fmt.Printf("\n%s: %s\n", ui.RenderAccent(issue.ID), issue.Title)
fmt.Printf("Status: %s\n", issue.Status)
fmt.Printf("Priority: P%d\n", issue.Priority)
fmt.Printf("Type: %s\n", issue.IssueType)
// Tufte-aligned header: STATUS_ICON ID · Title [Priority · STATUS]
fmt.Printf("\n%s\n", formatIssueHeader(issue))
// Metadata: Owner · Type | Created · Updated
fmt.Println(formatIssueMetadata(issue))
if issue.Description != "" {
fmt.Printf("\nDescription:\n%s\n", issue.Description)
fmt.Printf("\n%s\n%s\n", ui.RenderBold("DESCRIPTION"), ui.RenderMarkdown(issue.Description))
}
fmt.Println()
displayIdx++
@@ -188,54 +188,17 @@ var showCmd = &cobra.Command{
}
if displayIdx > 0 {
fmt.Println("\n" + strings.Repeat("─", 60))
fmt.Println("\n" + ui.RenderMuted(strings.Repeat("─", 60)))
}
displayIdx++
// Format output (same as direct mode below)
tierEmoji := ""
statusSuffix := ""
switch issue.CompactionLevel {
case 1:
tierEmoji = " 🗜️"
statusSuffix = " (compacted L1)"
case 2:
tierEmoji = " 📦"
statusSuffix = " (compacted L2)"
}
// Tufte-aligned header: STATUS_ICON ID · Title [Priority · STATUS]
fmt.Printf("\n%s\n", formatIssueHeader(issue))
fmt.Printf("\n%s: %s%s\n", ui.RenderAccent(issue.ID), issue.Title, tierEmoji)
fmt.Printf("Status: %s%s\n", issue.Status, statusSuffix)
if issue.CloseReason != "" {
fmt.Printf("Close reason: %s\n", issue.CloseReason)
}
if issue.ClosedBySession != "" {
fmt.Printf("Closed by session: %s\n", issue.ClosedBySession)
}
fmt.Printf("Priority: P%d\n", issue.Priority)
fmt.Printf("Type: %s\n", issue.IssueType)
if issue.Assignee != "" {
fmt.Printf("Assignee: %s\n", issue.Assignee)
}
if issue.EstimatedMinutes != nil {
fmt.Printf("Estimated: %d minutes\n", *issue.EstimatedMinutes)
}
fmt.Printf("Created: %s\n", issue.CreatedAt.Format("2006-01-02 15:04"))
if issue.CreatedBy != "" {
fmt.Printf("Created by: %s\n", issue.CreatedBy)
}
fmt.Printf("Updated: %s\n", issue.UpdatedAt.Format("2006-01-02 15:04"))
if issue.DueAt != nil {
fmt.Printf("Due: %s\n", issue.DueAt.Format("2006-01-02 15:04"))
}
if issue.DeferUntil != nil {
fmt.Printf("Deferred until: %s\n", issue.DeferUntil.Format("2006-01-02 15:04"))
}
if issue.ExternalRef != nil && *issue.ExternalRef != "" {
fmt.Printf("External Ref: %s\n", *issue.ExternalRef)
}
// Metadata: Owner · Type | Created · Updated
fmt.Println(formatIssueMetadata(issue))
// Show compaction status
// Compaction info (if applicable)
if issue.CompactionLevel > 0 {
fmt.Println()
if issue.OriginalSize > 0 {
@@ -243,47 +206,40 @@ var showCmd = &cobra.Command{
saved := issue.OriginalSize - currentSize
if saved > 0 {
reduction := float64(saved) / float64(issue.OriginalSize) * 100
fmt.Printf("📊 Original: %d bytes | Compressed: %d bytes (%.0f%% reduction)\n",
fmt.Printf("📊 %d → %d bytes (%.0f%% reduction)\n",
issue.OriginalSize, currentSize, reduction)
}
}
tierEmoji2 := "🗜️"
if issue.CompactionLevel == 2 {
tierEmoji2 = "📦"
}
compactedDate := ""
if issue.CompactedAt != nil {
compactedDate = issue.CompactedAt.Format("2006-01-02")
}
fmt.Printf("%s Compacted: %s (Tier %d)\n", tierEmoji2, compactedDate, issue.CompactionLevel)
}
// Content sections
if issue.Description != "" {
fmt.Printf("\nDescription:\n%s\n", issue.Description)
fmt.Printf("\n%s\n%s\n", ui.RenderBold("DESCRIPTION"), ui.RenderMarkdown(issue.Description))
}
if issue.Design != "" {
fmt.Printf("\nDesign:\n%s\n", issue.Design)
fmt.Printf("\n%s\n%s\n", ui.RenderBold("DESIGN"), ui.RenderMarkdown(issue.Design))
}
if issue.Notes != "" {
fmt.Printf("\nNotes:\n%s\n", issue.Notes)
fmt.Printf("\n%s\n%s\n", ui.RenderBold("NOTES"), ui.RenderMarkdown(issue.Notes))
}
if issue.AcceptanceCriteria != "" {
fmt.Printf("\nAcceptance Criteria:\n%s\n", issue.AcceptanceCriteria)
fmt.Printf("\n%s\n%s\n", ui.RenderBold("ACCEPTANCE CRITERIA"), ui.RenderMarkdown(issue.AcceptanceCriteria))
}
if len(details.Labels) > 0 {
fmt.Printf("\nLabels: %v\n", details.Labels)
fmt.Printf("\n%s %s\n", ui.RenderBold("LABELS:"), strings.Join(details.Labels, ", "))
}
// Dependencies with semantic colors
if len(details.Dependencies) > 0 {
fmt.Printf("\nDepends on (%d):\n", len(details.Dependencies))
fmt.Printf("\n%s\n", ui.RenderBold("DEPENDS ON"))
for _, dep := range details.Dependencies {
fmt.Printf(" → %s: %s [P%d]\n", dep.ID, dep.Title, dep.Priority)
fmt.Println(formatDependencyLine("→", dep))
}
}
// Dependents grouped by type with semantic colors
if len(details.Dependents) > 0 {
// Group by dependency type for clarity
var blocks, children, related, discovered []*types.IssueWithDependencyMetadata
for _, dep := range details.Dependents {
switch dep.DependencyType {
@@ -301,35 +257,35 @@ var showCmd = &cobra.Command{
}
if len(children) > 0 {
fmt.Printf("\nChildren (%d):\n", len(children))
fmt.Printf("\n%s\n", ui.RenderBold("CHILDREN"))
for _, dep := range children {
fmt.Printf(" ↳ %s: %s [P%d - %s]\n", dep.ID, dep.Title, dep.Priority, dep.Status)
fmt.Println(formatDependencyLine("↳", dep))
}
}
if len(blocks) > 0 {
fmt.Printf("\nBlocks (%d):\n", len(blocks))
fmt.Printf("\n%s\n", ui.RenderBold("BLOCKS"))
for _, dep := range blocks {
fmt.Printf(" ← %s: %s [P%d - %s]\n", dep.ID, dep.Title, dep.Priority, dep.Status)
fmt.Println(formatDependencyLine("←", dep))
}
}
if len(related) > 0 {
fmt.Printf("\nRelated (%d):\n", len(related))
fmt.Printf("\n%s\n", ui.RenderBold("RELATED"))
for _, dep := range related {
fmt.Printf(" ↔ %s: %s [P%d - %s]\n", dep.ID, dep.Title, dep.Priority, dep.Status)
fmt.Println(formatDependencyLine("↔", dep))
}
}
if len(discovered) > 0 {
fmt.Printf("\nDiscovered (%d):\n", len(discovered))
fmt.Printf("\n%s\n", ui.RenderBold("DISCOVERED"))
for _, dep := range discovered {
fmt.Printf(" ◊ %s: %s [P%d - %s]\n", dep.ID, dep.Title, dep.Priority, dep.Status)
fmt.Println(formatDependencyLine("◊", dep))
}
}
}
if len(details.Comments) > 0 {
fmt.Printf("\nComments (%d):\n", len(details.Comments))
fmt.Printf("\n%s\n", ui.RenderBold("COMMENTS"))
for _, comment := range details.Comments {
fmt.Printf(" [%s] %s\n", comment.Author, comment.CreatedAt.Format("2006-01-02 15:04"))
fmt.Printf(" %s %s\n", ui.RenderMuted(comment.CreatedAt.Format("2006-01-02")), comment.Author)
commentLines := strings.Split(comment.Text, "\n")
for _, line := range commentLines {
fmt.Printf(" %s\n", line)
@@ -418,102 +374,55 @@ var showCmd = &cobra.Command{
}
if idx > 0 {
fmt.Println("\n" + strings.Repeat("─", 60))
fmt.Println("\n" + ui.RenderMuted(strings.Repeat("─", 60)))
}
// Add compaction emoji to title line
tierEmoji := ""
statusSuffix := ""
switch issue.CompactionLevel {
case 1:
tierEmoji = " 🗜️"
statusSuffix = " (compacted L1)"
case 2:
tierEmoji = " 📦"
statusSuffix = " (compacted L2)"
}
// Tufte-aligned header: STATUS_ICON ID · Title [Priority · STATUS]
fmt.Printf("\n%s\n", formatIssueHeader(issue))
fmt.Printf("\n%s: %s%s\n", ui.RenderAccent(issue.ID), issue.Title, tierEmoji)
fmt.Printf("Status: %s%s\n", issue.Status, statusSuffix)
if issue.CloseReason != "" {
fmt.Printf("Close reason: %s\n", issue.CloseReason)
}
if issue.ClosedBySession != "" {
fmt.Printf("Closed by session: %s\n", issue.ClosedBySession)
}
fmt.Printf("Priority: P%d\n", issue.Priority)
fmt.Printf("Type: %s\n", issue.IssueType)
if issue.Assignee != "" {
fmt.Printf("Assignee: %s\n", issue.Assignee)
}
if issue.EstimatedMinutes != nil {
fmt.Printf("Estimated: %d minutes\n", *issue.EstimatedMinutes)
}
fmt.Printf("Created: %s\n", issue.CreatedAt.Format("2006-01-02 15:04"))
if issue.CreatedBy != "" {
fmt.Printf("Created by: %s\n", issue.CreatedBy)
}
fmt.Printf("Updated: %s\n", issue.UpdatedAt.Format("2006-01-02 15:04"))
if issue.DueAt != nil {
fmt.Printf("Due: %s\n", issue.DueAt.Format("2006-01-02 15:04"))
}
if issue.DeferUntil != nil {
fmt.Printf("Deferred until: %s\n", issue.DeferUntil.Format("2006-01-02 15:04"))
}
if issue.ExternalRef != nil && *issue.ExternalRef != "" {
fmt.Printf("External Ref: %s\n", *issue.ExternalRef)
}
// Metadata: Owner · Type | Created · Updated
fmt.Println(formatIssueMetadata(issue))
// Show compaction status footer
// Compaction info (if applicable)
if issue.CompactionLevel > 0 {
tierEmoji := "🗜️"
if issue.CompactionLevel == 2 {
tierEmoji = "📦"
}
tierName := fmt.Sprintf("Tier %d", issue.CompactionLevel)
fmt.Println()
if issue.OriginalSize > 0 {
currentSize := len(issue.Description) + len(issue.Design) + len(issue.Notes) + len(issue.AcceptanceCriteria)
saved := issue.OriginalSize - currentSize
if saved > 0 {
reduction := float64(saved) / float64(issue.OriginalSize) * 100
fmt.Printf("📊 Original: %d bytes | Compressed: %d bytes (%.0f%% reduction)\n",
fmt.Printf("📊 %d → %d bytes (%.0f%% reduction)\n",
issue.OriginalSize, currentSize, reduction)
}
}
compactedDate := ""
if issue.CompactedAt != nil {
compactedDate = issue.CompactedAt.Format("2006-01-02")
}
fmt.Printf("%s Compacted: %s (%s)\n", tierEmoji, compactedDate, tierName)
}
// Content sections
if issue.Description != "" {
fmt.Printf("\nDescription:\n%s\n", issue.Description)
fmt.Printf("\n%s\n%s\n", ui.RenderBold("DESCRIPTION"), ui.RenderMarkdown(issue.Description))
}
if issue.Design != "" {
fmt.Printf("\nDesign:\n%s\n", issue.Design)
fmt.Printf("\n%s\n%s\n", ui.RenderBold("DESIGN"), ui.RenderMarkdown(issue.Design))
}
if issue.Notes != "" {
fmt.Printf("\nNotes:\n%s\n", issue.Notes)
fmt.Printf("\n%s\n%s\n", ui.RenderBold("NOTES"), ui.RenderMarkdown(issue.Notes))
}
if issue.AcceptanceCriteria != "" {
fmt.Printf("\nAcceptance Criteria:\n%s\n", issue.AcceptanceCriteria)
fmt.Printf("\n%s\n%s\n", ui.RenderBold("ACCEPTANCE CRITERIA"), ui.RenderMarkdown(issue.AcceptanceCriteria))
}
// Show labels
labels, _ := issueStore.GetLabels(ctx, issue.ID)
if len(labels) > 0 {
fmt.Printf("\nLabels: %v\n", labels)
fmt.Printf("\n%s %s\n", ui.RenderBold("LABELS:"), strings.Join(labels, ", "))
}
// Show dependencies
// Show dependencies with semantic colors
deps, _ := issueStore.GetDependencies(ctx, issue.ID)
if len(deps) > 0 {
fmt.Printf("\nDepends on (%d):\n", len(deps))
fmt.Printf("\n%s\n", ui.RenderBold("DEPENDS ON"))
for _, dep := range deps {
fmt.Printf(" → %s: %s [P%d]\n", dep.ID, dep.Title, dep.Priority)
fmt.Println(formatSimpleDependencyLine("→", dep))
}
}
@@ -541,27 +450,27 @@ var showCmd = &cobra.Command{
}
if len(children) > 0 {
fmt.Printf("\nChildren (%d):\n", len(children))
fmt.Printf("\n%s\n", ui.RenderBold("CHILDREN"))
for _, dep := range children {
fmt.Printf(" ↳ %s: %s [P%d - %s]\n", dep.ID, dep.Title, dep.Priority, dep.Status)
fmt.Println(formatDependencyLine("↳", dep))
}
}
if len(blocks) > 0 {
fmt.Printf("\nBlocks (%d):\n", len(blocks))
fmt.Printf("\n%s\n", ui.RenderBold("BLOCKS"))
for _, dep := range blocks {
fmt.Printf(" ← %s: %s [P%d - %s]\n", dep.ID, dep.Title, dep.Priority, dep.Status)
fmt.Println(formatDependencyLine("←", dep))
}
}
if len(related) > 0 {
fmt.Printf("\nRelated (%d):\n", len(related))
fmt.Printf("\n%s\n", ui.RenderBold("RELATED"))
for _, dep := range related {
fmt.Printf(" ↔ %s: %s [P%d - %s]\n", dep.ID, dep.Title, dep.Priority, dep.Status)
fmt.Println(formatDependencyLine("↔", dep))
}
}
if len(discovered) > 0 {
fmt.Printf("\nDiscovered (%d):\n", len(discovered))
fmt.Printf("\n%s\n", ui.RenderBold("DISCOVERED"))
for _, dep := range discovered {
fmt.Printf(" ◊ %s: %s [P%d - %s]\n", dep.ID, dep.Title, dep.Priority, dep.Status)
fmt.Println(formatDependencyLine("◊", dep))
}
}
}
@@ -569,9 +478,9 @@ var showCmd = &cobra.Command{
// Fallback for non-SQLite storage
dependents, _ := issueStore.GetDependents(ctx, issue.ID)
if len(dependents) > 0 {
fmt.Printf("\nBlocks (%d):\n", len(dependents))
fmt.Printf("\n%s\n", ui.RenderBold("BLOCKS"))
for _, dep := range dependents {
fmt.Printf(" ← %s: %s [P%d - %s]\n", dep.ID, dep.Title, dep.Priority, dep.Status)
fmt.Println(formatSimpleDependencyLine("←", dep))
}
}
}
@@ -579,9 +488,13 @@ var showCmd = &cobra.Command{
// Show comments
comments, _ := issueStore.GetIssueComments(ctx, issue.ID)
if len(comments) > 0 {
fmt.Printf("\nComments (%d):\n", len(comments))
fmt.Printf("\n%s\n", ui.RenderBold("COMMENTS"))
for _, comment := range comments {
fmt.Printf(" [%s at %s]\n %s\n\n", comment.Author, comment.CreatedAt.Format("2006-01-02 15:04"), comment.Text)
fmt.Printf(" %s %s\n", ui.RenderMuted(comment.CreatedAt.Format("2006-01-02")), comment.Author)
commentLines := strings.Split(comment.Text, "\n")
for _, line := range commentLines {
fmt.Printf(" %s\n", line)
}
}
}
@@ -605,10 +518,178 @@ var showCmd = &cobra.Command{
// formatShortIssue returns a compact one-line representation of an issue
// Format: <id> [<status>] P<priority> <type>: <title>
// Format: STATUS_ICON ID PRIORITY [Type] Title
func formatShortIssue(issue *types.Issue) string {
return fmt.Sprintf("%s [%s] P%d %s: %s",
issue.ID, issue.Status, issue.Priority, issue.IssueType, issue.Title)
statusIcon := ui.RenderStatusIcon(string(issue.Status))
priorityTag := ui.RenderPriority(issue.Priority)
// Type badge only for notable types
typeBadge := ""
switch issue.IssueType {
case "epic":
typeBadge = ui.TypeEpicStyle.Render("[epic]") + " "
case "bug":
typeBadge = ui.TypeBugStyle.Render("[bug]") + " "
}
// Closed issues: entire line is muted
if issue.Status == types.StatusClosed {
return fmt.Sprintf("%s %s %s %s%s",
statusIcon,
ui.RenderMuted(issue.ID),
ui.RenderMuted(fmt.Sprintf("● P%d", issue.Priority)),
ui.RenderMuted(string(issue.IssueType)),
ui.RenderMuted(" "+issue.Title))
}
return fmt.Sprintf("%s %s %s %s%s", statusIcon, issue.ID, priorityTag, typeBadge, issue.Title)
}
// formatIssueHeader returns the Tufte-aligned header line
// Format: ID · Title [Priority · STATUS]
// All elements in bd show get semantic colors since focus is on one issue
func formatIssueHeader(issue *types.Issue) string {
// Get status icon and style
statusIcon := ui.RenderStatusIcon(string(issue.Status))
statusStyle := ui.GetStatusStyle(string(issue.Status))
statusStr := statusStyle.Render(strings.ToUpper(string(issue.Status)))
// Priority with semantic color (includes ● icon)
priorityTag := ui.RenderPriority(issue.Priority)
// Type badge for notable types
typeBadge := ""
switch issue.IssueType {
case "epic":
typeBadge = " " + ui.TypeEpicStyle.Render("[EPIC]")
case "bug":
typeBadge = " " + ui.TypeBugStyle.Render("[BUG]")
}
// Compaction indicator
tierEmoji := ""
switch issue.CompactionLevel {
case 1:
tierEmoji = " 🗜️"
case 2:
tierEmoji = " 📦"
}
// Build header: STATUS_ICON ID · Title [Priority · STATUS]
idStyled := ui.RenderAccent(issue.ID)
return fmt.Sprintf("%s %s%s · %s%s [%s · %s]",
statusIcon, idStyled, typeBadge, issue.Title, tierEmoji, priorityTag, statusStr)
}
// formatIssueMetadata returns the metadata line(s) with grouped info
// Format: Owner: user · Type: task
//
// Created: 2026-01-06 · Updated: 2026-01-08
func formatIssueMetadata(issue *types.Issue) string {
var lines []string
// Line 1: Owner/Assignee · Type
metaParts := []string{}
if issue.CreatedBy != "" {
metaParts = append(metaParts, fmt.Sprintf("Owner: %s", issue.CreatedBy))
}
if issue.Assignee != "" {
metaParts = append(metaParts, fmt.Sprintf("Assignee: %s", issue.Assignee))
}
// Type with semantic color
typeStr := string(issue.IssueType)
switch issue.IssueType {
case "epic":
typeStr = ui.TypeEpicStyle.Render("epic")
case "bug":
typeStr = ui.TypeBugStyle.Render("bug")
}
metaParts = append(metaParts, fmt.Sprintf("Type: %s", typeStr))
if len(metaParts) > 0 {
lines = append(lines, strings.Join(metaParts, " · "))
}
// Line 2: Created · Updated · Due/Defer
timeParts := []string{}
timeParts = append(timeParts, fmt.Sprintf("Created: %s", issue.CreatedAt.Format("2006-01-02")))
timeParts = append(timeParts, fmt.Sprintf("Updated: %s", issue.UpdatedAt.Format("2006-01-02")))
if issue.DueAt != nil {
timeParts = append(timeParts, fmt.Sprintf("Due: %s", issue.DueAt.Format("2006-01-02")))
}
if issue.DeferUntil != nil {
timeParts = append(timeParts, fmt.Sprintf("Deferred: %s", issue.DeferUntil.Format("2006-01-02")))
}
if len(timeParts) > 0 {
lines = append(lines, strings.Join(timeParts, " · "))
}
// Line 3: Close reason (if closed)
if issue.Status == types.StatusClosed && issue.CloseReason != "" {
lines = append(lines, ui.RenderMuted(fmt.Sprintf("Close reason: %s", issue.CloseReason)))
}
// Line 4: External ref (if exists)
if issue.ExternalRef != nil && *issue.ExternalRef != "" {
lines = append(lines, fmt.Sprintf("External: %s", *issue.ExternalRef))
}
return strings.Join(lines, "\n")
}
// formatDependencyLine formats a single dependency with semantic colors
// Closed items get entire row muted - the work is done, no need for attention
func formatDependencyLine(prefix string, dep *types.IssueWithDependencyMetadata) string {
// Status icon (always rendered with semantic color)
statusIcon := ui.GetStatusIcon(string(dep.Status))
// Closed items: mute entire row since the work is complete
if dep.Status == types.StatusClosed {
return fmt.Sprintf(" %s %s %s: %s %s",
prefix, statusIcon,
ui.RenderMuted(dep.ID),
ui.RenderMuted(dep.Title),
ui.RenderMuted(fmt.Sprintf("● P%d", dep.Priority)))
}
// Active items: ID with status color, priority with semantic color
style := ui.GetStatusStyle(string(dep.Status))
idStr := style.Render(dep.ID)
priorityTag := ui.RenderPriority(dep.Priority)
// Type indicator for epics/bugs
typeStr := ""
if dep.IssueType == "epic" {
typeStr = ui.TypeEpicStyle.Render("(EPIC)") + " "
} else if dep.IssueType == "bug" {
typeStr = ui.TypeBugStyle.Render("(BUG)") + " "
}
return fmt.Sprintf(" %s %s %s: %s%s %s", prefix, statusIcon, idStr, typeStr, dep.Title, priorityTag)
}
// formatSimpleDependencyLine formats a dependency without metadata (fallback)
// Closed items get entire row muted - the work is done, no need for attention
func formatSimpleDependencyLine(prefix string, dep *types.Issue) string {
statusIcon := ui.GetStatusIcon(string(dep.Status))
// Closed items: mute entire row since the work is complete
if dep.Status == types.StatusClosed {
return fmt.Sprintf(" %s %s %s: %s %s",
prefix, statusIcon,
ui.RenderMuted(dep.ID),
ui.RenderMuted(dep.Title),
ui.RenderMuted(fmt.Sprintf("● P%d", dep.Priority)))
}
// Active items: use semantic colors
style := ui.GetStatusStyle(string(dep.Status))
idStr := style.Render(dep.ID)
priorityTag := ui.RenderPriority(dep.Priority)
return fmt.Sprintf(" %s %s %s: %s %s", prefix, statusIcon, idStr, dep.Title, priorityTag)
}
// showIssueRefs displays issues that reference the given issue(s), grouped by relationship type
@@ -747,13 +828,23 @@ func showIssueRefs(ctx context.Context, args []string, resolvedIDs []string, rou
}
// displayRefGroup displays a group of references with a given type
// Closed items get entire row muted - the work is done, no need for attention
func displayRefGroup(depType types.DependencyType, refs []*types.IssueWithDependencyMetadata) {
// Get emoji for type
emoji := getRefTypeEmoji(depType)
fmt.Printf("\n %s %s (%d):\n", emoji, depType, len(refs))
for _, ref := range refs {
// Color ID based on status
// Closed items: mute entire row since the work is complete
if ref.Status == types.StatusClosed {
fmt.Printf(" %s: %s %s\n",
ui.RenderMuted(ref.ID),
ui.RenderMuted(ref.Title),
ui.RenderMuted(fmt.Sprintf("[P%d - %s]", ref.Priority, ref.Status)))
continue
}
// Active items: color ID based on status
var idStr string
switch ref.Status {
case types.StatusOpen:
@@ -762,8 +853,6 @@ func displayRefGroup(depType types.DependencyType, refs []*types.IssueWithDepend
idStr = ui.StatusInProgressStyle.Render(ref.ID)
case types.StatusBlocked:
idStr = ui.StatusBlockedStyle.Render(ref.ID)
case types.StatusClosed:
idStr = ui.StatusClosedStyle.Render(ref.ID)
default:
idStr = ref.ID
}