Skip to content

feat(recruiting): live Exa search round-trip + Settings key UI (FHR-72)#95

Merged
matthewod11-stack merged 1 commit into
mainfrom
matthew/fhr-72-recruit-s03-round-trip-one-live-exa-search-to-the-ui
May 26, 2026
Merged

feat(recruiting): live Exa search round-trip + Settings key UI (FHR-72)#95
matthewod11-stack merged 1 commit into
mainfrom
matthew/fhr-72-recruit-s03-round-trip-one-live-exa-search-to-the-ui

Conversation

@matthewod11-stack
Copy link
Copy Markdown
Owner

Summary

  • Closes Recruit MS1 — first demo-able state of the module. Type a query in the Recruit tab → raw Exa hits on screen, BYOK key from macOS Keychain.
  • New recruiting/adapters/exa.rs (typed Exa wire format + 2 fixture tests); recruiting_search_exa Tauri command with a tagged-enum RecruitingSearchError the frontend pattern-matches on.
  • 3 Exa-specific key-mgmt commands bypass the LLM-only provider-registry gate in commands/api_keys.rs while sharing keyring storage (account exa_api_key). Same pattern future Hunter/GitHub/X adapters should follow.
  • New ExaKeyInput Settings component (focused on data-source keys, no PROVIDER_META coupling). RecruitingView gets a 5-state UI with a missing-key banner that deep-links to Settings.
  • QoL: pretauri:dev npm-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 ignored
  • npm run type-check: clean
  • Live: search "engineer" → 10 raw Exa hits rendered as cards (title/url/author/date/score)
  • Live: missing-key path → banner "Recruiting needs your Exa API key" → "Add your Exa key in Settings →" → Settings opens → paste UUID → Save → "Configured" → re-run search → hits render
  • RECRUITING_ENABLED flag flipped back to false before commit (matches main; section tree-shakes out)

🤖 Generated with Claude Code

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>
Copilot AI review requested due to automatic review settings May 26, 2026 16:41
@matthewod11-stack matthewod11-stack merged commit 2eaaf93 into main May 26, 2026
6 checks passed
@matthewod11-stack matthewod11-stack deleted the matthew/fhr-72-recruit-s03-round-trip-one-live-exa-search-to-the-ui branch May 26, 2026 16:41
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 /search adapter (with deserialization fixture tests) and a recruiting_search_exa Tauri 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 RecruitingView UI (idle/loading/success/missing-key banner/soft error) and wires a Settings deep-link; adds a pretauri:dev hook 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 exaexa_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?;
Comment thread package.json
"preview": "vite preview",
"type-check": "tsc --noEmit",
"tauri": "tauri",
"pretauri:dev": "kill -9 $(lsof -ti:1420 -sTCP:LISTEN) 2>/dev/null || true",
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants