diff --git a/src/tui/controller.ts b/src/tui/controller.ts index 9a0e862..57f1ffe 100644 --- a/src/tui/controller.ts +++ b/src/tui/controller.ts @@ -2042,6 +2042,7 @@ export class TuiController { const REFRESH_DEBOUNCE_MS = 300; let refreshTimer: ReturnType | null = null; let refreshFallbackIndex: number | null = null; + // Watcher for database directory changes. let dataWatcher: fs.FSWatcher | null = null; let isShuttingDown = false; @@ -2067,6 +2068,12 @@ export class TuiController { const dataDir = pathImpl.dirname(dataPath); const dataFile = pathImpl.basename(dataPath); try { + // Initialize lastJsonlMtime from the current JSONL export if present so + // subsequent watch events can compare against a known baseline. If the + // stat fails, leave lastJsonlMtime as null which will cause watch events + // to be ignored until a successful stat occurs. + // No longer consult a JSONL mtime for watch decisions; always refresh + // on observed database directory events (debounced). // Watch for changes to either the main DB file or the WAL file. // In SQLite WAL mode, changes are written to the -wal file first, // so we need to watch both files to detect all database changes. @@ -2080,6 +2087,20 @@ export class TuiController { watchDebounce = setTimeout(() => { watchDebounce = null; const selectedIndex = typeof list.selected === 'number' ? (list.selected as number) : 0; + // If the watcher provided a specific filename (eg. 'worklog.db' or + // 'worklog.db-wal') then refresh unconditionally — the event + // directly refers to the database file. When filename is undefined + // we only refresh when the transient JSONL export mtime changed to + // avoid unnecessary reloads caused by unrelated directory changes. + if (filename) { + scheduleRefreshFromDatabase(selectedIndex); + return; + } + // Always refresh on debounced directory events. The watcher may + // provide a filename for the DB or WAL; when it does we refresh + // immediately. When filename is undefined (some platforms) we also + // refresh — we no longer rely on a JSONL export mtime to gate + // updates. scheduleRefreshFromDatabase(selectedIndex); }, 75); }); diff --git a/test/tui-integration.test.ts b/test/tui-integration.test.ts index 3877710..75619a6 100644 --- a/test/tui-integration.test.ts +++ b/test/tui-integration.test.ts @@ -585,20 +585,24 @@ describe('TUI integration: style preservation', () => { const onWatch = watchCallbacks[0]; const baselineCalls = listMock.mock.calls.length; + // We no longer consult a JSONL mtime for watch decisions; every + // debounced directory event should schedule a refresh. Verify that + // each debounced callback results in an additional db.list() call. onWatch('change', undefined); await vi.advanceTimersByTimeAsync(400); - expect(listMock.mock.calls.length).toBe(baselineCalls); + expect(listMock.mock.calls.length).toBeGreaterThan(baselineCalls); + const afterFirstCalls = listMock.mock.calls.length; dataMtimeMs = 2000; onWatch('change', undefined); await vi.advanceTimersByTimeAsync(400); - expect(listMock.mock.calls.length).toBeGreaterThan(baselineCalls); + expect(listMock.mock.calls.length).toBeGreaterThan(afterFirstCalls); - const afterChangedMtimeCalls = listMock.mock.calls.length; + const afterSecondCalls = listMock.mock.calls.length; mtimeReadError = true; onWatch('change', undefined); await vi.advanceTimersByTimeAsync(400); - expect(listMock.mock.calls.length).toBe(afterChangedMtimeCalls); + expect(listMock.mock.calls.length).toBeGreaterThan(afterSecondCalls); vi.useRealTimers(); }); diff --git a/tmux.windows.conf.yaml b/tmux.windows.conf.yaml new file mode 100644 index 0000000..151d492 --- /dev/null +++ b/tmux.windows.conf.yaml @@ -0,0 +1,14 @@ +session_name: Dev +windows: + - name: "ContextHub" + cwd: "~/projects/ContextHub" + panes: + - cmd: "opencode -c" + - cmd: "wl tui" + split: + dir: vertical + size_pct: 50 + - cmd: "clear" + split: + dir: horizontal + size_pct: 30