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<string, any>` 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.
This commit is contained in:
Barrett Ruth 2026-03-11 12:55:36 -04:00 committed by Barrett Ruth
parent 46b5d52b60
commit 939251f629
Signed by: barrett
GPG key ID: A6C96C9349D2FC81
16 changed files with 144 additions and 80 deletions

View file

@ -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()

View file

@ -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()

View file

@ -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)

View file

@ -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()