fix(diff): preserve due/rec when absent from buffer line

Problem: `diff.apply` overwrites `task.due` and `task.recur` with `nil`
whenever those fields aren't present as inline tokens in the buffer line.
Because metadata is rendered as virtual text (never in the line text),
every description edit silently clears due dates and recurrence rules.

Solution: Only update `due`, `recur`, and `recur_mode` in the existing-
task branch when the parsed entry actually contains them (non-nil). Users
can still set/change these inline by typing `due:<date>` or `rec:<rule>`;
clearing them requires `:Pending edit <id> -due`.
This commit is contained in:
Barrett Ruth 2026-03-05 12:18:57 -05:00
parent 4187357a41
commit 87679e9857
2 changed files with 27 additions and 12 deletions

View file

@ -121,17 +121,19 @@ function M.apply(lines, s, hidden_ids)
task.priority = entry.priority task.priority = entry.priority
changed = true changed = true
end end
if task.due ~= entry.due then if entry.due ~= nil and task.due ~= entry.due then
task.due = entry.due task.due = entry.due
changed = true changed = true
end end
if task.recur ~= entry.rec then if entry.rec ~= nil then
task.recur = entry.rec if task.recur ~= entry.rec then
changed = true task.recur = entry.rec
end changed = true
if task.recur_mode ~= entry.rec_mode then end
task.recur_mode = entry.rec_mode if task.recur_mode ~= entry.rec_mode then
changed = true task.recur_mode = entry.rec_mode
changed = true
end
end end
if entry.status and task.status ~= entry.status then if entry.status and task.status ~= entry.status then
task.status = entry.status task.status = entry.status

View file

@ -199,7 +199,7 @@ describe('diff', function()
assert.are.equal(modified_after_first, task.modified) assert.are.equal(modified_after_first, task.modified)
end) end)
it('clears due when removed from buffer line', function() it('preserves due when not present in buffer line', function()
s:add({ description = 'Pay bill', due = '2026-03-15' }) s:add({ description = 'Pay bill', due = '2026-03-15' })
s:save() s:save()
local lines = { local lines = {
@ -209,7 +209,20 @@ describe('diff', function()
diff.apply(lines, s) diff.apply(lines, s)
s:load() s:load()
local task = s:get(1) local task = s:get(1)
assert.is_nil(task.due) assert.are.equal('2026-03-15', task.due)
end)
it('updates due when inline token is present', function()
s:add({ description = 'Pay bill', due = '2026-03-15' })
s:save()
local lines = {
'# Inbox',
'/1/- [ ] Pay bill due:2026-04-01',
}
diff.apply(lines, s)
s:load()
local task = s:get(1)
assert.are.equal('2026-04-01', task.due)
end) end)
it('stores recur field on new tasks from buffer', function() it('stores recur field on new tasks from buffer', function()
@ -237,7 +250,7 @@ describe('diff', function()
assert.are.equal('weekly', task.recur) assert.are.equal('weekly', task.recur)
end) end)
it('clears recur when token removed from line', function() it('preserves recur when not present in buffer line', function()
s:add({ description = 'Task', recur = 'daily' }) s:add({ description = 'Task', recur = 'daily' })
s:save() s:save()
local lines = { local lines = {
@ -247,7 +260,7 @@ describe('diff', function()
diff.apply(lines, s) diff.apply(lines, s)
s:load() s:load()
local task = s:get(1) local task = s:get(1)
assert.is_nil(task.recur) assert.are.equal('daily', task.recur)
end) end)
it('parses rec: with completion mode prefix', function() it('parses rec: with completion mode prefix', function()