From 597ec6bb2a08dc62e151b35c26f726f62f384c2d Mon Sep 17 00:00:00 2001 From: Barrett Ruth Date: Wed, 25 Feb 2026 13:03:58 -0500 Subject: [PATCH] feat(diff): propagate recurrence through buffer reconciliation Problem: the diff layer does not extract or apply recurrence fields, so rec: tokens written in the buffer are silently ignored on :w. Solution: add rec and rec_mode to ParsedEntry, extract them in parse_buffer(), and pass them through create and update paths in apply(). --- lua/pending/diff.lua | 16 ++++++++++ spec/diff_spec.lua | 73 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 89 insertions(+) diff --git a/lua/pending/diff.lua b/lua/pending/diff.lua index 85f083c..bec3baa 100644 --- a/lua/pending/diff.lua +++ b/lua/pending/diff.lua @@ -10,6 +10,8 @@ local store = require('pending.store') ---@field status? string ---@field category? string ---@field due? string +---@field rec? string +---@field rec_mode? string ---@field lnum integer ---@class pending.diff @@ -48,6 +50,8 @@ function M.parse_buffer(lines) status = status, category = metadata.cat or current_category or config.get().default_category, due = metadata.due, + rec = metadata.rec, + rec_mode = metadata.rec_mode, lnum = i, }) end @@ -90,6 +94,8 @@ function M.apply(lines) category = entry.category, priority = entry.priority, due = entry.due, + recur = entry.rec, + recur_mode = entry.rec_mode, order = order_counter, }) else @@ -112,6 +118,14 @@ function M.apply(lines) task.due = entry.due changed = true end + if task.recur ~= entry.rec then + task.recur = entry.rec + changed = true + end + if task.recur_mode ~= entry.rec_mode then + task.recur_mode = entry.rec_mode + changed = true + end if entry.status and task.status ~= entry.status then task.status = entry.status if entry.status == 'done' then @@ -135,6 +149,8 @@ function M.apply(lines) category = entry.category, priority = entry.priority, due = entry.due, + recur = entry.rec, + recur_mode = entry.rec_mode, order = order_counter, }) end diff --git a/spec/diff_spec.lua b/spec/diff_spec.lua index fda2165..d8e25c2 100644 --- a/spec/diff_spec.lua +++ b/spec/diff_spec.lua @@ -69,6 +69,25 @@ describe('diff', function() assert.are.equal('Work', result[2].category) end) + it('extracts rec: token from buffer line', function() + local lines = { + '## Inbox', + '/1/- [ ] Take trash out rec:weekly', + } + local result = diff.parse_buffer(lines) + assert.are.equal('weekly', result[2].rec) + end) + + it('extracts rec: with completion mode', function() + local lines = { + '## Inbox', + '/1/- [ ] Water plants rec:!daily', + } + local result = diff.parse_buffer(lines) + assert.are.equal('daily', result[2].rec) + assert.are.equal('completion', result[2].rec_mode) + end) + it('inline due: token is parsed', function() local lines = { '## Inbox', @@ -206,6 +225,60 @@ describe('diff', function() assert.is_nil(task.due) end) + it('stores recur field on new tasks from buffer', function() + local lines = { + '## Inbox', + '- [ ] Take out trash rec:weekly', + } + diff.apply(lines) + store.unload() + store.load() + local tasks = store.active_tasks() + assert.are.equal(1, #tasks) + assert.are.equal('weekly', tasks[1].recur) + end) + + it('updates recur field when changed inline', function() + store.add({ description = 'Task', recur = 'daily' }) + store.save() + local lines = { + '## Todo', + '/1/- [ ] Task rec:weekly', + } + diff.apply(lines) + store.unload() + store.load() + local task = store.get(1) + assert.are.equal('weekly', task.recur) + end) + + it('clears recur when token removed from line', function() + store.add({ description = 'Task', recur = 'daily' }) + store.save() + local lines = { + '## Todo', + '/1/- [ ] Task', + } + diff.apply(lines) + store.unload() + store.load() + local task = store.get(1) + assert.is_nil(task.recur) + end) + + it('parses rec: with completion mode prefix', function() + local lines = { + '## Inbox', + '- [ ] Water plants rec:!weekly', + } + diff.apply(lines) + store.unload() + store.load() + local tasks = store.active_tasks() + assert.are.equal('weekly', tasks[1].recur) + assert.are.equal('completion', tasks[1].recur_mode) + end) + it('clears priority when [N] is removed from buffer line', function() store.add({ description = 'Task name', priority = 1 }) store.save()