feat: add annotations, undo system, and schema updates
Add annotations as JSON column on tasks table with Annotate/Denotate methods and CLI commands. Add undo system backed by change_log with lightweight undo_stack table (capped at 10 entries). All mutating CLI commands (add, done, delete, modify, start, stop) now record undo entries. Undo restores prior task state from change_log data. Schema changes (in v1 migration): - annotations TEXT column on tasks - undo_stack table - annotations field in change_log triggers Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -198,6 +198,17 @@ func generateEditableContent(task *engine.Task) string {
|
||||
sb.WriteString("tags: \n")
|
||||
}
|
||||
|
||||
// Annotations
|
||||
sb.WriteString("\n# Annotations (add/remove/modify lines below)\n")
|
||||
if len(task.Annotations) > 0 {
|
||||
for i, ann := range task.Annotations {
|
||||
ts := time.Unix(ann.Timestamp, 0).Format("2006-01-02 15:04")
|
||||
sb.WriteString(fmt.Sprintf("annotation.%d: %s | %s\n", i+1, ts, ann.Text))
|
||||
}
|
||||
}
|
||||
sb.WriteString("# To add: annotation.N: YYYY-MM-DD HH:MM | text\n")
|
||||
sb.WriteString("# To remove: delete the line\n")
|
||||
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
@@ -362,6 +373,32 @@ func applyNonStatusFields(task *engine.Task, fields map[string]string) error {
|
||||
}
|
||||
}
|
||||
|
||||
// Annotations - rebuild from annotation.N fields
|
||||
var newAnnotations []engine.Annotation
|
||||
for key, value := range fields {
|
||||
if strings.HasPrefix(key, "annotation.") && value != "" {
|
||||
parts := strings.SplitN(value, " | ", 2)
|
||||
if len(parts) == 2 {
|
||||
ts, err := time.Parse("2006-01-02 15:04", strings.TrimSpace(parts[0]))
|
||||
if err != nil {
|
||||
// If we can't parse the timestamp, use current time
|
||||
ts = time.Now()
|
||||
}
|
||||
newAnnotations = append(newAnnotations, engine.Annotation{
|
||||
Timestamp: ts.Unix(),
|
||||
Text: strings.TrimSpace(parts[1]),
|
||||
})
|
||||
} else {
|
||||
// No timestamp separator, treat entire value as text
|
||||
newAnnotations = append(newAnnotations, engine.Annotation{
|
||||
Timestamp: time.Now().Unix(),
|
||||
Text: strings.TrimSpace(value),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
task.Annotations = newAnnotations
|
||||
|
||||
// Tags - replace all tags
|
||||
if tagsStr, ok := fields["tags"]; ok {
|
||||
// Remove all existing tags
|
||||
|
||||
Reference in New Issue
Block a user