diff --git a/cmd/tui.go b/cmd/tui.go index 30a0f609..b1608530 100644 --- a/cmd/tui.go +++ b/cmd/tui.go @@ -5,15 +5,22 @@ import ( "github.com/hmans/beans/internal/tui" ) +var tuiExcludeArchived bool + var tuiCmd = &cobra.Command{ Use: "tui", Short: "Open the interactive TUI", Long: `Opens an interactive terminal user interface for browsing and managing beans.`, RunE: func(cmd *cobra.Command, args []string) error { + // Override config if flag was explicitly set + if cmd.Flags().Changed("exclude-archived") { + cfg.TUI.ExcludeArchived = tuiExcludeArchived + } return tui.Run(core, cfg) }, } func init() { + tuiCmd.Flags().BoolVarP(&tuiExcludeArchived, "exclude-archived", "e", false, "Exclude beans with archive statuses (completed, scrapped)") rootCmd.AddCommand(tuiCmd) } diff --git a/internal/config/config.go b/internal/config/config.go index 1ae7fdf1..e10da376 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -73,12 +73,18 @@ type PriorityConfig struct { // Note: Statuses are no longer stored in config - they are hardcoded like types. type Config struct { Beans BeansConfig `yaml:"beans"` + TUI TUIConfig `yaml:"tui,omitempty"` // configDir is the directory containing the config file (not serialized) // Used to resolve relative paths configDir string `yaml:"-"` } +// TUIConfig defines settings for the TUI. +type TUIConfig struct { + ExcludeArchived bool `yaml:"exclude_archived,omitempty"` +} + // BeansConfig defines settings for bean creation. type BeansConfig struct { // Path is the path to the beans directory (relative to config file location) diff --git a/internal/graph/filters.go b/internal/graph/filters.go index a98dcc56..700b9364 100644 --- a/internal/graph/filters.go +++ b/internal/graph/filters.go @@ -87,6 +87,11 @@ func ApplyFilter(beans []*bean.Bean, filter *model.BeanFilter, core *beancore.Co result = filterByNoBlockedBy(result) } + // Archive filter + if filter.ExcludeArchived != nil && *filter.ExcludeArchived { + result = filterByNotArchived(result, core) + } + return result } @@ -331,3 +336,14 @@ func filterByNoBlockedBy(beans []*bean.Bean) []*bean.Bean { } return result } + +// filterByNotArchived filters beans that are not in the archive directory. +func filterByNotArchived(beans []*bean.Bean, core *beancore.Core) []*bean.Bean { + var result []*bean.Bean + for _, b := range beans { + if !core.IsArchived(b.ID) { + result = append(result, b) + } + } + return result +} diff --git a/internal/graph/generated.go b/internal/graph/generated.go index 5b1ea971..40fed35b 100644 --- a/internal/graph/generated.go +++ b/internal/graph/generated.go @@ -3842,7 +3842,7 @@ func (ec *executionContext) unmarshalInputBeanFilter(ctx context.Context, obj an asMap[k] = v } - fieldsInOrder := [...]string{"search", "status", "excludeStatus", "type", "excludeType", "priority", "excludePriority", "tags", "excludeTags", "hasParent", "parentId", "hasBlocking", "blockingId", "isBlocked", "hasBlockedBy", "blockedById", "noParent", "noBlocking", "noBlockedBy"} + fieldsInOrder := [...]string{"search", "status", "excludeStatus", "type", "excludeType", "priority", "excludePriority", "tags", "excludeTags", "hasParent", "parentId", "hasBlocking", "blockingId", "isBlocked", "hasBlockedBy", "blockedById", "noParent", "noBlocking", "noBlockedBy", "excludeArchived"} for _, k := range fieldsInOrder { v, ok := asMap[k] if !ok { @@ -3982,6 +3982,13 @@ func (ec *executionContext) unmarshalInputBeanFilter(ctx context.Context, obj an return it, err } it.NoBlockedBy = data + case "excludeArchived": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("excludeArchived")) + data, err := ec.unmarshalOBoolean2ᚖbool(ctx, v) + if err != nil { + return it, err + } + it.ExcludeArchived = data } } diff --git a/internal/graph/model/models_gen.go b/internal/graph/model/models_gen.go index 8e50a440..495aed85 100644 --- a/internal/graph/model/models_gen.go +++ b/internal/graph/model/models_gen.go @@ -54,6 +54,8 @@ type BeanFilter struct { NoBlocking *bool `json:"noBlocking,omitempty"` // Exclude beans that have explicit blocked-by entries NoBlockedBy *bool `json:"noBlockedBy,omitempty"` + // Exclude beans that are in the archive directory + ExcludeArchived *bool `json:"excludeArchived,omitempty"` } // Structured body modifications applied atomically. diff --git a/internal/graph/schema.graphqls b/internal/graph/schema.graphqls index 165ce67a..151ee388 100644 --- a/internal/graph/schema.graphqls +++ b/internal/graph/schema.graphqls @@ -252,4 +252,6 @@ input BeanFilter { noBlocking: Boolean "Exclude beans that have explicit blocked-by entries" noBlockedBy: Boolean + "Exclude beans that are in the archive directory" + excludeArchived: Boolean } diff --git a/internal/tui/list.go b/internal/tui/list.go index aca94150..0ae6a66d 100644 --- a/internal/tui/list.go +++ b/internal/tui/list.go @@ -167,10 +167,16 @@ func (m listModel) Init() tea.Cmd { } func (m listModel) loadBeans() tea.Msg { - // Build filter if tag filter is set + // Build filter based on active filters var filter *model.BeanFilter - if m.tagFilter != "" { - filter = &model.BeanFilter{Tags: []string{m.tagFilter}} + if m.tagFilter != "" || m.config.TUI.ExcludeArchived { + filter = &model.BeanFilter{} + if m.tagFilter != "" { + filter.Tags = []string{m.tagFilter} + } + if m.config.TUI.ExcludeArchived { + filter.ExcludeArchived = &m.config.TUI.ExcludeArchived + } } // Query filtered beans