feat: add Tauri event hook and lore.db file watcher

Adds real-time updates when lore syncs new GitLab data by watching
the lore.db file for changes.

React hook (src/hooks/useTauriEvents.ts):
- useTauriEvent(): Subscribe to a single Tauri event with auto-cleanup
- useTauriEvents(): Subscribe to multiple events with a handler map
- Typed payloads for each event type:
  - global-shortcut-triggered: toggle-window | quick-capture
  - lore-data-changed: void (refresh trigger)
  - sync-status: started | completed | failed
  - error-notification: code + message

Rust watcher (src-tauri/src/watcher.rs):
- Watches lore's data directory for lore.db modifications
- Uses notify crate with 2-second poll interval
- Emits "lore-data-changed" event to frontend on file change
- Handles atomic writes by watching parent directory
- Gracefully handles missing lore.db (logs warning, skips watcher)

Test coverage:
- Hook subscription and cleanup behavior
- Focus store test fix: clear localStorage before each test

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
teernisse
2026-02-26 09:55:10 -05:00
parent df26eab361
commit 7404acdfb4
4 changed files with 333 additions and 0 deletions

View File

@@ -4,6 +4,8 @@ import { makeFocusItem } from "../helpers/fixtures";
describe("useFocusStore", () => {
beforeEach(() => {
// Clear persisted state before each test
localStorage.clear();
// Reset store between tests
useFocusStore.setState({
current: null,
@@ -189,4 +191,34 @@ describe("useFocusStore", () => {
expect(useFocusStore.getState().error).toBe("something broke");
});
});
describe("persistence", () => {
it("persists current and queue to localStorage", () => {
const items = [
makeFocusItem({ id: "a", title: "First" }),
makeFocusItem({ id: "b", title: "Second" }),
];
useFocusStore.getState().setItems(items);
const stored = localStorage.getItem("mc-focus-store");
expect(stored).not.toBeNull();
const parsed = JSON.parse(stored!);
expect(parsed.state.current.id).toBe("a");
expect(parsed.state.queue).toHaveLength(1);
expect(parsed.state.queue[0].id).toBe("b");
});
it("does not persist isLoading or error", () => {
useFocusStore.setState({ isLoading: true, error: "oops" });
const stored = localStorage.getItem("mc-focus-store");
expect(stored).not.toBeNull();
const parsed = JSON.parse(stored!);
expect(parsed.state).not.toHaveProperty("isLoading");
expect(parsed.state).not.toHaveProperty("error");
});
});
});