Skip to content

feat: lazily page broad search buckets#108

Merged
Moskize91 merged 2 commits into
mainfrom
codex/lazy-search-bucket-cursors
Jul 8, 2026
Merged

feat: lazily page broad search buckets#108
Moskize91 merged 2 commits into
mainfrom
codex/lazy-search-bucket-cursors

Conversation

@Moskize91

@Moskize91 Moskize91 commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Replace broad search's eager full-result materialization with bucket/keyset cursor paging.
  • Prioritize chapter-title hits as bucket 0, then object hit caches, then source/summary FTS hits.
  • Avoid writing broad search pages into search_results; hydrate source text only for the current page.

Verification

  • pnpm run typecheck
  • pnpm test:run -- --runInBand
  • pnpm run lint
  • Manual check: after deleting the query cache, wg 'wikg:///Users/taozeyu/Downloads/三国演义.wikg' --query '桃园三结义' --json returned in ~2s with search_results = 0.

Cache note

If local search state behaves oddly after this change, delete the local cache directory and rerun the query:

rm -rf ~/.wikigraph/cache

The cache is rebuilt automatically.

@coderabbitai

coderabbitai Bot commented Jul 8, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: fcfd2499-f7e7-4074-b544-82dbe40269c3

📥 Commits

Reviewing files that changed from the base of the PR and between 979c93b and daccee5.

📒 Files selected for processing (4)
  • src/archive/query/archive-view.ts
  • src/archive/query/search-cache.ts
  • src/archive/search-index/search-index.ts
  • test/archive/query/archive-view.test.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • test/archive/query/archive-view.test.ts
  • src/archive/search-index/search-index.ts
  • src/archive/query/archive-view.ts
  • src/archive/query/search-cache.ts

Summary by CodeRabbit

  • New Features

    • Added bucketed, cursor-driven pagination for search results to improve how result pages continue seamlessly.
    • Improved text search paging consistency using ranked ordering with deterministic tie-breakers.
  • Bug Fixes

    • Enhanced stable pagination so successive pages are less likely to reorder results.
    • Updated search-cache behavior to better support cursor-based paging and continuation.
  • Tests

    • Adjusted pagination tests to validate cursor changes across pages and stability of returned items.

Walkthrough

This PR adds bucketed search pagination for archive search. Bucket cursors now encode versioned bucket and per-bucket keys. findArchiveObjects detects bucket cursors, skips non-bucket cache paths in bucketed mode, and paginates through bucket-specific readers. search-cache.ts adds cursor encoding/decoding, bucket page readers, cache population, and session metadata updates. search-index.ts adds rank-aware text pagination and first-seen deduplication. A test was updated for the new cursor behavior.

Sequence Diagram(s)

sequenceDiagram
  participant ArchiveView
  participant SearchCache
  participant SearchIndex
  participant SessionDB

  ArchiveView->>SearchCache: decodeBucketSearchSessionCursor(cursor)
  ArchiveView->>SearchCache: readSearchSessionMetadataForCursor(sessionId)
  SearchCache->>SessionDB: touch session and read metadata
  SessionDB-->>SearchCache: descriptor
  ArchiveView->>SearchIndex: querySearchIndex(textAfter with rank)
  SearchIndex-->>ArchiveView: ranked text hits
  ArchiveView->>SearchCache: readSearchSessionObjectBucketPage / readSearchSessionChunkBucketPage
  SearchCache-->>ArchiveView: bucket page + next cursor
Loading

Possibly related PRs

  • oomol-lab/wiki-graph#96: Introduces earlier chapter-title archive object handling that matches the new chapter-title bucket path in this PR.
  • oomol-lab/wiki-graph#100: Changes the archive search index freshness checks that this PR now uses before starting bucketed search.
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly matches the PR’s broad-search bucketed pagination changes.
Description check ✅ Passed The description is directly related to the changes and verification steps in the PR.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch codex/lazy-search-bucket-cursors

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
test/archive/query/archive-view.test.ts (1)

1473-1486: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Assertion relies on multi-page results for "Source".

expect(secondPage.nextCursor).not.toBe(firstPage.nextCursor) only meaningfully verifies advancement when the query yields more than one page. If the fixture ever produces a single page, both cursors become null and this passes for the wrong reason (or fails inconsistently). Consider also asserting firstPage.nextCursor is non-null to lock in the intended multi-page path.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/archive/query/archive-view.test.ts` around lines 1473 - 1486, The
pagination assertion in archive-view test for findArchiveObjects should
explicitly lock in the multi-page path for "Source". Update the test around
firstPage and secondPage so it asserts firstPage.nextCursor is non-null before
using it, and keep the secondPage cursor comparison only after that check. This
ensures the test is validating cursor advancement in the intended multi-page
scenario rather than passing when both cursors are null.
src/archive/query/archive-view.ts (1)

1280-1303: 🚀 Performance & Scalability | 🔵 Trivial | 🏗️ Heavy lift

populateObjectBucketCaches recomputes the full structured search on every bucket‑1 page.

readObjectBucketPage calls populateObjectBucketCaches unconditionally, which re-runs mentions.listBySurfaceTerms, a full-index querySearchIndex({ types: null, objectHitLimit: SEARCH_INDEX_FTS_HIT_LIMIT }), findEntities, and findTriples, then re-populates the cache — for every page request that lands in bucket 1. The keyset cache the population fills is meant to avoid exactly this recomputation, so paging deep into the object bucket repeatedly pays the full search cost.

Consider populating once per session (guard on whether the session's object caches are already populated) and skipping repopulation when resuming from an after cursor.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/archive/query/archive-view.ts` around lines 1280 - 1303,
`readObjectBucketPage` is always calling `populateObjectBucketCaches`, which
forces the full structured search work to rerun on every bucket-1 page. Update
the object-bucket paging flow so `populateObjectBucketCaches` is only invoked
once per session when the cache is missing, and skip repopulating when
continuing from an `after` cursor. Use the existing `session` state in
`readObjectBucketPage`/`populateObjectBucketCaches` to detect whether the object
caches are already populated before recomputing and repopulating.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/archive/query/search-cache.ts`:
- Around line 372-403: `readSearchSessionObjectBucketPage` is skipping triple
rows whenever `entityRows.length > limit`, which can drop higher-ranked triples
and desync paging. Update the object-bucket pagination flow to fetch both entity
and triple buckets for the page window, or maintain separate keyset state per
bucket so neither side is excluded early. Also align `compareObjectBucketHits`
with the SQL ordering keys used by `readSearchSessionEntityBucketRows` and
`readSearchSessionTripleBucketRows` so the `after` cursor is derived from the
same tie-break order as the database.

In `@src/archive/search-index/search-index.ts`:
- Around line 436-500: `querySearchIndex` is letting duplicate sentence hits
overwrite each other in the result `Map`, which makes the stored text `rank`
depend on tier execution order and breaks pagination in `readTextBucketPage`.
Update the deduping logic in `querySearchIndex` so a sentence keeps a stable
rank regardless of later tiers (for example, preserve the first rank seen or
otherwise merge deterministically instead of blindly using `Map.set(...)`). Make
sure the value consumed by `textAfter` and `isAfterTextKey` stays consistent
across pages.

---

Nitpick comments:
In `@src/archive/query/archive-view.ts`:
- Around line 1280-1303: `readObjectBucketPage` is always calling
`populateObjectBucketCaches`, which forces the full structured search work to
rerun on every bucket-1 page. Update the object-bucket paging flow so
`populateObjectBucketCaches` is only invoked once per session when the cache is
missing, and skip repopulating when continuing from an `after` cursor. Use the
existing `session` state in `readObjectBucketPage`/`populateObjectBucketCaches`
to detect whether the object caches are already populated before recomputing and
repopulating.

In `@test/archive/query/archive-view.test.ts`:
- Around line 1473-1486: The pagination assertion in archive-view test for
findArchiveObjects should explicitly lock in the multi-page path for "Source".
Update the test around firstPage and secondPage so it asserts
firstPage.nextCursor is non-null before using it, and keep the secondPage cursor
comparison only after that check. This ensures the test is validating cursor
advancement in the intended multi-page scenario rather than passing when both
cursors are null.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 65a92f43-bd89-41aa-aed3-724501872860

📥 Commits

Reviewing files that changed from the base of the PR and between 223050c and 56fa579.

📒 Files selected for processing (4)
  • src/archive/query/archive-view.ts
  • src/archive/query/search-cache.ts
  • src/archive/search-index/search-index.ts
  • test/archive/query/archive-view.test.ts

Comment thread src/archive/query/search-cache.ts
Comment thread src/archive/search-index/search-index.ts
@Moskize91 Moskize91 force-pushed the codex/lazy-search-bucket-cursors branch from 979c93b to daccee5 Compare July 8, 2026 08:43
@Moskize91 Moskize91 merged commit b0e1639 into main Jul 8, 2026
3 checks passed
@Moskize91 Moskize91 deleted the codex/lazy-search-bucket-cursors branch July 8, 2026 08:47
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.

1 participant