Skip to content

Commit c75bb4f

Browse files
committed
feat: task annotations & history stack
1 parent 2fc5eea commit c75bb4f

File tree

9 files changed

+950
-5
lines changed

9 files changed

+950
-5
lines changed

cmd/task_commands.go

Lines changed: 147 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package main
22

33
import (
4+
"fmt"
5+
"strconv"
46
"strings"
57

68
"github.com/spf13/cobra"
@@ -37,7 +39,7 @@ time tracking. Tasks can be filtered by status, priority, project, or context.`,
3739
)
3840

3941
for _, init := range []func(*handlers.TaskHandler) *cobra.Command{
40-
addTaskCmd, listTaskCmd, viewTaskCmd, updateTaskCmd, editTaskCmd, deleteTaskCmd,
42+
addTaskCmd, listTaskCmd, viewTaskCmd, updateTaskCmd, editTaskCmd, deleteTaskCmd, taskAnnotateCmd, taskBulkEditCmd,
4143
} {
4244
cmd := init(c.handler)
4345
cmd.GroupID = "task-ops"
@@ -53,7 +55,7 @@ time tracking. Tasks can be filtered by status, priority, project, or context.`,
5355
}
5456

5557
for _, init := range []func(*handlers.TaskHandler) *cobra.Command{
56-
timesheetViewCmd, taskStartCmd, taskStopCmd, taskCompleteCmd, taskRecurCmd, taskDependCmd,
58+
timesheetViewCmd, taskStartCmd, taskStopCmd, taskCompleteCmd, taskRecurCmd, taskDependCmd, taskUndoCmd, taskHistoryCmd,
5759
} {
5860
cmd := init(c.handler)
5961
cmd.GroupID = "task-tracking"
@@ -609,3 +611,146 @@ UUIDs to specify dependencies.`,
609611
root.AddCommand(addCmd, removeCmd, listCmd, blockedByCmd)
610612
return root
611613
}
614+
615+
func taskAnnotateCmd(h *handlers.TaskHandler) *cobra.Command {
616+
root := &cobra.Command{
617+
Use: "annotate",
618+
Aliases: []string{"note"},
619+
Short: "Manage task annotations",
620+
Long: `Add, list, or remove annotations on tasks.
621+
622+
Annotations are timestamped notes that provide context and updates
623+
about a task's progress or relevant information.`,
624+
}
625+
626+
addCmd := &cobra.Command{
627+
Use: "add <task-id> <annotation>",
628+
Short: "Add an annotation to a task",
629+
Aliases: []string{"create"},
630+
Args: cobra.MinimumNArgs(2),
631+
RunE: func(c *cobra.Command, args []string) error {
632+
taskID := args[0]
633+
annotation := strings.Join(args[1:], " ")
634+
defer h.Close()
635+
return h.Annotate(c.Context(), taskID, annotation)
636+
},
637+
}
638+
639+
listCmd := &cobra.Command{
640+
Use: "list <task-id>",
641+
Short: "List all annotations for a task",
642+
Aliases: []string{"ls", "show"},
643+
Args: cobra.ExactArgs(1),
644+
RunE: func(c *cobra.Command, args []string) error {
645+
defer h.Close()
646+
return h.ListAnnotations(c.Context(), args[0])
647+
},
648+
}
649+
650+
removeCmd := &cobra.Command{
651+
Use: "remove <task-id> <index>",
652+
Short: "Remove an annotation by index",
653+
Aliases: []string{"rm", "delete"},
654+
Args: cobra.ExactArgs(2),
655+
RunE: func(c *cobra.Command, args []string) error {
656+
taskID := args[0]
657+
index, err := strconv.Atoi(args[1])
658+
if err != nil {
659+
return fmt.Errorf("invalid annotation index: %w", err)
660+
}
661+
defer h.Close()
662+
return h.RemoveAnnotation(c.Context(), taskID, index)
663+
},
664+
}
665+
666+
root.AddCommand(addCmd, listCmd, removeCmd)
667+
return root
668+
}
669+
670+
func taskBulkEditCmd(h *handlers.TaskHandler) *cobra.Command {
671+
cmd := &cobra.Command{
672+
Use: "bulk-edit <task-id>...",
673+
Aliases: []string{"bulk"},
674+
Short: "Update multiple tasks at once",
675+
Long: `Update multiple tasks with the same changes.
676+
677+
Allows batch updates to status, priority, project, context, and tags.
678+
Use --add-tags to add tags without replacing existing ones.
679+
Use --remove-tags to remove specific tags from tasks.
680+
681+
Examples:
682+
noteleaf todo bulk-edit 1 2 3 --status done
683+
noteleaf todo bulk-edit 1 2 --project web --priority high
684+
noteleaf todo bulk-edit 1 2 3 --add-tags urgent,review`,
685+
Args: cobra.MinimumNArgs(1),
686+
RunE: func(c *cobra.Command, args []string) error {
687+
status, _ := c.Flags().GetString("status")
688+
priority, _ := c.Flags().GetString("priority")
689+
project, _ := c.Flags().GetString("project")
690+
context, _ := c.Flags().GetString("context")
691+
tags, _ := c.Flags().GetStringSlice("tags")
692+
addTags, _ := c.Flags().GetBool("add-tags")
693+
removeTags, _ := c.Flags().GetBool("remove-tags")
694+
695+
defer h.Close()
696+
return h.BulkEdit(c.Context(), args, status, priority, project, context, tags, addTags, removeTags)
697+
},
698+
}
699+
700+
cmd.Flags().String("status", "", "Set status for all tasks")
701+
cmd.Flags().String("priority", "", "Set priority for all tasks")
702+
cmd.Flags().String("project", "", "Set project for all tasks")
703+
cmd.Flags().String("context", "", "Set context for all tasks")
704+
cmd.Flags().StringSlice("tags", []string{}, "Set tags for all tasks")
705+
cmd.Flags().Bool("add-tags", false, "Add tags instead of replacing")
706+
cmd.Flags().Bool("remove-tags", false, "Remove specified tags")
707+
708+
return cmd
709+
}
710+
711+
func taskUndoCmd(h *handlers.TaskHandler) *cobra.Command {
712+
cmd := &cobra.Command{
713+
Use: "undo <task-id>",
714+
Short: "Undo the last change to a task",
715+
Long: `Revert a task to its previous state before the last update.
716+
717+
This command uses the task history to restore the task to how it was
718+
before the most recent modification.
719+
720+
Examples:
721+
noteleaf todo undo 1
722+
noteleaf todo undo abc-123-uuid`,
723+
Args: cobra.ExactArgs(1),
724+
RunE: func(c *cobra.Command, args []string) error {
725+
defer h.Close()
726+
return h.UndoTask(c.Context(), args[0])
727+
},
728+
}
729+
730+
return cmd
731+
}
732+
733+
func taskHistoryCmd(h *handlers.TaskHandler) *cobra.Command {
734+
cmd := &cobra.Command{
735+
Use: "history <task-id>",
736+
Aliases: []string{"log"},
737+
Short: "Show change history for a task",
738+
Long: `Display the history of changes made to a task.
739+
740+
Shows a chronological list of modifications with timestamps.
741+
742+
Examples:
743+
noteleaf todo history 1
744+
noteleaf todo history 1 --limit 5`,
745+
Args: cobra.ExactArgs(1),
746+
RunE: func(c *cobra.Command, args []string) error {
747+
limit, _ := c.Flags().GetInt("limit")
748+
defer h.Close()
749+
return h.ShowHistory(c.Context(), args[0], limit)
750+
},
751+
}
752+
753+
cmd.Flags().IntP("limit", "n", 10, "Limit number of history entries")
754+
755+
return cmd
756+
}

internal/docs/ROADMAP.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -130,9 +130,9 @@ Planned functionality for a complete baseline release.
130130
- [x] Recurrence (`recur`, `until`, templates)
131131
- [x] Wait/scheduled dates
132132
- [x] Urgency scoring
133-
- [ ] Operations
134-
- [ ] `annotate`
135-
- [ ] Bulk edit and undo/history
133+
- [x] Operations
134+
- [x] `annotate`
135+
- [x] Bulk edit and undo/history
136136
- [x] Reports and Views
137137
- [x] Next actions
138138
- [x] Completed/waiting/blocked reports

0 commit comments

Comments
 (0)