feat(search): hybrid vector⊕full-text retrieval (RRF) with graceful fallback - #93
Merged
Conversation
Search only ever ran semantic vector search with a regex fallback — and the
vector index isn't present on the cluster, so in practice every query fell back
to a brittle exact-substring regex scan. Upgrade the retrieval layer to a
hybrid ranker that degrades cleanly at every tier:
1. HYBRID — $vectorSearch fused with Atlas $search (full-text, typo-tolerant)
via Reciprocal Rank Fusion in app code (both retrievers run concurrently).
2. VECTOR — semantic only, when the text index is absent/empty.
3. TEXT — Atlas $search only, when the gateway/vector index is absent.
4. REGEX — the existing case-insensitive scan, needs no index, last resort.
Each tier is non-throwing for the expected failure modes (no gateway, missing
index) so search never hard-fails — and it transparently improves as the
platform creates the indexes.
- `src/lib/mongo/search.ts`: rewrite around ranked-id retrievers + RRF
(`reciprocalRankFuse`, exported + unit-tested); add `atlasTextSearchIds`
($search + fuzzy), split vector into `vectorSearchIds`, keep the regex path;
add `autocompleteEventNames` ($search autocomplete → regex-prefix fallback).
`mode` gains `"hybrid"`.
- `src/app/actions/search.ts`: `SearchMode` type; new `autocompleteEventsAction`
for type-ahead.
- `src/app/search/page.tsx`: push a single active category into the server
query (index-side narrowing) instead of only client-side filtering.
- `docs/search-platform-requests.md`: the two indexes the Mukoko platform must
create to light this up (vector + full-text), plus the later 8.1 upgrade
(native $rankFusion) and automated-embeddings notes.
App-side RRF is used deliberately because native $rankFusion needs MongoDB 8.1
and the cluster is 8.0.x; swap once upgraded.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RH8QR1QYMnMHDDW62K2XSj
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Claude encountered an error —— View job I'll analyze this and get back to you. |
Reworded so no wrapped prose line starts with "+ `$search`", which markdownlint read as a plus-style list marker. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RH8QR1QYMnMHDDW62K2XSj
|
Claude encountered an error —— View job I'll analyze this and get back to you. |
The regex-metacharacter escape was duplicated in regexSearch and autocompleteEventNames; extract a single escapeRegex() helper. No behavior change — verified by search.test.ts. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RH8QR1QYMnMHDDW62K2XSj
|
Claude encountered an error —— View job I'll analyze this and get back to you. |
This was referenced Jul 25, 2026
bryanfawcett
pushed a commit
that referenced
this pull request
Jul 26, 2026
A wrapped sentence put "#93" at the start of a line, which markdownlint parsed as a missing-space ATX heading. Reword so no line begins with #. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C56AXtdhSMbh1pbouNZHAL
bryanfawcett
added a commit
that referenced
this pull request
Jul 27, 2026
…t-5 follow-ups (#101) * chore(#100): remove extracted MCP worker + admin app, rewire, add follow-ups The Mukoko Events MCP server and Mukoko Events Admin app now live in their own repos (nyuchi/mukoko-events-mcp, nyuchi/mukoko-events-admin). This removes them from the nhimbe repo and rewires everything so the public app still builds/lints/tests green. Removals: - Delete worker/ and admin/ directories. - Delete the distribution surfaces that travel with the MCP repo: plugins/, connectors/, top-level skills/, and .claude-plugin/marketplace.json. Rewiring: - package.json: drop the "admin" npm workspace; regenerate package-lock.json with no admin/worker entries. - .github/workflows/ci.yml: remove the admin (Lint & Build, Tests) and worker (Tests, Type Check) jobs; the app's Lint & Build + Frontend Tests remain. - eslint.config.mjs / tsconfig.json / .markdownlint-cli2.jsonc: drop the worker/.wrangler and admin/** ignores/excludes. - next.config.ts: unchanged — the /admin* -> ADMIN_URL redirect stays by design. - .claude/ dev tooling (launch.json, deploy skill, security-reviewer) updated to point at the new repos. Docs: - CLAUDE.md, README.md, AGENTS.md, RELEASES.md, docs/README.md rewritten where they described worker/, admin/, and the distribution surfaces — now pointing at nyuchi/mukoko-events-mcp and nyuchi/mukoko-events-admin. The shared Mongo admin-read layer (src/lib/mongo/admin.ts, admin-types.ts) stays here for the admin app to consume. Part-5 investigations (docs/issue-100-followups.md): - Palette autocomplete swap is BLOCKED on #93 (autocompleteEventsAction does not exist on this branch); documented the exact two-line change for later. - SSR-first sweep of /search, /my-events, /calendar, /profile — evaluation only, no conversions; /my-events is the best SSR candidate. - MongoDB "repeated things" — captured what was swept clean and what a DB-connected follow-up still needs. Verified: npm run lint (0 errors), npm run test:run (716 passed / 97 files), npm run build (succeeds with placeholder env). Design-token guard green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C56AXtdhSMbh1pbouNZHAL * fix(docs): satisfy markdownlint MD018 in issue-100 followups A wrapped sentence put "#93" at the start of a line, which markdownlint parsed as a missing-space ATX heading. Reword so no line begins with #. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C56AXtdhSMbh1pbouNZHAL * docs: correct the UI registry to Mzizi (mzizi.dev), not registry.mukoko.com The design-system registry is Mzizi (mzizi.dev, shadcn-compatible), resolved by the mzizi add CLI / Mzizi MCP. Rename the "Mukoko Registry" section, fix the registry.mukoko.com URL, and register @mzizi in components.json so the config matches the doc. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C56AXtdhSMbh1pbouNZHAL * docs: fix stale worker/admin and self-hosted-auth references post-#100 split Reconcile the last present-tense references to the extracted worker/ and admin/ directories (issue #100) and one removed auth dir: - RELEASES.md: worker/ "is now nhimbe-mcp" -> former dir became the Mukoko Events MCP, later extracted; drop the standalone `admin/` dir path. - docs/plans/README.md + hono-migration.md banner: worker/ extracted to nyuchi/mukoko-events-mcp (banners are archival meta; plan bodies kept). - .claude/settings.json: drop the stale *worker/* eslint-hook exclusion. - .claude/agents/security-reviewer.md: auth is the hosted AuthKit UI (/auth/hosted + /callback), not the removed self-hosted src/app/auth/signin/. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C56AXtdhSMbh1pbouNZHAL --------- Co-authored-by: Bryan Fawcett <noreply@anthropic.com>
bryanfawcett
marked this pull request as ready for review
July 28, 2026 07:31
There was a problem hiding this comment.
Claude Code Review
This repository is configured for manual code reviews. Comment @claude review for a one-time review, or @claude review always to subscribe this PR to a review on every future push.
Tip: disable this comment in your organization's Code Review settings.
|
Claude encountered an error —— View job I'll analyze this and get back to you. |
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
$vectorSearchfused with typo-tolerant Atlas$searchfull-text — that degrades gracefully at every tier, so it improves transparently as the Mukoko platform creates the search indexes.Type of Change
Context
Today search only runs vector search with a regex fallback — and the
event_vector_indexisn't present on the cluster, so in practice every query falls back to a brittle exact-substring regex scan. The headline MongoDB features (native$rankFusionhybrid, automated embeddings) need a MongoDB 8.1 upgrade (cluster is 8.0.x) and platform-created indexes — neither can ship from this repo. So this PR delivers the equivalent quality from app code and files the platform requests.Changes
src/lib/mongo/search.ts— rewritten around ranked-id retrievers + Reciprocal Rank Fusion:$vectorSearch⊕ Atlas$search(fuzzy), fused viareciprocalRankFuse(RRF-60, exported + unit-tested), both run concurrently.$searchonly (gateway/vector absent).Every tier is non-throwing for missing gateway/index. Adds
autocompleteEventNames($searchautocomplete → regex-prefix fallback).src/app/actions/search.ts—SearchMode("hybrid" | "vector" | "text"); newautocompleteEventsActionfor type-ahead.src/app/search/page.tsx— push a single active category into the server query (index-side narrowing) rather than only client-side filtering.docs/search-platform-requests.md— the two indexes the platform must create (vector + full-text), plus the later 8.1 / native$rankFusionand automated-embedding (Voyage) notes.Why app-side RRF: native
$rankFusionneeds MongoDB 8.1; swap to it once the cluster is upgraded (localized change, same result contract).Test Plan
npm run test:run— 714 passed / 97 files)search.test.ts— RRF scoring/ordering/dedupe/weights)npm run lint— 0 errors)npm run build)Platform follow-ups (see
docs/search-platform-requests.md)event_vector_indexonevents.eventEmbeddings(768-dim cosine +city/categoryfilters) — unblocks semantic search + backfill embeddings.events_text_index(Atlas Search) onevents.events— unblocks fuzzy full-text + autocomplete.$rankFusion; evaluate automated (Voyage) embeddings.🤖 Generated with Claude Code
https://claude.ai/code/session_01RH8QR1QYMnMHDDW62K2XSj
Generated by Claude Code