feat(recruiting): live Exa search round-trip + Settings key UI (FHR-72)#95
Merged
matthewod11-stack merged 1 commit intoMay 26, 2026
Conversation
Closes Recruit MS1 — first demo-able state of the module. Type a query in
the Recruit tab → raw Exa hits on screen. BYOK Exa key from macOS Keychain.
- New adapter layer: recruiting/adapters/{mod,exa}.rs with typed Exa wire
format (ExaSearchResponse / ExaHit, Option<T> for all but id+url) + 2
fixture-based deserialization tests.
- New Tauri commands: recruiting_search_exa returns a tagged-enum
RecruitingSearchError the frontend pattern-matches on (MissingKey /
InvalidKey / RateLimit / Network / ExaApi / Internal). Plus 3 Exa-
specific key-mgmt commands (recruiting_has/store/delete_exa_key) that
bypass the LLM-only provider-registry gate in commands/api_keys.rs
while sharing the same keyring storage (account: exa_api_key).
- RecruitingView: 5-state UI (idle/loading/success/error/missingKey)
with hit cards (title/url/author/date/score) and a missing-key banner
that deep-links to Settings.
- ExaKeyInput: focused data-source key input with UUID format validation;
sibling of ApiKeyInput, no PROVIDER_META coupling. Pattern for future
Hunter / GitHub / X keys.
- Settings: new Recruiting section gated on RECRUITING_ENABLED so it
tree-shakes out of flag-off bundles.
- TS: Result<{ok,data}|{ok,error}> command wrapper for branch-on-error
UIs; ExaHit/ExaSearchResponse/RecruitingSearchError mirror Rust.
- QoL: pretauri:dev npm-lifecycle hook clears orphan Vite processes on
port 1420 before each launch (was a recurring footgun on abnormal
Tauri exits).
- keyring.rs: 1-line assertion in test_keychain_account_for_provider
locks down the exa_api_key account name as a contract.
Verified: cargo test --lib 482 passing (was 480, +2 from exa fixtures);
npm run type-check clean; live round-trip confirmed end-to-end including
missing-key → banner → Settings deep-link → save → re-search loop.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Adds the first end-to-end “Recruit” module demo flow by round-tripping a live Exa search from the UI through new Tauri commands into a typed Rust Exa adapter, plus a Settings UI for storing an Exa BYOK key in the system keychain.
Changes:
- Introduces a typed Rust Exa
/searchadapter (with deserialization fixture tests) and arecruiting_search_exaTauri command that returns a tagged error enum for frontend branching. - Adds recruiting-namespaced Exa API key management commands and a dedicated Settings component (
ExaKeyInput) for storing/removing the Exa key. - Implements a 5-state
RecruitingViewUI (idle/loading/success/missing-key banner/soft error) and wires a Settings deep-link; adds apretauri:devhook to clear port 1420.
Reviewed changes
Copilot reviewed 13 out of 13 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| src/lib/types.ts | Adds Exa wire/response types and discriminated recruiting search error/result unions for typed UI handling. |
| src/lib/tauri-commands.ts | Adds recruitingSearchExa wrapper (Result-style) and Exa key management invoke wrappers. |
| src/components/settings/SettingsPanel.tsx | Adds a Recruiting Settings section (gated by RECRUITING_ENABLED) with the new Exa key input. |
| src/components/settings/ExaKeyInput.tsx | New Settings UI for storing/removing Exa key with lightweight UUID-format validation. |
| src/components/recruiting/RecruitingView.tsx | Implements live Exa search input + result rendering, missing-key banner, and soft error display. |
| src/App.tsx | Passes a Settings-open callback into RecruitingView for deep-linking from the missing-key banner. |
| src-tauri/src/recruiting/mod.rs | Exposes adapters and adds a shared EXA_PROVIDER_ID constant for keyring lookups. |
| src-tauri/src/recruiting/adapters/mod.rs | New adapter module namespace for recruiting data sources. |
| src-tauri/src/recruiting/adapters/exa.rs | New Exa adapter with typed request/response structs, error mapping, and fixture deserialization tests. |
| src-tauri/src/lib.rs | Registers new recruiting commands (search + Exa key management) with the Tauri invoke handler. |
| src-tauri/src/keyring.rs | Extends provider→keychain-account mapping test coverage to include exa → exa_api_key. |
| src-tauri/src/commands/recruiting.rs | Adds recruiting_search_exa command + tagged error enum and recruiting-namespaced Exa key commands. |
| package.json | Adds pretauri:dev hook to kill any process listening on port 1420 before tauri:dev. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+10
to
+13
| // Storage layer is shared: this uses the existing generic | ||
| // `storeProviderApiKey('exa', ...)` / `hasProviderApiKey('exa')` / | ||
| // `deleteProviderApiKey('exa')` commands, which write under Keychain account | ||
| // `exa_api_key` (locked down by a unit test in `src-tauri/src/keyring.rs`). |
Comment on lines
+121
to
+127
| let response = Client::new() | ||
| .post(EXA_SEARCH_URL) | ||
| .header("x-api-key", api_key) | ||
| .header("content-type", "application/json") | ||
| .json(&body) | ||
| .send() | ||
| .await?; |
| "preview": "vite preview", | ||
| "type-check": "tsc --noEmit", | ||
| "tauri": "tauri", | ||
| "pretauri:dev": "kill -9 $(lsof -ti:1420 -sTCP:LISTEN) 2>/dev/null || true", |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
recruiting/adapters/exa.rs(typed Exa wire format + 2 fixture tests);recruiting_search_exaTauri command with a tagged-enumRecruitingSearchErrorthe frontend pattern-matches on.commands/api_keys.rswhile sharing keyring storage (accountexa_api_key). Same pattern future Hunter/GitHub/X adapters should follow.ExaKeyInputSettings component (focused on data-source keys, noPROVIDER_METAcoupling).RecruitingViewgets a 5-state UI with a missing-key banner that deep-links to Settings.pretauri:devnpm-lifecycle hook clears orphan Vite processes on port 1420 before launch.Closes FHR-72.
Test plan
cargo test --lib: 482 passing (was 480; +2 from exa adapter deserialization fixtures), 0 failing, 1 ignorednpm run type-check: cleanRECRUITING_ENABLEDflag flipped back tofalsebefore commit (matches main; section tree-shakes out)🤖 Generated with Claude Code