From c8c35ab534565f290a4457162666ff7333d71431 Mon Sep 17 00:00:00 2001 From: Barrett Ruth Date: Tue, 24 Feb 2026 18:29:49 -0500 Subject: [PATCH] feat(store): add snapshot() and atomic json write Problem: the single-level undo used shallow references so mutations during diff.apply() corrupted the saved state. JSON writes were also non-atomic, risking partial writes on crash. Solution: add M.snapshot() which deep-copies active tasks (including _extra). Change M.save() to write a .tmp file then rename atomically. --- lua/pending/store.lua | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/lua/pending/store.lua b/lua/pending/store.lua index fae9e27..5838414 100644 --- a/lua/pending/store.lua +++ b/lua/pending/store.lua @@ -175,12 +175,18 @@ function M.save() table.insert(out.tasks, task_to_table(task)) end local encoded = vim.json.encode(out) - local f = io.open(path, 'w') + local tmp = path .. '.tmp' + local f = io.open(tmp, 'w') if not f then - error('pending.nvim: cannot write to ' .. path) + error('pending.nvim: cannot write to ' .. tmp) end f:write(encoded) f:close() + local ok, rename_err = os.rename(tmp, path) + if not ok then + os.remove(tmp) + error('pending.nvim: cannot rename ' .. tmp .. ' to ' .. path .. ': ' .. tostring(rename_err)) + end end ---@return pending.Data @@ -284,6 +290,27 @@ function M.replace_tasks(tasks) M.data().tasks = tasks end +---@return pending.Task[] +function M.snapshot() + local result = {} + for _, task in ipairs(M.active_tasks()) do + local copy = {} + for k, v in pairs(task) do + if k ~= '_extra' then + copy[k] = v + end + end + if task._extra then + copy._extra = {} + for k, v in pairs(task._extra) do + copy._extra[k] = v + end + end + table.insert(result, copy --[[@as pending.Task]]) + end + return result +end + ---@param id integer function M.set_next_id(id) M.data().next_id = id