Skip to content

Commit 43990e6

Browse files
committed
feat(archive): show bean list before confirmation prompt
- Display formatted list of beans to be archived - Uses existing RenderBeanRow for consistent styling - Shows ID, type, status, tags, and title for each bean
1 parent 1fdd957 commit 43990e6

File tree

2 files changed

+76
-16
lines changed

2 files changed

+76
-16
lines changed
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
title: Show bean list in archive confirmation
3+
status: completed
4+
type: feature
5+
priority: normal
6+
created_at: 2025-12-12T22:43:45Z
7+
updated_at: 2025-12-12T22:45:19Z
8+
---
9+
10+
The `beans archive` command should show a list of beans that are going to be archived above the confirmation prompt. Reuse existing bean listing code if possible.

cmd/archive.go

Lines changed: 66 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@ import (
44
"fmt"
55

66
"github.com/charmbracelet/huh"
7-
"github.com/spf13/cobra"
7+
"github.com/hmans/beans/internal/bean"
88
"github.com/hmans/beans/internal/beancore"
99
"github.com/hmans/beans/internal/output"
10+
"github.com/hmans/beans/internal/ui"
11+
"github.com/spf13/cobra"
1012
)
1113

1214
var (
@@ -22,14 +24,14 @@ var archiveCmd = &cobra.Command{
2224
If other beans reference beans being archived (as parent or via blocking), you will be
2325
warned and those references will be removed. Use -f to skip all warnings.`,
2426
RunE: func(cmd *cobra.Command, args []string) error {
25-
beans := core.All()
27+
allBeans := core.All()
2628

2729
// Find beans with any archive status
28-
var archiveBeans []string
30+
var archiveBeans []*bean.Bean
2931
archiveSet := make(map[string]bool)
30-
for _, b := range beans {
32+
for _, b := range allBeans {
3133
if cfg.IsArchiveStatus(b.Status) {
32-
archiveBeans = append(archiveBeans, b.ID)
34+
archiveBeans = append(archiveBeans, b)
3335
archiveSet[b.ID] = true
3436
}
3537
}
@@ -42,10 +44,13 @@ warned and those references will be removed. Use -f to skip all warnings.`,
4244
return nil
4345
}
4446

47+
// Sort beans for consistent display
48+
bean.SortByStatusPriorityAndType(archiveBeans, cfg.StatusNames(), cfg.PriorityNames(), cfg.TypeNames())
49+
4550
// Find incoming links from non-archived beans to beans being archived
4651
var externalLinks []beancore.IncomingLink
47-
for _, id := range archiveBeans {
48-
links := core.FindIncomingLinks(id)
52+
for _, b := range archiveBeans {
53+
links := core.FindIncomingLinks(b.ID)
4954
for _, link := range links {
5055
// Only count links from beans NOT being archived
5156
if !archiveSet[link.FromBean.ID] {
@@ -57,6 +62,11 @@ warned and those references will be removed. Use -f to skip all warnings.`,
5762

5863
// JSON implies force (no prompts for machines)
5964
if !archiveForce && !archiveJSON {
65+
// Show list of beans to be archived
66+
fmt.Printf("Beans to archive (%d):\n\n", len(archiveBeans))
67+
printBeanList(archiveBeans)
68+
fmt.Println()
69+
6070
// Show warning if there are external links
6171
if hasExternalLinks {
6272
fmt.Printf("Warning: %d bean(s) link to beans being archived:\n", len(externalLinks))
@@ -93,27 +103,27 @@ warned and those references will be removed. Use -f to skip all warnings.`,
93103

94104
// Remove external links before deletion
95105
removedRefs := 0
96-
for _, id := range archiveBeans {
97-
removed, err := core.RemoveLinksTo(id)
106+
for _, b := range archiveBeans {
107+
removed, err := core.RemoveLinksTo(b.ID)
98108
if err != nil {
99109
if archiveJSON {
100-
return output.Error(output.ErrFileError, fmt.Sprintf("failed to remove references to %s: %s", id, err))
110+
return output.Error(output.ErrFileError, fmt.Sprintf("failed to remove references to %s: %s", b.ID, err))
101111
}
102-
return fmt.Errorf("failed to remove references to %s: %w", id, err)
112+
return fmt.Errorf("failed to remove references to %s: %w", b.ID, err)
103113
}
104114
removedRefs += removed
105115
}
106116

107117
// Delete all beans with archive status
108118
var deleted []string
109-
for _, id := range archiveBeans {
110-
if err := core.Delete(id); err != nil {
119+
for _, b := range archiveBeans {
120+
if err := core.Delete(b.ID); err != nil {
111121
if archiveJSON {
112-
return output.Error(output.ErrFileError, fmt.Sprintf("failed to delete bean %s: %s", id, err.Error()))
122+
return output.Error(output.ErrFileError, fmt.Sprintf("failed to delete bean %s: %s", b.ID, err.Error()))
113123
}
114-
return fmt.Errorf("failed to delete bean %s: %w", id, err)
124+
return fmt.Errorf("failed to delete bean %s: %w", b.ID, err)
115125
}
116-
deleted = append(deleted, id)
126+
deleted = append(deleted, b.ID)
117127
}
118128

119129
if archiveJSON {
@@ -128,6 +138,46 @@ warned and those references will be removed. Use -f to skip all warnings.`,
128138
},
129139
}
130140

141+
// printBeanList prints a formatted list of beans
142+
func printBeanList(beans []*bean.Bean) {
143+
// Calculate max ID width
144+
maxIDWidth := 0
145+
for _, b := range beans {
146+
if len(b.ID) > maxIDWidth {
147+
maxIDWidth = len(b.ID)
148+
}
149+
}
150+
maxIDWidth += 2 // padding
151+
152+
// Check if any beans have tags
153+
hasTags := false
154+
for _, b := range beans {
155+
if len(b.Tags) > 0 {
156+
hasTags = true
157+
break
158+
}
159+
}
160+
161+
// Print each bean
162+
for _, b := range beans {
163+
colors := cfg.GetBeanColors(b.Status, b.Type, b.Priority)
164+
row := ui.RenderBeanRow(b.ID, b.Status, b.Type, b.Title, ui.BeanRowConfig{
165+
StatusColor: colors.StatusColor,
166+
TypeColor: colors.TypeColor,
167+
PriorityColor: colors.PriorityColor,
168+
Priority: b.Priority,
169+
IsArchive: colors.IsArchive,
170+
MaxTitleWidth: 60,
171+
ShowCursor: false,
172+
IsSelected: false,
173+
Tags: b.Tags,
174+
ShowTags: hasTags,
175+
IDColWidth: maxIDWidth,
176+
})
177+
fmt.Println(row)
178+
}
179+
}
180+
131181
func init() {
132182
archiveCmd.Flags().BoolVarP(&archiveForce, "force", "f", false, "Skip confirmation and warnings")
133183
archiveCmd.Flags().BoolVar(&archiveJSON, "json", false, "Output as JSON (implies --force)")

0 commit comments

Comments
 (0)