Fix working set IDs to match display order

BREAKING CHANGE: BuildWorkingSet() now accepts []*Task instead of *Filter

Problem:
- Working set IDs were assigned based on database query order (unsorted)
- Reports displayed tasks in sorted order (by urgency)
- Result: IDs didn't match displayed task positions (ID 1 wasn't first task)

Solution:
- Changed BuildWorkingSet() to accept pre-sorted task slice
- Reports now pass sorted tasks to BuildWorkingSet()
- IDs are assigned sequentially to match display order (1, 2, 3...)

Behavior:
- Reports rebuild working set on every execution with fresh IDs
- Task operations (done, modify, info) use saved working set IDs
- After completing a task, re-running report renumbers remaining tasks

Example:
  Before:  opal list shows ID 1 = low urgency task (wrong)
  After:   opal list shows ID 1 = highest urgency task (correct)

Tested scenarios:
✓ List report: IDs 1-N match urgency order
✓ Next report: IDs 1-5 match top urgent tasks
✓ Task completion: IDs renumber correctly after removal
✓ Multiple operations: Use saved working set (correct behavior)
✓ Different reports: Each builds own sequential IDs
This commit is contained in:
2026-01-06 14:54:01 +01:00
parent 8f6db4672a
commit 5b660c3c1c
4 changed files with 36 additions and 18 deletions
+6 -2
View File
@@ -50,8 +50,12 @@ func modifyTasks(filterArgs, modifierArgs []string) error {
// Load working set for ID resolution // Load working set for ID resolution
ws, err := engine.LoadWorkingSet() ws, err := engine.LoadWorkingSet()
if err != nil { if err != nil {
// If no working set exists yet, build one // If no working set exists yet, build one from filtered tasks
ws, err = engine.BuildWorkingSet(filter) tasks, err := engine.GetTasks(filter)
if err != nil {
return fmt.Errorf("failed to get tasks: %w", err)
}
ws, err = engine.BuildWorkingSet(tasks)
if err != nil { if err != nil {
return fmt.Errorf("failed to build working set: %w", err) return fmt.Errorf("failed to build working set: %w", err)
} }
+3 -3
View File
@@ -47,14 +47,14 @@ func runReport(reportName string, filters []string) error {
return err return err
} }
// Execute the report // Execute the report (returns sorted tasks)
tasks, err := report.Execute(filters) tasks, err := report.Execute(filters)
if err != nil { if err != nil {
return fmt.Errorf("failed to execute report: %w", err) return fmt.Errorf("failed to execute report: %w", err)
} }
// Build working set for display IDs // Build working set from sorted tasks - IDs will match display order
ws, err := engine.BuildWorkingSet(report.BaseFilter) ws, err := engine.BuildWorkingSet(tasks)
if err != nil { if err != nil {
return fmt.Errorf("failed to build working set: %w", err) return fmt.Errorf("failed to build working set: %w", err)
} }
+4 -7
View File
@@ -14,13 +14,10 @@ type WorkingSet struct {
byID map[int]uuid.UUID // display_id -> UUID byID map[int]uuid.UUID // display_id -> UUID
} }
// BuildWorkingSet creates a working set from filter results and persists to DB // BuildWorkingSet creates a working set from a list of tasks and persists to DB
func BuildWorkingSet(filter *Filter) (*WorkingSet, error) { // Tasks should be pre-sorted in the desired display order
tasks, err := GetTasks(filter) // IDs are assigned sequentially (1, 2, 3, ...) based on task order in the slice
if err != nil { func BuildWorkingSet(tasks []*Task) (*WorkingSet, error) {
return nil, err
}
ws := &WorkingSet{ ws := &WorkingSet{
byUUID: make(map[uuid.UUID]*Task), byUUID: make(map[uuid.UUID]*Task),
byID: make(map[int]uuid.UUID), byID: make(map[int]uuid.UUID),
+23 -6
View File
@@ -10,8 +10,13 @@ func TestBuildWorkingSet(t *testing.T) {
task2, _ := CreateTask("Task 2") task2, _ := CreateTask("Task 2")
task3, _ := CreateTask("Task 3") task3, _ := CreateTask("Task 3")
// Build working set with default filter // Get tasks and build working set
ws, err := BuildWorkingSet(DefaultFilter()) tasks, err := GetTasks(DefaultFilter())
if err != nil {
t.Fatalf("Failed to get tasks: %v", err)
}
ws, err := BuildWorkingSet(tasks)
if err != nil { if err != nil {
t.Fatalf("Failed to build working set: %v", err) t.Fatalf("Failed to build working set: %v", err)
} }
@@ -52,7 +57,12 @@ func TestLoadWorkingSet(t *testing.T) {
CreateTask("Load test 1") CreateTask("Load test 1")
CreateTask("Load test 2") CreateTask("Load test 2")
ws1, err := BuildWorkingSet(DefaultFilter()) tasks, err := GetTasks(DefaultFilter())
if err != nil {
t.Fatalf("Failed to get tasks: %v", err)
}
ws1, err := BuildWorkingSet(tasks)
if err != nil { if err != nil {
t.Fatalf("Failed to build working set: %v", err) t.Fatalf("Failed to build working set: %v", err)
} }
@@ -82,7 +92,12 @@ func TestWorkingSetWithFilter(t *testing.T) {
// Build working set with high priority filter // Build working set with high priority filter
filter, _ := ParseFilter([]string{"priority:H"}) filter, _ := ParseFilter([]string{"priority:H"})
ws, err := BuildWorkingSet(filter) tasks, err := GetTasks(filter)
if err != nil {
t.Fatalf("Failed to get tasks: %v", err)
}
ws, err := BuildWorkingSet(tasks)
if err != nil { if err != nil {
t.Fatalf("Failed to build filtered working set: %v", err) t.Fatalf("Failed to build filtered working set: %v", err)
} }
@@ -105,7 +120,8 @@ func TestGetTaskByDisplayID(t *testing.T) {
CreateTask("Display ID test 2") CreateTask("Display ID test 2")
CreateTask("Display ID test 3") CreateTask("Display ID test 3")
ws, _ := BuildWorkingSet(DefaultFilter()) tasks, _ := GetTasks(DefaultFilter())
ws, _ := BuildWorkingSet(tasks)
// Test valid display IDs // Test valid display IDs
for i := 1; i <= ws.Size(); i++ { for i := 1; i <= ws.Size(); i++ {
@@ -130,7 +146,8 @@ func TestWorkingSetGetTasks(t *testing.T) {
CreateTask("GetTasks test 1") CreateTask("GetTasks test 1")
CreateTask("GetTasks test 2") CreateTask("GetTasks test 2")
ws, _ := BuildWorkingSet(DefaultFilter()) taskList, _ := GetTasks(DefaultFilter())
ws, _ := BuildWorkingSet(taskList)
tasks := ws.GetTasks() tasks := ws.GetTasks()
if len(tasks) != ws.Size() { if len(tasks) != ws.Size() {