feat: add annotations, undo system, and schema updates

Add annotations as JSON column on tasks table with Annotate/Denotate
methods and CLI commands. Add undo system backed by change_log with
lightweight undo_stack table (capped at 10 entries). All mutating CLI
commands (add, done, delete, modify, start, stop) now record undo
entries. Undo restores prior task state from change_log data.

Schema changes (in v1 migration):
- annotations TEXT column on tasks
- undo_stack table
- annotations field in change_log triggers

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-19 13:54:58 +01:00
parent 6fb8a40a43
commit 7aaaa86a0a
16 changed files with 753 additions and 76 deletions
+19 -7
View File
@@ -112,7 +112,8 @@ func runMigrations() error {
recurrence_duration INTEGER,
parent_uuid TEXT,
annotations TEXT DEFAULT NULL,
FOREIGN KEY (parent_uuid) REFERENCES tasks(uuid) ON DELETE CASCADE
);
@@ -209,6 +210,15 @@ func runMigrations() error {
CREATE INDEX idx_refresh_tokens_hash ON refresh_tokens(token_hash);
CREATE INDEX idx_refresh_tokens_user ON refresh_tokens(user_id);
-- Undo stack (local-only, references change_log entries)
CREATE TABLE undo_stack (
id INTEGER PRIMARY KEY AUTOINCREMENT,
created_at INTEGER NOT NULL,
op_type TEXT NOT NULL,
task_uuid TEXT NOT NULL,
change_log_id INTEGER NOT NULL
);
-- Triggers to populate change_log
CREATE TRIGGER track_task_create AFTER INSERT ON tasks
BEGIN
@@ -241,10 +251,11 @@ func runMigrations() error {
CASE WHEN NEW.until_date IS NOT NULL THEN 'until: ' || NEW.until_date || CHAR(10) ELSE '' END ||
CASE WHEN NEW.recurrence_duration IS NOT NULL THEN 'recurrence: ' || NEW.recurrence_duration || CHAR(10) ELSE '' END ||
CASE WHEN NEW.parent_uuid IS NOT NULL THEN 'parent_uuid: ' || NEW.parent_uuid || CHAR(10) ELSE '' END ||
(SELECT CASE WHEN COUNT(*) > 0
CASE WHEN NEW.annotations IS NOT NULL THEN 'annotations: ' || NEW.annotations || CHAR(10) ELSE '' END ||
(SELECT CASE WHEN COUNT(*) > 0
THEN 'tags: ' || GROUP_CONCAT(tag, ',') || CHAR(10)
ELSE ''
END
ELSE ''
END
FROM (SELECT tag FROM tags WHERE task_id = NEW.id ORDER BY tag))
);
END;
@@ -280,10 +291,11 @@ func runMigrations() error {
CASE WHEN NEW.until_date IS NOT NULL THEN 'until: ' || NEW.until_date || CHAR(10) ELSE '' END ||
CASE WHEN NEW.recurrence_duration IS NOT NULL THEN 'recurrence: ' || NEW.recurrence_duration || CHAR(10) ELSE '' END ||
CASE WHEN NEW.parent_uuid IS NOT NULL THEN 'parent_uuid: ' || NEW.parent_uuid || CHAR(10) ELSE '' END ||
(SELECT CASE WHEN COUNT(*) > 0
CASE WHEN NEW.annotations IS NOT NULL THEN 'annotations: ' || NEW.annotations || CHAR(10) ELSE '' END ||
(SELECT CASE WHEN COUNT(*) > 0
THEN 'tags: ' || GROUP_CONCAT(tag, ',') || CHAR(10)
ELSE ''
END
ELSE ''
END
FROM (SELECT tag FROM tags WHERE task_id = NEW.id ORDER BY tag))
);
END;