fix: use Modifier.Set() to maintain AttributeOrder invariant in API handlers
API handlers were populating SetAttributes directly without appending to AttributeOrder. Since Apply() only iterates AttributeOrder, all updates via PUT/POST were silently dropped — causing edits to revert and tasks to disappear on reload. Adds Modifier.Set() helper, safety net in Apply()/ApplyToNew(), adds description and status to applyNonDateAttribute, and fixes the recurrence->recur key mismatch. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -4,14 +4,15 @@ package engine
|
||||
// Used by parseAddArgs (cmd/add.go), ParseFilter, and ParseModifier
|
||||
// to distinguish modifiers from description text.
|
||||
var ValidAttributeKeys = map[string]bool{
|
||||
"due": true,
|
||||
"priority": true,
|
||||
"project": true,
|
||||
"recur": true,
|
||||
"status": true,
|
||||
"wait": true,
|
||||
"scheduled": true,
|
||||
"until": true,
|
||||
"description": true,
|
||||
"due": true,
|
||||
"priority": true,
|
||||
"project": true,
|
||||
"recur": true,
|
||||
"status": true,
|
||||
"wait": true,
|
||||
"scheduled": true,
|
||||
"until": true,
|
||||
}
|
||||
|
||||
// DateKeys is the subset of ValidAttributeKeys that hold date values.
|
||||
|
||||
@@ -23,6 +23,13 @@ func NewModifier() *Modifier {
|
||||
}
|
||||
}
|
||||
|
||||
// Set adds an attribute to the modifier, maintaining the SetAttributes and
|
||||
// AttributeOrder invariant. Pass nil to clear the attribute.
|
||||
func (m *Modifier) Set(key string, value *string) {
|
||||
m.SetAttributes[key] = value
|
||||
m.AttributeOrder = append(m.AttributeOrder, key)
|
||||
}
|
||||
|
||||
// ParseModifier parses command-line args into Modifier.
|
||||
// Only recognized attribute keys (ValidAttributeKeys) are accepted;
|
||||
// unrecognized key:value tokens produce an error.
|
||||
@@ -86,6 +93,14 @@ func (m *Modifier) Apply(task *Task) error {
|
||||
resolvedDates["created"] = task.Created
|
||||
resolvedDates["modified"] = task.Modified
|
||||
|
||||
// Safety net: if SetAttributes were populated without AttributeOrder,
|
||||
// reconstruct order from map keys so updates aren't silently dropped.
|
||||
if len(m.AttributeOrder) == 0 && len(m.SetAttributes) > 0 {
|
||||
for key := range m.SetAttributes {
|
||||
m.AttributeOrder = append(m.AttributeOrder, key)
|
||||
}
|
||||
}
|
||||
|
||||
// Apply attributes in the order they were specified (important for relative references)
|
||||
dateKeys := DateKeys
|
||||
|
||||
@@ -134,6 +149,14 @@ func (m *Modifier) ApplyToNew(task *Task) error {
|
||||
resolvedDates["created"] = task.Created
|
||||
}
|
||||
|
||||
// Safety net: if SetAttributes were populated without AttributeOrder,
|
||||
// reconstruct order from map keys so updates aren't silently dropped.
|
||||
if len(m.AttributeOrder) == 0 && len(m.SetAttributes) > 0 {
|
||||
for key := range m.SetAttributes {
|
||||
m.AttributeOrder = append(m.AttributeOrder, key)
|
||||
}
|
||||
}
|
||||
|
||||
// Apply attributes in the order they were specified (important for relative references)
|
||||
dateKeys := DateKeys
|
||||
|
||||
@@ -156,9 +179,17 @@ func (m *Modifier) ApplyToNew(task *Task) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// applyNonDateAttribute applies a non-date attribute (priority, project, recur) to a task.
|
||||
// applyNonDateAttribute applies a non-date attribute to a task.
|
||||
func applyNonDateAttribute(key string, valuePtr *string, task *Task) error {
|
||||
switch key {
|
||||
case "description":
|
||||
if valuePtr != nil {
|
||||
task.Description = *valuePtr
|
||||
}
|
||||
case "status":
|
||||
if valuePtr != nil && len(*valuePtr) > 0 {
|
||||
task.Status = Status((*valuePtr)[0])
|
||||
}
|
||||
case "priority":
|
||||
if valuePtr == nil {
|
||||
task.Priority = PriorityDefault
|
||||
|
||||
Reference in New Issue
Block a user