cd /news/ai-tools/paper-finder-a-free-ai-powered-resea… · home topics ai-tools article
[ARTICLE · art-78006] src=dev.to ↗ pub= topic=ai-tools verified=true sentiment=· neutral

Paper Finder: A Free AI-Powered Research Tool and the 7-Second Freeze I Had to Debug

A developer built Paper Finder, a free AI-powered research tool that searches arXiv, Semantic Scholar, and Crossref and re-ranks results using a browser-based AI model. The developer debugged a 7-second input freeze caused by synchronous WASM inference blocking the main thread, and resolved it by moving the model into a Web Worker.

read2 min views1 publishedJul 29, 2026

I built Paper Finder, a free tool that searches arXiv, Semantic Scholar, and Crossref at once, then re-ranks the results by semantic relevance.

The ranking uses a small AI model that runs entirely in the browser, so there are no API costs or accounts. But it introduced a nasty bug: typing in the search box could freeze for up to seven seconds.

Paper Finder initially sorted results by year. To improve relevance, I added Xenova/all-MiniLM-L6-v2

through transformers.js. It embeds the query and each result, then ranks them using cosine similarity.

The results appeared before re-ranking began, so I assumed the AI work was safely happening "in the background."

Then a user sent me a Chrome DevTools trace showing 1,350.9 ms of input delay. My first thought was, "That can't be the AI—it's async."

I was wrong.

I used Chrome's PerformanceObserver

with the longtask

entry type, which reports tasks that block the main thread for more than 50 ms:

const observer = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    console.log(`Blocked for ${entry.duration}ms`);
  }
});

observer.observe({ entryTypes: ["longtask"] });

After clearing the browser's model cache and running a fresh search, I saw this:

{ duration: 7079, start: 13735 }

One task had blocked the main thread for 7,079 ms. During that time, the page couldn't process typing, clicks, or anything else.

await

didn't help The embedding call used await

, but that doesn't move work off the main thread. It only yields while waiting for genuinely asynchronous work, such as network requests or timers.

WASM inference is synchronous CPU work. Wrapping it in a Promise changes how you receive the result, not where the computation runs. The model initialization and inference were still competing with rendering and input on the main thread.

I moved the model into a dedicated Web Worker:

// embedder.worker.ts
self.onmessage = async (event) => {
  const extractor = await getExtractor();

  const output = await extractor(event.data.texts, {
    pooling: "mean",
    normalize: true,
  });

  self.postMessage({ vectors: /* ... */ });
};

The main thread now sends text to the worker and waits for the vectors to come back. The public interface didn't need to change—a Worker-backed Promise looks the same to callers.

After repeating the cold-cache test:

longtasks: []

Zero long tasks, and the search box remained responsive throughout.

The main lesson: await

does not mean "won't block the main thread." For client-side ML or any other CPU-heavy work, use a Web Worker from the start.

The source is on GitHub, and the live app is at paperfinder.dev.

── more in #ai-tools 4 stories · sorted by recency
── more on @paper finder 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/paper-finder-a-free-…] indexed:0 read:2min 2026-07-29 ·