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:
2026-02-19 13:44:56 +01:00
parent 779da6ddfd
commit b02c40f716
14 changed files with 323 additions and 48 deletions
+45
View File
@@ -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)
}