feat: tier 3 polish — indexing status surfaces + citation reliability #2

Merged
hegdeatri merged 11 commits from feature/tier3-polish into master 2026-05-18 18:23:59 +01:00
Owner

Summary

Two Tier 3 polish slices on one branch.

Indexing status surfaces (Slice A):

  • last_index_event: Arc<std::sync::Mutex<Option<IndexEvent>>> latched on AppState; updated by send_event, cleared by rotate_index_cancel.
  • New GET /index/state returns the latched event (or 204).
  • IndexerProvider fetches it on mount / vault switch — webview reload mid-indexing now resumes the live phase instead of dropping to idle.
  • VaultsRight gains a one-line index strip; NavRail gains an amber-pulse / red-dot badge on the Indexer item (visible from any section).

Citation reliability (Slice B):

  • /notes/lookup resolves each title via three strategies: case-insensitive exact title → FTS5 title:"..." phrase → LIKE %term%. Each hit carries match_quality ∈ {exact, fts, like} and query (the original wikilink target, so fuzzy hits resolve back to the user input).
  • Response shape changes from Vec<note> to { notes: [...], unresolved: [...] }. Missing titles surface as synthetic FileReferences with matchQuality: "missing".
  • Wikilink renders three states: clean (exact), dashed-fuzzy (FTS/LIKE, with "Closest match" hint), strike-through-missing (broken, aria-disabled, cursor: not-allowed).
  • useChat now splits [[Note|Alias]] and only looks up the target half.

Spec'd citation_warning SSE event folded into the lookup response — the existing post-stream lookupNotes call carries the same info. Rationale in the plan.

Implementation plan: docs/superpowers/plans/2026-05-18-tier3-polish.md.

Test plan

  • Backend: `cd src-tauri && cargo test --lib` — 65 passing (3 new: index_event serde, lookup response serde, MatchQuality snake_case).
  • Backend: `cargo clippy --lib -- -D warnings` — clean.
  • Frontend: `bun run lint` + `bun run build` — clean.
  • Manual: `bunx tauri dev`; trigger a reindex; reload the webview mid-flight → live progress strip + nav-rail badge come back. Switch vaults → badge clears + VaultsRight strip clears.
  • Manual: ask the agent something that produces `ExactTitle`, `partial title`, and `GarbageTitle` wikilinks → expect clean, dashed-amber, strike-through respectively.
## Summary Two Tier 3 polish slices on one branch. **Indexing status surfaces (Slice A):** - `last_index_event: Arc<std::sync::Mutex<Option<IndexEvent>>>` latched on `AppState`; updated by `send_event`, cleared by `rotate_index_cancel`. - New `GET /index/state` returns the latched event (or 204). - `IndexerProvider` fetches it on mount / vault switch — webview reload mid-indexing now resumes the live phase instead of dropping to idle. - `VaultsRight` gains a one-line index strip; `NavRail` gains an amber-pulse / red-dot badge on the Indexer item (visible from any section). **Citation reliability (Slice B):** - `/notes/lookup` resolves each title via three strategies: case-insensitive exact title → FTS5 `title:"..."` phrase → `LIKE %term%`. Each hit carries `match_quality` ∈ {exact, fts, like} and `query` (the original wikilink target, so fuzzy hits resolve back to the user input). - Response shape changes from `Vec<note>` to `{ notes: [...], unresolved: [...] }`. Missing titles surface as synthetic `FileReference`s with `matchQuality: "missing"`. - `Wikilink` renders three states: clean (exact), dashed-fuzzy (FTS/LIKE, with "Closest match" hint), strike-through-missing (broken, `aria-disabled`, `cursor: not-allowed`). - `useChat` now splits `[[Note|Alias]]` and only looks up the target half. Spec'd `citation_warning` SSE event folded into the lookup response — the existing post-stream `lookupNotes` call carries the same info. Rationale in the plan. Implementation plan: `docs/superpowers/plans/2026-05-18-tier3-polish.md`. ## Test plan - [ ] Backend: \`cd src-tauri && cargo test --lib\` — 65 passing (3 new: index_event serde, lookup response serde, MatchQuality snake_case). - [ ] Backend: \`cargo clippy --lib -- -D warnings\` — clean. - [ ] Frontend: \`bun run lint\` + \`bun run build\` — clean. - [ ] Manual: \`bunx tauri dev\`; trigger a reindex; reload the webview mid-flight → live progress strip + nav-rail badge come back. Switch vaults → badge clears + VaultsRight strip clears. - [ ] Manual: ask the agent something that produces \`[[ExactTitle]]\`, \`[[partial title]]\`, and \`[[GarbageTitle]]\` wikilinks → expect clean, dashed-amber, strike-through respectively.
Replaces the stale "awaiting merge" line with the merge commit (38e8aba)
and clears the now-resolved missing_transmute_annotations bullet (fixed
in 7c9eb23).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two slices on one branch:
- Slice A: latched IndexEvent in AppState + GET /index/state + on-mount
  rehydration + VaultsRight strip + NavRail badge
- Slice B: fuzzy /notes/lookup (case-insensitive exact -> FTS5 -> LIKE)
  with match_quality + unresolved titles + broken-link Wikilink

Post-review revisions: std::sync::Mutex latch (send_event runs inside
db.write blocking closures; tokio RwLock try_write can wedge), thread
query through lookup response (fuzzy hits filename != input target),
collapse frontend bundle into one commit to keep lint green per-revision,
rusqlite .optional() over .ok() to avoid swallowing real errors.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Threads a `last_index_event: Arc<std::sync::Mutex<Option<IndexEvent>>>`
through `index_vault`, `diff_and_update_vault`, the manual reindex
spawn, the lifecycle catch-up, and the watcher. `send_event` writes
the event to the latch before broadcasting; rotate_index_cancel clears
it on vault switch/add. `std::sync::Mutex` (not tokio's RwLock) so it
can be touched from inside `db.write` blocking closures.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Returns the latched IndexEvent or 204 No Content. Frontend reads
this on mount + on vault switch so a webview reload re-attaches to
the live indexing phase instead of dropping back to "idle".

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds fetchIndexState() + a mount/vault-switch effect in
IndexerProvider that reads the backend's latched IndexEvent and
fills state when the SSE stream hasn't already produced something
fresher. Webview reloads mid-indexing now resume the live phase
instead of dropping to "idle".

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pulls indexProgress from useIndexer and renders a phase line +
detail metric-card alongside the existing Path / Stats cards. Idle
vaults look the same as before; active or recently-finished indexing
shows the live state.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Active phases (scanning / indexing / embedding) show an amber pulse;
errors show a solid red dot. Visible from any section so the user
sees ongoing index work even when the live progress card isn't on
screen.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three-tier resolver per title:
  1. case-insensitive exact title match  -> match_quality: "exact"
  2. FTS5 title-column phrase match      -> match_quality: "fts"
  3. LIKE %term% fallback                -> match_quality: "like"

Response shape: { notes: [...], unresolved: [...] }. Each note
carries `match_quality` AND `query` (the original input title) so
the frontend Wikilink component can match by user input rather than
by resolved DB filename. Misses go in `unresolved`.

note_row uses rusqlite .optional() so only QueryReturnedNoRows
becomes None — real errors propagate as AppError::Internal instead
of being silently swallowed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three files in lockstep so no intermediate revision has a broken
lint/build:

- chat-client.ts: lookupNotes returns NoteLookupResponse, new
  NoteLookupHit type carries match_quality + query (the original
  wikilink target)
- mock-data.ts: FileReference gains optional query + matchQuality
  (exact | fts | like | missing)
- useChat.tsx: split wikilink target from |alias before lookup;
  map hits onto FileReference with matchQuality; synthesize a
  "missing" FileReference for each unresolved title so the Wikilink
  component can render a broken-link affordance

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Resolution order: query-based match first (canonical, works for any
match_quality), filename match as legacy backstop, then missing
placeholder match. Renders three states:

- exact   : unchanged
- fuzzy   : dashed border + amber icon + "Closest match: ..." title
- missing : strikethrough, muted, cursor not-allowed, no preview /
            Obsidian handoff; aria-disabled for screen readers

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Both items 1 and 2 (indexing status surfaces + citation reliability)
landed on feature/tier3-polish. Item 3 (test infrastructure
baseline) remains open per scope.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
hegdeatri merged commit 32af38abc9 into master 2026-05-18 18:23:59 +01:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
hegdeatri/pkma-rs!2
No description provided.