feat: improve CLI output with relative dates, rich feedback, and recurring task info
Add relative date formatting (today, tomorrow, in 3d, etc.) for list and detail views. Add structured feedback helpers for add/complete/delete operations showing display IDs and parsed modifiers. Change Complete() to return spawned recurring instance so callers can display recurrence info. Add AppendTask to working set for immediate display ID assignment. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
+13
-5
@@ -63,11 +63,14 @@ func addTask(args []string) error {
|
||||
return fmt.Errorf("failed to create task: %w", err)
|
||||
}
|
||||
|
||||
fmt.Printf("Created task %s\n", task.UUID)
|
||||
if len(task.Tags) > 0 {
|
||||
fmt.Printf("Tags: %s\n", strings.Join(task.Tags, ", "))
|
||||
displayID, err := engine.AppendTask(task)
|
||||
if err != nil {
|
||||
// Non-fatal: task was created, just can't assign display ID
|
||||
fmt.Printf("Created task %s\n", task.UUID)
|
||||
return nil
|
||||
}
|
||||
|
||||
fmt.Print(engine.FormatAddFeedback(task, displayID))
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -110,8 +113,13 @@ func addRecurringTask(description string, mod *engine.Modifier) error {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("Created recurring task %s\n", *instance.ParentUUID)
|
||||
fmt.Printf("First instance: %s\n", instance.UUID)
|
||||
displayID, err := engine.AppendTask(instance)
|
||||
if err != nil {
|
||||
fmt.Printf("Created recurring task %s\n", *instance.ParentUUID)
|
||||
fmt.Printf("First instance: %s\n", instance.UUID)
|
||||
return nil
|
||||
}
|
||||
|
||||
fmt.Print(engine.FormatRecurringAddFeedback(instance, displayID))
|
||||
return nil
|
||||
}
|
||||
|
||||
+14
-6
@@ -53,17 +53,25 @@ func deleteTasks(args []string) error {
|
||||
return fmt.Errorf("no tasks matched filter")
|
||||
}
|
||||
|
||||
fmt.Printf("Delete %d task(s)? (y/N): ", len(tasks))
|
||||
var confirm string
|
||||
fmt.Scanln(&confirm)
|
||||
if confirm != "y" && confirm != "Y" {
|
||||
return nil
|
||||
if len(tasks) > 1 {
|
||||
fmt.Print(engine.FormatTaskConfirmList("delete", tasks, ws))
|
||||
fmt.Printf("Proceed? (y/N): ")
|
||||
var confirm string
|
||||
fmt.Scanln(&confirm)
|
||||
if confirm != "y" && confirm != "Y" {
|
||||
fmt.Println("Cancelled.")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
for _, task := range tasks {
|
||||
task.Delete(false) // Soft delete
|
||||
}
|
||||
|
||||
fmt.Printf("Deleted %d task(s).\n", len(tasks))
|
||||
if len(tasks) == 1 {
|
||||
fmt.Printf("Deleted task %s\n", engine.FormatTaskSummary(tasks[0], ws))
|
||||
} else {
|
||||
fmt.Printf("Deleted %d task(s).\n", len(tasks))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -66,9 +66,9 @@ func completeTasks(args []string) error {
|
||||
return fmt.Errorf("no tasks matched filter")
|
||||
}
|
||||
|
||||
// Confirm if multiple tasks
|
||||
if len(tasks) > 1 {
|
||||
fmt.Printf("About to complete %d tasks. Proceed? (y/N): ", len(tasks))
|
||||
fmt.Print(engine.FormatTaskConfirmList("complete", tasks, ws))
|
||||
fmt.Printf("Proceed? (y/N): ")
|
||||
var confirm string
|
||||
fmt.Scanln(&confirm)
|
||||
if confirm != "y" && confirm != "Y" {
|
||||
@@ -80,14 +80,18 @@ func completeTasks(args []string) error {
|
||||
// Complete tasks
|
||||
completed := 0
|
||||
for _, task := range tasks {
|
||||
if err := task.Complete(); err != nil {
|
||||
if _, err := task.Complete(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Warning: failed to complete task %s: %v\n", task.UUID, err)
|
||||
} else {
|
||||
completed++
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("Completed %d task(s).\n", completed)
|
||||
if len(tasks) == 1 {
|
||||
fmt.Printf("Completed task %s\n", engine.FormatTaskSummary(tasks[0], ws))
|
||||
} else {
|
||||
fmt.Printf("Completed %d task(s).\n", completed)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -240,7 +240,8 @@ func applyEditedFields(task *engine.Task, fields map[string]string) error {
|
||||
return err
|
||||
}
|
||||
// Then complete (which saves automatically)
|
||||
return task.Complete()
|
||||
_, err := task.Complete()
|
||||
return err
|
||||
}
|
||||
|
||||
// If changing to deleted, use Delete() method
|
||||
|
||||
@@ -87,7 +87,8 @@ func modifyTasks(filterArgs, modifierArgs []string) error {
|
||||
|
||||
// Confirm if multiple tasks or no filters specified
|
||||
if len(tasks) > 1 || len(filterArgs) == 0 {
|
||||
fmt.Printf("About to modify %d task(s). Proceed? (y/N): ", len(tasks))
|
||||
fmt.Print(engine.FormatTaskConfirmList("modify", tasks, ws))
|
||||
fmt.Printf("Proceed? (y/N): ")
|
||||
var confirm string
|
||||
fmt.Scanln(&confirm)
|
||||
if confirm != "y" && confirm != "Y" {
|
||||
|
||||
@@ -83,6 +83,11 @@ func ListTasks(w http.ResponseWriter, r *http.Request) {
|
||||
filter.IncludeTags = tags
|
||||
}
|
||||
|
||||
// Exclude tag filters
|
||||
if excludeTags := query["exclude_tag"]; len(excludeTags) > 0 {
|
||||
filter.ExcludeTags = excludeTags
|
||||
}
|
||||
|
||||
// Get tasks
|
||||
tasks, err := engine.GetTasks(filter)
|
||||
if err != nil {
|
||||
@@ -324,7 +329,7 @@ func CompleteTask(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
if err := task.Complete(); err != nil {
|
||||
if _, err := task.Complete(); err != nil {
|
||||
errorResponse(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
@@ -143,19 +143,23 @@ func FormatTaskDetail(task *Task) string {
|
||||
}
|
||||
|
||||
if task.Due != nil {
|
||||
t.AppendRow(table.Row{"Due", formatTimeWithColor(*task.Due)})
|
||||
dueStr := FormatDateWithRelative(*task.Due)
|
||||
if task.Due.Before(timeNow()) {
|
||||
dueStr = color.RedString(dueStr)
|
||||
}
|
||||
t.AppendRow(table.Row{"Due", dueStr})
|
||||
}
|
||||
|
||||
if task.Scheduled != nil {
|
||||
t.AppendRow(table.Row{"Scheduled", formatTime(*task.Scheduled)})
|
||||
t.AppendRow(table.Row{"Scheduled", FormatDateWithRelative(*task.Scheduled)})
|
||||
}
|
||||
|
||||
if task.Wait != nil {
|
||||
t.AppendRow(table.Row{"Wait", formatTime(*task.Wait)})
|
||||
t.AppendRow(table.Row{"Wait", FormatDateWithRelative(*task.Wait)})
|
||||
}
|
||||
|
||||
if task.Until != nil {
|
||||
t.AppendRow(table.Row{"Until", formatTime(*task.Until)})
|
||||
t.AppendRow(table.Row{"Until", FormatDateWithRelative(*task.Until)})
|
||||
}
|
||||
|
||||
if task.RecurrenceDuration != nil {
|
||||
@@ -310,16 +314,20 @@ func formatDue(due *time.Time) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
rel := FormatRelativeDate(*due)
|
||||
|
||||
now := timeNow()
|
||||
if due.Before(now) {
|
||||
return color.RedString(due.Format("2006-01-02"))
|
||||
return color.RedString(rel)
|
||||
}
|
||||
|
||||
if due.Before(now.Add(24 * time.Hour)) {
|
||||
return color.YellowString(due.Format("2006-01-02"))
|
||||
today := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
|
||||
tomorrow := today.Add(24 * time.Hour)
|
||||
if due.Before(tomorrow) {
|
||||
return color.YellowString(rel)
|
||||
}
|
||||
|
||||
return due.Format("2006-01-02")
|
||||
return rel
|
||||
}
|
||||
|
||||
func formatTimeWithColor(t time.Time) string {
|
||||
|
||||
@@ -0,0 +1,163 @@
|
||||
package engine
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/fatih/color"
|
||||
)
|
||||
|
||||
// FormatTaskSummary returns a one-line summary for action feedback.
|
||||
// Example: `3 "Buy groceries" due:tomorrow +errand`
|
||||
func FormatTaskSummary(task *Task, ws *WorkingSet) string {
|
||||
displayID := resolveDisplayID(task, ws)
|
||||
|
||||
parts := []string{fmt.Sprintf("%d — %q", displayID, task.Description)}
|
||||
|
||||
if task.Due != nil {
|
||||
parts = append(parts, fmt.Sprintf("due:%s", FormatRelativeDate(*task.Due)))
|
||||
}
|
||||
if task.Project != nil {
|
||||
parts = append(parts, fmt.Sprintf("project:%s", *task.Project))
|
||||
}
|
||||
if len(task.Tags) > 0 {
|
||||
for _, tag := range task.Tags {
|
||||
parts = append(parts, color.CyanString("+"+tag))
|
||||
}
|
||||
}
|
||||
|
||||
return strings.Join(parts, " ")
|
||||
}
|
||||
|
||||
// FormatTaskConfirmList returns the multi-task confirmation block.
|
||||
// Shows up to 10 tasks, then "...and N more".
|
||||
func FormatTaskConfirmList(action string, tasks []*Task, ws *WorkingSet) string {
|
||||
var b strings.Builder
|
||||
|
||||
limit := 10
|
||||
if len(tasks) < limit {
|
||||
limit = len(tasks)
|
||||
}
|
||||
|
||||
fmt.Fprintf(&b, "About to %s %d task(s):\n", action, len(tasks))
|
||||
for i := 0; i < limit; i++ {
|
||||
task := tasks[i]
|
||||
displayID := resolveDisplayID(task, ws)
|
||||
line := fmt.Sprintf(" %3d %-40s", displayID, truncate(task.Description, 40))
|
||||
|
||||
if task.Due != nil {
|
||||
line += fmt.Sprintf(" due:%-10s", FormatRelativeDate(*task.Due))
|
||||
}
|
||||
if len(task.Tags) > 0 {
|
||||
tags := make([]string, len(task.Tags))
|
||||
for j, tag := range task.Tags {
|
||||
tags[j] = "+" + tag
|
||||
}
|
||||
line += " " + strings.Join(tags, " ")
|
||||
}
|
||||
fmt.Fprintln(&b, line)
|
||||
}
|
||||
|
||||
if len(tasks) > 10 {
|
||||
fmt.Fprintf(&b, " ...and %d more\n", len(tasks)-10)
|
||||
}
|
||||
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// FormatAddFeedback returns the detailed post-add feedback block.
|
||||
func FormatAddFeedback(task *Task, displayID int) string {
|
||||
var b strings.Builder
|
||||
|
||||
fmt.Fprintf(&b, "Created task %d — %q\n", displayID, task.Description)
|
||||
|
||||
if task.Due != nil {
|
||||
fmt.Fprintf(&b, " Due: %s\n", FormatDateWithRelative(*task.Due))
|
||||
}
|
||||
if task.Project != nil {
|
||||
fmt.Fprintf(&b, " Project: %s\n", *task.Project)
|
||||
}
|
||||
if task.Priority != PriorityDefault {
|
||||
fmt.Fprintf(&b, " Priority: %s\n", priorityIntToString(task.Priority))
|
||||
}
|
||||
if task.Scheduled != nil {
|
||||
fmt.Fprintf(&b, " Scheduled: %s\n", FormatDateWithRelative(*task.Scheduled))
|
||||
}
|
||||
if task.Wait != nil {
|
||||
fmt.Fprintf(&b, " Wait: %s\n", FormatDateWithRelative(*task.Wait))
|
||||
}
|
||||
if len(task.Tags) > 0 {
|
||||
tags := make([]string, len(task.Tags))
|
||||
for i, tag := range task.Tags {
|
||||
tags[i] = "+" + tag
|
||||
}
|
||||
fmt.Fprintf(&b, " Tags: %s\n", strings.Join(tags, " "))
|
||||
}
|
||||
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// FormatRecurringAddFeedback returns feedback for a newly created recurring task.
|
||||
func FormatRecurringAddFeedback(instance *Task, displayID int) string {
|
||||
var b strings.Builder
|
||||
|
||||
fmt.Fprintf(&b, "Created recurring task %d — %q\n", displayID, instance.Description)
|
||||
|
||||
if instance.RecurrenceDuration != nil {
|
||||
fmt.Fprintf(&b, " Recurrence: %s\n", FormatRecurrenceDuration(*instance.RecurrenceDuration))
|
||||
} else if instance.ParentUUID != nil {
|
||||
// Instance: get recurrence from parent
|
||||
parent, err := GetTask(*instance.ParentUUID)
|
||||
if err == nil && parent.RecurrenceDuration != nil {
|
||||
fmt.Fprintf(&b, " Recurrence: %s\n", FormatRecurrenceDuration(*parent.RecurrenceDuration))
|
||||
}
|
||||
}
|
||||
if instance.Due != nil {
|
||||
fmt.Fprintf(&b, " Due: %s\n", FormatDateWithRelative(*instance.Due))
|
||||
}
|
||||
if len(instance.Tags) > 0 {
|
||||
tags := make([]string, len(instance.Tags))
|
||||
for i, tag := range instance.Tags {
|
||||
tags[i] = "+" + tag
|
||||
}
|
||||
fmt.Fprintf(&b, " Tags: %s\n", strings.Join(tags, " "))
|
||||
}
|
||||
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// FormatCompletionFeedback returns completion feedback with recurrence info.
|
||||
func FormatCompletionFeedback(task *Task, displayID int, nextInstance *Task) string {
|
||||
var b strings.Builder
|
||||
|
||||
fmt.Fprintf(&b, "Completed task %d — %q\n", displayID, task.Description)
|
||||
|
||||
if nextInstance != nil {
|
||||
if nextInstance.Due != nil {
|
||||
fmt.Fprintf(&b, "Next instance created — due: %s\n", FormatDateWithRelative(*nextInstance.Due))
|
||||
} else {
|
||||
fmt.Fprintf(&b, "Next instance created\n")
|
||||
}
|
||||
}
|
||||
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func resolveDisplayID(task *Task, ws *WorkingSet) int {
|
||||
if ws == nil {
|
||||
return 0
|
||||
}
|
||||
for id, uuid := range ws.byID {
|
||||
if uuid == task.UUID {
|
||||
return id
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func truncate(s string, max int) string {
|
||||
if len(s) <= max {
|
||||
return s
|
||||
}
|
||||
return s[:max-1] + "…"
|
||||
}
|
||||
@@ -188,20 +188,21 @@ func CreateRecurringTask(description string, mod *Modifier) (*Task, error) {
|
||||
return instance, nil
|
||||
}
|
||||
|
||||
// SpawnNextInstance creates a new task instance from completed recurring task
|
||||
func SpawnNextInstance(completedInstance *Task) error {
|
||||
// SpawnNextInstance creates a new task instance from completed recurring task.
|
||||
// Returns the newly created instance, or nil if recurrence has expired.
|
||||
func SpawnNextInstance(completedInstance *Task) (*Task, error) {
|
||||
if completedInstance.ParentUUID == nil {
|
||||
return fmt.Errorf("task is not a recurring instance")
|
||||
return nil, fmt.Errorf("task is not a recurring instance")
|
||||
}
|
||||
|
||||
// Load template
|
||||
template, err := GetTask(*completedInstance.ParentUUID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to load template: %w", err)
|
||||
return nil, fmt.Errorf("failed to load template: %w", err)
|
||||
}
|
||||
|
||||
if template.RecurrenceDuration == nil {
|
||||
return fmt.Errorf("template has no recurrence duration")
|
||||
return nil, fmt.Errorf("template has no recurrence duration")
|
||||
}
|
||||
|
||||
// Calculate next due date
|
||||
@@ -212,7 +213,7 @@ func SpawnNextInstance(completedInstance *Task) error {
|
||||
} else if completedInstance.Due != nil {
|
||||
baseDate = *completedInstance.Due
|
||||
} else {
|
||||
return fmt.Errorf("recurring instance has no due or end date")
|
||||
return nil, fmt.Errorf("recurring instance has no due or end date")
|
||||
}
|
||||
|
||||
next := CalculateNextDue(baseDate, *template.RecurrenceDuration)
|
||||
@@ -221,7 +222,7 @@ func SpawnNextInstance(completedInstance *Task) error {
|
||||
// Check if we're past 'until' date
|
||||
if template.Until != nil && nextDue != nil && nextDue.After(*template.Until) {
|
||||
// Don't spawn, recurrence has expired
|
||||
return nil
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Create new instance
|
||||
@@ -243,20 +244,20 @@ func SpawnNextInstance(completedInstance *Task) error {
|
||||
}
|
||||
|
||||
if err := newInstance.Save(); err != nil {
|
||||
return fmt.Errorf("failed to save new instance: %w", err)
|
||||
return nil, fmt.Errorf("failed to save new instance: %w", err)
|
||||
}
|
||||
|
||||
// Copy tags from template
|
||||
templateTags, err := template.GetTags()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get template tags: %w", err)
|
||||
return nil, fmt.Errorf("failed to get template tags: %w", err)
|
||||
}
|
||||
|
||||
for _, tag := range templateTags {
|
||||
if err := newInstance.AddTag(tag); err != nil {
|
||||
return fmt.Errorf("failed to add tag: %w", err)
|
||||
return nil, fmt.Errorf("failed to add tag: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
return newInstance, nil
|
||||
}
|
||||
|
||||
@@ -196,7 +196,7 @@ func TestSpawnNextInstance(t *testing.T) {
|
||||
}
|
||||
|
||||
// Complete the instance (should spawn next)
|
||||
if err := instance1.Complete(); err != nil {
|
||||
if _, err := instance1.Complete(); err != nil {
|
||||
t.Fatalf("Failed to complete instance: %v", err)
|
||||
}
|
||||
|
||||
@@ -306,7 +306,7 @@ func TestRecurrenceWithUntilDate(t *testing.T) {
|
||||
}
|
||||
|
||||
// Complete instance - should NOT spawn new one (past until date)
|
||||
if err := instance.Complete(); err != nil {
|
||||
if _, err := instance.Complete(); err != nil {
|
||||
t.Fatalf("Failed to complete instance: %v", err)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
package engine
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"time"
|
||||
)
|
||||
|
||||
// FormatRelativeDate returns a human-readable relative date string.
|
||||
// Within 14 days: "today", "tomorrow", "yesterday", "in 3d", "2d ago"
|
||||
// Beyond 14 days: "Feb 28", "Mar 15"
|
||||
// Cross-year: "Feb 28 2027"
|
||||
func FormatRelativeDate(t time.Time) string {
|
||||
now := timeNow()
|
||||
today := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
|
||||
target := time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, t.Location())
|
||||
|
||||
days := int(math.Round(target.Sub(today).Hours() / 24))
|
||||
|
||||
switch {
|
||||
case days == 0:
|
||||
return "today"
|
||||
case days == 1:
|
||||
return "tomorrow"
|
||||
case days == -1:
|
||||
return "yesterday"
|
||||
case days > 1 && days <= 14:
|
||||
return fmt.Sprintf("in %dd", days)
|
||||
case days < -1 && days >= -14:
|
||||
return fmt.Sprintf("%dd ago", -days)
|
||||
default:
|
||||
if t.Year() != now.Year() {
|
||||
return t.Format("Jan 2 2006")
|
||||
}
|
||||
return t.Format("Jan 2")
|
||||
}
|
||||
}
|
||||
|
||||
// FormatDateWithRelative returns "2026-02-20 (in 2 days)" style.
|
||||
// Used in info/detail views where both absolute and relative are useful.
|
||||
func FormatDateWithRelative(t time.Time) string {
|
||||
absolute := t.Format("2006-01-02 15:04")
|
||||
relative := FormatRelativeDate(t)
|
||||
return fmt.Sprintf("%s (%s)", absolute, relative)
|
||||
}
|
||||
@@ -694,25 +694,27 @@ func (t *Task) GetTags() ([]string, error) {
|
||||
return tags, nil
|
||||
}
|
||||
|
||||
// Complete marks a task as completed
|
||||
func (t *Task) Complete() error {
|
||||
// Complete marks a task as completed.
|
||||
// Returns the next recurring instance if one was spawned, or nil.
|
||||
func (t *Task) Complete() (*Task, error) {
|
||||
t.Status = StatusCompleted
|
||||
now := timeNow()
|
||||
t.End = &now
|
||||
|
||||
if err := t.Save(); err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// If this is a recurring instance, spawn next instance
|
||||
if t.ParentUUID != nil {
|
||||
if err := SpawnNextInstance(t); err != nil {
|
||||
// Log error but don't fail the completion
|
||||
return fmt.Errorf("completed task but failed to spawn next instance: %w", err)
|
||||
next, err := SpawnNextInstance(t)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("completed task but failed to spawn next instance: %w", err)
|
||||
}
|
||||
return next, nil
|
||||
}
|
||||
|
||||
return nil
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Delete marks a task as deleted (soft delete)
|
||||
|
||||
@@ -165,7 +165,7 @@ func TestTaskComplete(t *testing.T) {
|
||||
t.Fatalf("Failed to create task: %v", err)
|
||||
}
|
||||
|
||||
if err := task.Complete(); err != nil {
|
||||
if _, err := task.Complete(); err != nil {
|
||||
t.Fatalf("Failed to complete task: %v", err)
|
||||
}
|
||||
|
||||
|
||||
@@ -142,3 +142,32 @@ func (ws *WorkingSet) GetTasks() []*Task {
|
||||
func (ws *WorkingSet) Size() int {
|
||||
return len(ws.byID)
|
||||
}
|
||||
|
||||
// ByID returns the display_id -> UUID mapping.
|
||||
func (ws *WorkingSet) ByID() map[int]uuid.UUID {
|
||||
return ws.byID
|
||||
}
|
||||
|
||||
// AppendTask inserts a task into the working set at MAX(display_id) + 1.
|
||||
// Returns the assigned display ID. The ID is valid until the next report
|
||||
// render, at which point the entire working set is rebuilt.
|
||||
func AppendTask(task *Task) (int, error) {
|
||||
db := GetDB()
|
||||
if db == nil {
|
||||
return 0, fmt.Errorf("database not initialized")
|
||||
}
|
||||
|
||||
var maxID int
|
||||
err := db.QueryRow("SELECT COALESCE(MAX(display_id), 0) FROM working_set").Scan(&maxID)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to query max display_id: %w", err)
|
||||
}
|
||||
|
||||
newID := maxID + 1
|
||||
_, err = db.Exec("INSERT INTO working_set (display_id, task_uuid) VALUES (?, ?)", newID, task.UUID.String())
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to append to working set: %w", err)
|
||||
}
|
||||
|
||||
return newID, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user