Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 24 additions & 1 deletion internal/ui/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -420,7 +420,7 @@ func NewAppModel(database *db.DB, exec *executor.Executor, workingDir string) *A

// Setup filter input
filterInput := textinput.New()
filterInput.Placeholder = "Filter by project, type, or text..."
filterInput.Placeholder = "Filter text, #id, or [project..."
filterInput.CharLimit = 50

// Get available executors for form filtering and warnings
Expand Down Expand Up @@ -1459,9 +1459,32 @@ func (m *AppModel) applyFilter() {
// Returns -1 if the task doesn't match, otherwise returns a positive score.
// Higher scores indicate better matches.
// This uses the same matching logic as the command palette (Ctrl+P) for consistency.
//
// Special prefixes:
// - "[project" filters only on the project field (matches the [project] display format)
func scoreTaskForFilter(task *db.Task, query string) int {
bestScore := -1

// Check for project-only filter prefix "[" (matches display format [project])
if strings.HasPrefix(query, "[") {
projectQuery := strings.TrimPrefix(query, "[")
// Also strip trailing ] if present (user typed full "[project]")
projectQuery = strings.TrimSuffix(projectQuery, "]")
if projectQuery == "" {
// Just "[" typed, show all tasks with a project
if task.Project != "" {
return 100
}
return -1
}
// Only match against project field
projectScore := fuzzyScore(task.Project, projectQuery)
if projectScore > 0 {
return projectScore
}
return -1
}

// Check task ID (exact or prefix match gets high priority)
idStr := fmt.Sprintf("%d", task.ID)
if strings.HasPrefix(query, "#") {
Expand Down
43 changes: 43 additions & 0 deletions internal/ui/app_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,49 @@ func TestScoreTaskForFilter(t *testing.T) {
wantMin: -1,
wantMax: -1,
},
// Project-only filter tests (using [ prefix)
{
name: "project-only filter matches project",
task: &db.Task{ID: 1, Title: "Some task", Project: "workflow"},
query: "[workflow",
wantMin: 100,
wantMax: 500,
},
{
name: "project-only filter with fuzzy match",
task: &db.Task{ID: 1, Title: "Some task", Project: "offerlab"},
query: "[ol",
wantMin: 100,
wantMax: 500,
},
{
name: "project-only filter excludes title matches",
task: &db.Task{ID: 1, Title: "workflow improvements", Project: ""},
query: "[workflow",
wantMin: -1,
wantMax: -1,
},
{
name: "project-only filter with trailing bracket",
task: &db.Task{ID: 1, Title: "Task", Project: "influencekit"},
query: "[influencekit]",
wantMin: 100,
wantMax: 700, // exact match gets high score
},
{
name: "just bracket shows tasks with project",
task: &db.Task{ID: 1, Title: "Task", Project: "myproject"},
query: "[",
wantMin: 100,
wantMax: 100,
},
{
name: "just bracket hides tasks without project",
task: &db.Task{ID: 1, Title: "Task", Project: ""},
query: "[",
wantMin: -1,
wantMax: -1,
},
}

for _, tt := range tests {
Expand Down