steps 8+9: worklog + FTS5 search

STEP 8 — Worklog:
- Migration 006: worklog_entries table (node_id, date, minutes,
  approximate, billable, summary, details)
- WorklogService: Add, Get, Update, Delete, ListByNode, SumMinutes,
  Report (text report generator with total time)
- CLI: verstak log add/list/report  (verstak log --help for usage)
- GUI tab: entries list with date/time/approx, add form with
  minutes+text+approx checkbox, total minutes counter

STEP 9 — FTS5 Search:
- FTS5 virtual table created lazily by search.Rebuild()
  (works with/without FTS5 compiled in — graceful fallback)
- SearchService: Index, Remove, Rebuild, Search (with FTS5 MATCH)
- CLI: verstak index rebuild — builds search index from node titles
- GUI search bar uses /api/search?q= (FTS5 when available,
  fallback to LIKE on node titles)

Acceptance: go build ./... pass, go test ./... pass (all packages).
This commit is contained in:
2026-05-31 02:25:25 +08:00
parent dae53fcbba
commit d6f7f1a9b8
11 changed files with 969 additions and 16 deletions
@@ -0,0 +1,22 @@
package storage
// migration006 — worklog_entries table.
const migration006 = `
CREATE TABLE IF NOT EXISTS worklog_entries (
id TEXT PRIMARY KEY,
node_id TEXT NOT NULL REFERENCES nodes(id),
started_at TEXT NULL,
ended_at TEXT NULL,
date TEXT NOT NULL,
minutes INTEGER NULL,
approximate INTEGER NOT NULL DEFAULT 1,
billable INTEGER NOT NULL DEFAULT 0,
summary TEXT NOT NULL,
details TEXT NULL,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_worklog_node ON worklog_entries(node_id);
CREATE INDEX IF NOT EXISTS idx_worklog_date ON worklog_entries(date);
`
@@ -0,0 +1,17 @@
package storage
// migration007 — FTS5 search index.
// Requires SQLite compiled with FTS5 (go build -tags sqlite_fts5).
// The migration is wrapped in a savepoint so it can be skipped on
// SQLite builds without FTS5 (the search_index table simply won't exist,
// and search falls back to LIKE on node titles).
const migration007 = `
CREATE VIRTUAL TABLE IF NOT EXISTS search_index USING fts5(
node_id UNINDEXED,
title,
content,
path,
tags,
type
);
`
+3 -1
View File
@@ -62,7 +62,9 @@ var migrationFiles = map[int]string{
3: migration003,
4: migration004,
5: migration005,
// 6: migration006, etc.
6: migration006,
// 7: migration007 (FTS5) — created lazily by search.Rebuild()
// 8: migration008, etc.
}
func (db *DB) runInitialSchema() error {