5b660c3c1c
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
145 lines
3.2 KiB
Go
145 lines
3.2 KiB
Go
package engine
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
// WorkingSet is a mapping from small integers to task UUIDs for filtered tasks.
|
|
// The small integers are meant to be stable, easily-typed identifiers for users to interact with
|
|
// important tasks.
|
|
type WorkingSet struct {
|
|
byUUID map[uuid.UUID]*Task
|
|
byID map[int]uuid.UUID // display_id -> UUID
|
|
}
|
|
|
|
// BuildWorkingSet creates a working set from a list of tasks and persists to DB
|
|
// Tasks should be pre-sorted in the desired display order
|
|
// IDs are assigned sequentially (1, 2, 3, ...) based on task order in the slice
|
|
func BuildWorkingSet(tasks []*Task) (*WorkingSet, error) {
|
|
ws := &WorkingSet{
|
|
byUUID: make(map[uuid.UUID]*Task),
|
|
byID: make(map[int]uuid.UUID),
|
|
}
|
|
|
|
db := GetDB()
|
|
if db == nil {
|
|
return nil, fmt.Errorf("database not initialized")
|
|
}
|
|
|
|
tx, err := db.Begin()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer tx.Rollback()
|
|
|
|
// Clear existing working set
|
|
if _, err := tx.Exec("DELETE FROM working_set"); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Insert new working set
|
|
stmt, err := tx.Prepare("INSERT INTO working_set (display_id, task_uuid) VALUES (?, ?)")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer stmt.Close()
|
|
|
|
for i, task := range tasks {
|
|
displayID := i + 1 // 1-based
|
|
ws.byUUID[task.UUID] = task
|
|
ws.byID[displayID] = task.UUID
|
|
|
|
if _, err := stmt.Exec(displayID, task.UUID.String()); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
if err := tx.Commit(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return ws, nil
|
|
}
|
|
|
|
// LoadWorkingSet loads the current working set from DB
|
|
func LoadWorkingSet() (*WorkingSet, error) {
|
|
db := GetDB()
|
|
if db == nil {
|
|
return nil, fmt.Errorf("database not initialized")
|
|
}
|
|
|
|
rows, err := db.Query(`
|
|
SELECT ws.display_id, ws.task_uuid
|
|
FROM working_set ws
|
|
ORDER BY ws.display_id
|
|
`)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
|
|
ws := &WorkingSet{
|
|
byUUID: make(map[uuid.UUID]*Task),
|
|
byID: make(map[int]uuid.UUID),
|
|
}
|
|
|
|
for rows.Next() {
|
|
var displayID int
|
|
var taskUUIDStr string
|
|
|
|
if err := rows.Scan(&displayID, &taskUUIDStr); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
taskUUID, err := uuid.Parse(taskUUIDStr)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to parse UUID: %w", err)
|
|
}
|
|
|
|
// Load the actual task
|
|
task, err := GetTask(taskUUID)
|
|
if err != nil {
|
|
// Task might have been deleted, skip it
|
|
continue
|
|
}
|
|
|
|
ws.byUUID[task.UUID] = task
|
|
ws.byID[displayID] = task.UUID
|
|
}
|
|
|
|
return ws, nil
|
|
}
|
|
|
|
// GetTaskByDisplayID resolves display ID to task
|
|
func (ws *WorkingSet) GetTaskByDisplayID(id int) (*Task, error) {
|
|
taskUUID, exists := ws.byID[id]
|
|
if !exists {
|
|
return nil, fmt.Errorf("invalid task ID: %d (not in current working set)", id)
|
|
}
|
|
task, exists := ws.byUUID[taskUUID]
|
|
if !exists {
|
|
return nil, fmt.Errorf("task UUID not found in working set")
|
|
}
|
|
return task, nil
|
|
}
|
|
|
|
// GetTasks returns all tasks in the working set
|
|
func (ws *WorkingSet) GetTasks() []*Task {
|
|
tasks := make([]*Task, 0, len(ws.byID))
|
|
for i := 1; i <= len(ws.byID); i++ {
|
|
if taskUUID, ok := ws.byID[i]; ok {
|
|
if task, ok := ws.byUUID[taskUUID]; ok {
|
|
tasks = append(tasks, task)
|
|
}
|
|
}
|
|
}
|
|
return tasks
|
|
}
|
|
|
|
// Size returns number of tasks in working set
|
|
func (ws *WorkingSet) Size() int {
|
|
return len(ws.byID)
|
|
}
|