From d26bdcb3a8de92bc0c23916f6fa5e0d40785af04 Mon Sep 17 00:00:00 2001 From: Barrett Ruth <62671086+barrettruth@users.noreply.github.com> Date: Wed, 11 Mar 2026 12:55:36 -0400 Subject: [PATCH] refactor: tighten LuaCATS annotations and canonicalize metadata fields (#141) * refactor: tighten LuaCATS annotations across modules Problem: type annotations repeated inline unions with no aliases, used `table` where structured types exist, and had loose `string` where union types should be used. Solution: add `pending.TaskStatus`, `pending.RecurMode`, `pending.TaskExtra`, `pending.ForgeType`, `pending.ForgeState`, `pending.ForgeAuthStatus` aliases and `pending.SyncBackend` interface. Replace inline unions and loose types with the new aliases in `store.lua`, `forge.lua`, `config.lua`, `diff.lua`, `views.lua`, `parse.lua`, `init.lua`, and `oauth.lua`. * refactor: canonicalize internal metadata field names Problem: `pending.Metadata` used shorthand field names (`cat`, `rec`, `rec_mode`) matching user-facing token syntax, coupling internal representation to config. `RecurSpec.from_completion` used a boolean where a `pending.RecurMode` alias exists. `category_syntax` was hardcoded to `'cat'` with no config option. Solution: rename `Metadata` fields to `category`/`recur`/`recur_mode`, add `category_syntax` config option (default `'cat'`), rename `ParsedEntry` fields to match, replace `RecurSpec.from_completion` with `mode: pending.RecurMode`, and restore `[string]` indexer on `pending.ForgeConfig` alongside explicit fields. --- doc/pending.txt | 14 +++++++++++--- lua/pending/complete.lua | 10 ++++++---- lua/pending/config.lua | 5 +++++ lua/pending/diff.lua | 30 +++++++++++++++--------------- lua/pending/forge.lua | 12 ++++++++---- lua/pending/init.lua | 27 ++++++++++++++++++++------- lua/pending/parse.lua | 27 +++++++++++++++++---------- lua/pending/recur.lua | 30 +++++++++++++++--------------- lua/pending/store.lua | 24 ++++++++++++++++++------ lua/pending/sync/oauth.lua | 9 +++++++++ lua/pending/views.lua | 2 +- plugin/pending.lua | 12 ++++++++---- spec/diff_spec.lua | 6 +++--- spec/forge_spec.lua | 2 +- spec/parse_spec.lua | 10 +++++----- spec/recur_spec.lua | 4 ++-- 16 files changed, 144 insertions(+), 80 deletions(-) diff --git a/doc/pending.txt b/doc/pending.txt index 90db8ee..7270c8e 100644 --- a/doc/pending.txt +++ b/doc/pending.txt @@ -605,9 +605,10 @@ Supported tokens: ~ `cat:Name` Move the task to the named category on save. `rec:` Set a recurrence rule (see |pending-recurrence|). -The token name for due dates defaults to `due` and is configurable via -`date_syntax` in |pending-config|. The token name for recurrence defaults to -`rec` and is configurable via `recur_syntax`. +The token name for categories defaults to `cat` and is configurable via +`category_syntax` in |pending-config|. The token name for due dates defaults +to `due` and is configurable via `date_syntax`. The token name for recurrence +defaults to `rec` and is configurable via `recur_syntax`. Example: > @@ -734,6 +735,7 @@ loads: >lua data_path = vim.fn.stdpath('data') .. '/pending/tasks.json', default_category = 'Todo', date_format = '%b %d', + category_syntax = 'cat', date_syntax = 'due', recur_syntax = 'rec', someday_date = '9999-12-30', @@ -817,6 +819,12 @@ Fields: ~ '%m/%d', -- 03/15 (year inferred) } < + {category_syntax} (string, default: 'cat') + The token name for inline category metadata. Change + this to use a different keyword, for example + `'category'` to write `category:Work` instead of + `cat:Work`. + {date_syntax} (string, default: 'due') The token name for inline due-date metadata. Change this to use a different keyword, for example `'by'` diff --git a/lua/pending/complete.lua b/lua/pending/complete.lua index 26f8798..480a488 100644 --- a/lua/pending/complete.lua +++ b/lua/pending/complete.lua @@ -136,9 +136,11 @@ function M.omnifunc(findstart, base) local dk = date_key() local rk = recur_key() + local ck = config.get().category_syntax or 'cat' + local checks = { { vim.pesc(dk) .. ':([%S]*)$', dk }, - { 'cat:([%S]*)$', 'cat' }, + { vim.pesc(ck) .. ':([%S]*)$', ck }, { vim.pesc(rk) .. ':([%S]*)$', rk }, } for _, b in ipairs(forge.backends()) do @@ -172,10 +174,10 @@ function M.omnifunc(findstart, base) table.insert(matches, { word = c.word, menu = '[' .. source .. ']', info = c.info }) end end - elseif source == 'cat' then + elseif source == (config.get().category_syntax or 'cat') then for _, c in ipairs(get_categories()) do if base == '' or c:sub(1, #base) == base then - table.insert(matches, { word = c, menu = '[cat]' }) + table.insert(matches, { word = c, menu = '[' .. source .. ']' }) end end elseif source == rk then @@ -190,7 +192,7 @@ function M.omnifunc(findstart, base) local seen = {} for _, task in ipairs(s:tasks()) do if task._extra and task._extra._forge_ref then - local ref = task._extra._forge_ref + local ref = task._extra._forge_ref --[[@as pending.ForgeRef]] local key = ref.owner .. '/' .. ref.repo if not seen[key] then seen[key] = true diff --git a/lua/pending/config.lua b/lua/pending/config.lua index 2ec13cc..4a5172e 100644 --- a/lua/pending/config.lua +++ b/lua/pending/config.lua @@ -43,6 +43,9 @@ ---@field close? boolean ---@field validate? boolean ---@field warn_missing_cli? boolean +---@field github? pending.ForgeInstanceConfig +---@field gitlab? pending.ForgeInstanceConfig +---@field codeberg? pending.ForgeInstanceConfig ---@field [string] pending.ForgeInstanceConfig ---@class pending.SyncConfig @@ -92,6 +95,7 @@ ---@field data_path string ---@field default_category string ---@field date_format string +---@field category_syntax string ---@field date_syntax string ---@field recur_syntax string ---@field someday_date string @@ -113,6 +117,7 @@ local defaults = { data_path = vim.fn.stdpath('data') .. '/pending/tasks.json', default_category = 'Todo', date_format = '%b %d', + category_syntax = 'cat', date_syntax = 'due', recur_syntax = 'rec', someday_date = '9999-12-30', diff --git a/lua/pending/diff.lua b/lua/pending/diff.lua index 103ba6a..24645a2 100644 --- a/lua/pending/diff.lua +++ b/lua/pending/diff.lua @@ -7,11 +7,11 @@ local parse = require('pending.parse') ---@field id? integer ---@field description? string ---@field priority? integer ----@field status? string +---@field status? pending.TaskStatus ---@field category? string ---@field due? string ----@field rec? string ----@field rec_mode? string +---@field recur? string +---@field recur_mode? pending.RecurMode ---@field forge_ref? pending.ForgeRef ---@field lnum integer @@ -65,10 +65,10 @@ function M.parse_buffer(lines) description = description, priority = priority, status = status, - category = metadata.cat or current_category or config.get().default_category, + category = metadata.category or current_category or config.get().default_category, due = metadata.due, - rec = metadata.rec, - rec_mode = metadata.rec_mode, + recur = metadata.recur, + recur_mode = metadata.recur_mode, forge_ref = forge_ref, lnum = i, }) @@ -126,8 +126,8 @@ function M.apply(lines, s, hidden_ids) category = entry.category, priority = entry.priority, due = entry.due, - recur = entry.rec, - recur_mode = entry.rec_mode, + recur = entry.recur, + recur_mode = entry.recur_mode, order = order_counter, _extra = entry.forge_ref and { _forge_ref = entry.forge_ref } or nil, }) @@ -157,13 +157,13 @@ function M.apply(lines, s, hidden_ids) task.due = entry.due changed = true end - if entry.rec ~= nil then - if task.recur ~= entry.rec then - task.recur = entry.rec + if entry.recur ~= nil then + if task.recur ~= entry.recur then + task.recur = entry.recur changed = true end - if task.recur_mode ~= entry.rec_mode then - task.recur_mode = entry.rec_mode + if task.recur_mode ~= entry.recur_mode then + task.recur_mode = entry.recur_mode changed = true end end @@ -201,8 +201,8 @@ function M.apply(lines, s, hidden_ids) category = entry.category, priority = entry.priority, due = entry.due, - recur = entry.rec, - recur_mode = entry.rec_mode, + recur = entry.recur, + recur_mode = entry.recur_mode, order = order_counter, _extra = entry.forge_ref and { _forge_ref = entry.forge_ref } or nil, }) diff --git a/lua/pending/forge.lua b/lua/pending/forge.lua index 6116a9f..28c173a 100644 --- a/lua/pending/forge.lua +++ b/lua/pending/forge.lua @@ -1,17 +1,21 @@ local config = require('pending.config') local log = require('pending.log') +---@alias pending.ForgeType 'issue'|'pull_request'|'merge_request'|'repo' +---@alias pending.ForgeState 'open'|'closed'|'merged' +---@alias pending.ForgeAuthStatus 'unknown'|'ok'|'failed' + ---@class pending.ForgeRef ---@field forge string ---@field owner string ---@field repo string ----@field type 'issue'|'pull_request'|'merge_request'|'repo' +---@field type pending.ForgeType ---@field number? integer ---@field url string ---@class pending.ForgeCache ---@field title? string ----@field state 'open'|'closed'|'merged' +---@field state pending.ForgeState ---@field labels? string[] ---@field fetched_at string @@ -27,10 +31,10 @@ local log = require('pending.log') ---@field auth_status_args string[] ---@field default_icon string ---@field default_issue_format string ----@field _auth? 'unknown'|'ok'|'failed' +---@field _auth? pending.ForgeAuthStatus ---@field parse_url fun(self: pending.ForgeBackend, url: string): pending.ForgeRef? ---@field api_args fun(self: pending.ForgeBackend, ref: pending.ForgeRef): string[] ----@field parse_state fun(self: pending.ForgeBackend, decoded: table): 'open'|'closed'|'merged' +---@field parse_state fun(self: pending.ForgeBackend, decoded: table): pending.ForgeState ---@class pending.forge local M = {} diff --git a/lua/pending/init.lua b/lua/pending/init.lua index 9b642ed..aeba431 100644 --- a/lua/pending/init.lua +++ b/lua/pending/init.lua @@ -984,10 +984,10 @@ function M.add(text) end s:add({ description = description, - category = metadata.cat, + category = metadata.category, due = metadata.due, - recur = metadata.rec, - recur_mode = metadata.rec_mode, + recur = metadata.recur, + recur_mode = metadata.recur_mode, priority = metadata.priority, }) _save_and_notify() @@ -998,6 +998,14 @@ function M.add(text) log.info('Task added: ' .. description) end +---@class pending.SyncBackend +---@field name string +---@field auth fun(): nil +---@field push? fun(): nil +---@field pull? fun(): nil +---@field sync? fun(): nil +---@field health? fun(): nil + ---@type string[]? local _sync_backends = nil @@ -1186,6 +1194,7 @@ end local function parse_edit_token(token) local recur = require('pending.recur') local cfg = require('pending.config').get() + local ck = cfg.category_syntax or 'cat' local dk = cfg.date_syntax or 'due' local rk = cfg.recur_syntax or 'rec' @@ -1201,7 +1210,7 @@ local function parse_edit_token(token) if token == '-due' or token == '-' .. dk then return 'due', vim.NIL, nil end - if token == '-cat' then + if token == '-' .. ck then return 'category', vim.NIL, nil end if token == '-rec' or token == '-' .. rk then @@ -1223,7 +1232,7 @@ local function parse_edit_token(token) 'Invalid date: ' .. due_val .. '. Use YYYY-MM-DD, today, tomorrow, +Nd, weekday names, etc.' end - local cat_val = token:match('^cat:(.+)$') + local cat_val = token:match('^' .. vim.pesc(ck) .. ':(.+)$') if cat_val then return 'category', cat_val, nil end @@ -1248,11 +1257,15 @@ local function parse_edit_token(token) .. token .. '. Valid: ' .. dk - .. ':, cat:, ' + .. ':, ' + .. ck + .. ':, ' .. rk .. ':, +!, -!, -' .. dk - .. ', -cat, -' + .. ', -' + .. ck + .. ', -' .. rk end diff --git a/lua/pending/parse.lua b/lua/pending/parse.lua index 5a705ef..c38fa54 100644 --- a/lua/pending/parse.lua +++ b/lua/pending/parse.lua @@ -2,9 +2,9 @@ local config = require('pending.config') ---@class pending.Metadata ---@field due? string ----@field cat? string ----@field rec? string ----@field rec_mode? 'scheduled'|'completion' +---@field category? string +---@field recur? string +---@field recur_mode? pending.RecurMode ---@field priority? integer ---@class pending.parse @@ -107,6 +107,11 @@ local function is_valid_datetime(s) return is_valid_date(date_part) and is_valid_time(time_part) end +---@return string +local function category_key() + return config.get().category_syntax or 'cat' +end + ---@return string local function date_key() return config.get().date_syntax or 'due' @@ -531,8 +536,10 @@ function M.body(text) local metadata = {} local i = #tokens + local ck = category_key() local dk = date_key() local rk = recur_key() + local cat_pattern = '^' .. vim.pesc(ck) .. ':(%S+)$' local date_pattern_strict = '^' .. vim.pesc(dk) .. ':(%d%d%d%d%-%d%d%-%d%d[T%d:]*)$' local date_pattern_any = '^' .. vim.pesc(dk) .. ':(.+)$' local rec_pattern = '^' .. vim.pesc(rk) .. ':(%S+)$' @@ -562,12 +569,12 @@ function M.body(text) metadata.due = resolved i = i - 1 else - local cat_val = token:match('^cat:(%S+)$') + local cat_val = token:match(cat_pattern) if cat_val then - if metadata.cat then + if metadata.category then break end - metadata.cat = cat_val + metadata.category = cat_val i = i - 1 else local pri_bangs = token:match('^%+(!+)$') @@ -581,19 +588,19 @@ function M.body(text) else local rec_val = token:match(rec_pattern) if rec_val then - if metadata.rec then + if metadata.recur then break end local recur = require('pending.recur') local raw_spec = rec_val if raw_spec:sub(1, 1) == '!' then - metadata.rec_mode = 'completion' + metadata.recur_mode = 'completion' raw_spec = raw_spec:sub(2) end if not recur.validate(raw_spec) then break end - metadata.rec = raw_spec + metadata.recur = raw_spec i = i - 1 else break @@ -624,7 +631,7 @@ function M.command_add(text) local rest = text:sub(#cat_prefix + 2):match('^%s*(.+)$') if rest then local desc, meta = M.body(rest) - meta.cat = meta.cat or cat_prefix + meta.category = meta.category or cat_prefix return desc, meta end end diff --git a/lua/pending/recur.lua b/lua/pending/recur.lua index 9c647aa..8891381 100644 --- a/lua/pending/recur.lua +++ b/lua/pending/recur.lua @@ -2,7 +2,7 @@ ---@field freq 'daily'|'weekly'|'monthly'|'yearly' ---@field interval integer ---@field byday? string[] ----@field from_completion boolean +---@field mode pending.RecurMode ---@field _raw? string ---@class pending.recur @@ -10,29 +10,29 @@ local M = {} ---@type table local named = { - daily = { freq = 'daily', interval = 1, from_completion = false }, + daily = { freq = 'daily', interval = 1, mode = 'scheduled' }, weekdays = { freq = 'weekly', interval = 1, byday = { 'MO', 'TU', 'WE', 'TH', 'FR' }, - from_completion = false, + mode = 'scheduled', }, - weekly = { freq = 'weekly', interval = 1, from_completion = false }, - biweekly = { freq = 'weekly', interval = 2, from_completion = false }, - monthly = { freq = 'monthly', interval = 1, from_completion = false }, - quarterly = { freq = 'monthly', interval = 3, from_completion = false }, - yearly = { freq = 'yearly', interval = 1, from_completion = false }, - annual = { freq = 'yearly', interval = 1, from_completion = false }, + weekly = { freq = 'weekly', interval = 1, mode = 'scheduled' }, + biweekly = { freq = 'weekly', interval = 2, mode = 'scheduled' }, + monthly = { freq = 'monthly', interval = 1, mode = 'scheduled' }, + quarterly = { freq = 'monthly', interval = 3, mode = 'scheduled' }, + yearly = { freq = 'yearly', interval = 1, mode = 'scheduled' }, + annual = { freq = 'yearly', interval = 1, mode = 'scheduled' }, } ---@param spec string ---@return pending.RecurSpec? function M.parse(spec) - local from_completion = false + local mode = 'scheduled' ---@type pending.RecurMode local s = spec if s:sub(1, 1) == '!' then - from_completion = true + mode = 'completion' s = s:sub(2) end @@ -44,7 +44,7 @@ function M.parse(spec) freq = base.freq, interval = base.interval, byday = base.byday, - from_completion = from_completion, + mode = mode, } end @@ -58,7 +58,7 @@ function M.parse(spec) return { freq = freq_map[unit], interval = num, - from_completion = from_completion, + mode = mode, } end @@ -66,7 +66,7 @@ function M.parse(spec) return { freq = 'daily', interval = 1, - from_completion = from_completion, + mode = mode, _raw = s, } end @@ -134,7 +134,7 @@ end ---@param base_date string ---@param spec string ----@param mode 'scheduled'|'completion' +---@param mode pending.RecurMode ---@return string function M.next_due(base_date, spec, mode) local parsed = M.parse(spec) diff --git a/lua/pending/store.lua b/lua/pending/store.lua index fcf420e..5870fc6 100644 --- a/lua/pending/store.lua +++ b/lua/pending/store.lua @@ -1,19 +1,31 @@ local config = require('pending.config') +---@alias pending.TaskStatus 'pending'|'done'|'deleted'|'wip'|'blocked' +---@alias pending.RecurMode 'scheduled'|'completion' + +---@class pending.TaskExtra +---@field _forge_ref? pending.ForgeRef +---@field _forge_cache? pending.ForgeCache +---@field _gtasks_task_id? string +---@field _gtasks_list_id? string +---@field _gcal_event_id? string +---@field _gcal_calendar_id? string +---@field [string] any + ---@class pending.Task ---@field id integer ---@field description string ----@field status 'pending'|'done'|'deleted'|'wip'|'blocked' +---@field status pending.TaskStatus ---@field category? string ---@field priority integer ---@field due? string ---@field recur? string ----@field recur_mode? 'scheduled'|'completion' +---@field recur_mode? pending.RecurMode ---@field entry string ---@field modified string ---@field end? string ---@field order integer ----@field _extra? table +---@field _extra? pending.TaskExtra ---@class pending.Data ---@field version integer @@ -24,14 +36,14 @@ local config = require('pending.config') ---@class pending.TaskFields ---@field description string ----@field status? string +---@field status? pending.TaskStatus ---@field category? string ---@field priority? integer ---@field due? string ---@field recur? string ----@field recur_mode? string +---@field recur_mode? pending.RecurMode ---@field order? integer ----@field _extra? table +---@field _extra? pending.TaskExtra ---@class pending.Store ---@field path string diff --git a/lua/pending/sync/oauth.lua b/lua/pending/sync/oauth.lua index 8e670c1..a49595c 100644 --- a/lua/pending/sync/oauth.lua +++ b/lua/pending/sync/oauth.lua @@ -24,6 +24,15 @@ local BUNDLED_CLIENT_SECRET = 'PLACEHOLDER' ---@field config_key string ---@class pending.OAuthClient : pending.OAuthClientOpts +---@field token_path fun(self: pending.OAuthClient): string +---@field resolve_credentials fun(self: pending.OAuthClient): pending.OAuthCredentials +---@field load_tokens fun(self: pending.OAuthClient): pending.OAuthTokens? +---@field save_tokens fun(self: pending.OAuthClient, tokens: pending.OAuthTokens): boolean +---@field refresh_access_token fun(self: pending.OAuthClient, creds: pending.OAuthCredentials, tokens: pending.OAuthTokens): pending.OAuthTokens? +---@field get_access_token fun(self: pending.OAuthClient): string? +---@field setup fun(self: pending.OAuthClient): nil +---@field auth fun(self: pending.OAuthClient, on_complete?: fun(ok: boolean): nil): nil +---@field clear_tokens fun(self: pending.OAuthClient): nil local OAuthClient = {} OAuthClient.__index = OAuthClient diff --git a/lua/pending/views.lua b/lua/pending/views.lua index b1e691e..6fd1739 100644 --- a/lua/pending/views.lua +++ b/lua/pending/views.lua @@ -13,7 +13,7 @@ local parse = require('pending.parse') ---@field id? integer ---@field due? string ---@field raw_due? string ----@field status? string +---@field status? pending.TaskStatus ---@field category? string ---@field overdue? boolean ---@field show_category? boolean diff --git a/plugin/pending.lua b/plugin/pending.lua index 084f162..394c064 100644 --- a/plugin/pending.lua +++ b/plugin/pending.lua @@ -6,18 +6,19 @@ vim.g.loaded_pending = true ---@return string[] local function edit_field_candidates() local cfg = require('pending.config').get() + local ck = cfg.category_syntax or 'cat' local dk = cfg.date_syntax or 'due' local rk = cfg.recur_syntax or 'rec' return { dk .. ':', - 'cat:', + ck .. ':', rk .. ':', '+!', '+!!', '+!!!', '-!', '-' .. dk, - '-cat', + '-' .. ck, '-' .. rk, } end @@ -135,7 +136,9 @@ local function complete_edit(arg_lead, cmd_line) return result end - local cat_prefix = arg_lead:match('^(cat:)(.*)$') + local ck = cfg.category_syntax or 'cat' + + local cat_prefix = arg_lead:match('^(' .. vim.pesc(ck) .. ':)(.*)$') if cat_prefix then local after_colon = arg_lead:sub(#cat_prefix + 1) local store = require('pending.store') @@ -192,7 +195,8 @@ end, { for _, task in ipairs(s:active_tasks()) do if task.category and not seen[task.category] then seen[task.category] = true - table.insert(candidates, 'cat:' .. task.category) + local ck = (require('pending.config').get().category_syntax or 'cat') + table.insert(candidates, ck .. ':' .. task.category) end end local filtered = {} diff --git a/spec/diff_spec.lua b/spec/diff_spec.lua index 791d7f6..355d2db 100644 --- a/spec/diff_spec.lua +++ b/spec/diff_spec.lua @@ -71,7 +71,7 @@ describe('diff', function() '/1/- [ ] Take trash out rec:weekly', } local result = diff.parse_buffer(lines) - assert.are.equal('weekly', result[2].rec) + assert.are.equal('weekly', result[2].recur) end) it('extracts rec: with completion mode', function() @@ -80,8 +80,8 @@ describe('diff', function() '/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) + assert.are.equal('daily', result[2].recur) + assert.are.equal('completion', result[2].recur_mode) end) it('inline due: token is parsed', function() diff --git a/spec/forge_spec.lua b/spec/forge_spec.lua index 84c812c..ab8d5c4 100644 --- a/spec/forge_spec.lua +++ b/spec/forge_spec.lua @@ -404,7 +404,7 @@ describe('forge parse.body integration', function() it('extracts category but keeps forge ref in description', function() local desc, meta = parse.body('Fix bug gh:user/repo#42 cat:Work') assert.equals('Fix bug gh:user/repo#42', desc) - assert.equals('Work', meta.cat) + assert.equals('Work', meta.category) end) it('leaves non-forge tokens as description', function() diff --git a/spec/parse_spec.lua b/spec/parse_spec.lua index 0820356..8f1135f 100644 --- a/spec/parse_spec.lua +++ b/spec/parse_spec.lua @@ -31,21 +31,21 @@ describe('parse', function() it('extracts category', function() local desc, meta = parse.body('Buy groceries cat:Errands') assert.are.equal('Buy groceries', desc) - assert.are.equal('Errands', meta.cat) + assert.are.equal('Errands', meta.category) end) it('extracts both due and cat', function() local desc, meta = parse.body('Buy milk due:2026-03-15 cat:Errands') assert.are.equal('Buy milk', desc) assert.are.equal('2026-03-15', meta.due) - assert.are.equal('Errands', meta.cat) + assert.are.equal('Errands', meta.category) end) it('extracts metadata in any order', function() local desc, meta = parse.body('Buy milk cat:Errands due:2026-03-15') assert.are.equal('Buy milk', desc) assert.are.equal('2026-03-15', meta.due) - assert.are.equal('Errands', meta.cat) + assert.are.equal('Errands', meta.category) end) it('stops at duplicate key', function() @@ -400,7 +400,7 @@ describe('parse', function() it('detects category prefix', function() local desc, meta = parse.command_add('School: Do homework') assert.are.equal('Do homework', desc) - assert.are.equal('School', meta.cat) + assert.are.equal('School', meta.category) end) it('ignores lowercase prefix', function() @@ -411,7 +411,7 @@ describe('parse', function() it('combines category prefix with inline metadata', function() local desc, meta = parse.command_add('School: Do homework due:2026-03-15') assert.are.equal('Do homework', desc) - assert.are.equal('School', meta.cat) + assert.are.equal('School', meta.category) assert.are.equal('2026-03-15', meta.due) end) end) diff --git a/spec/recur_spec.lua b/spec/recur_spec.lua index 53b7478..c072b7b 100644 --- a/spec/recur_spec.lua +++ b/spec/recur_spec.lua @@ -8,7 +8,7 @@ describe('recur', function() local r = recur.parse('daily') assert.are.equal('daily', r.freq) assert.are.equal(1, r.interval) - assert.is_false(r.from_completion) + assert.are.equal('scheduled', r.mode) end) it('parses weekdays', function() @@ -79,7 +79,7 @@ describe('recur', function() it('parses ! prefix as completion-based', function() local r = recur.parse('!weekly') assert.are.equal('weekly', r.freq) - assert.is_true(r.from_completion) + assert.are.equal('completion', r.mode) end) it('parses raw RRULE fragment', function()