LanternSupport Widget (RAG)
What it does for you
A help chat on your site that only answers from your own info.
An embeddable support widget that answers only from your own docs with citations, says I don't know instead of guessing, and exports as one self-contained script.

Who it's for
Small businesses that answer the same questions all day, like hours, pricing, and how to get started. It shows where each answer came from and says so when it doesn't know.
Screenshots

In more detail
Lantern is a chat bubble for a website that answers visitors' questions from the business's own pages, shows where each answer came from, and admits when it doesn't know. A small business like a home care agency gets the same questions all day (hours, pricing, how to start). Generic chatbots guess. A support bot is only worth having if it sticks to what the business actually wrote and hands off to a person when it can't answer.
Honest limits
Claude mode was never run against a real key. It's covered only by faked fetch tests. The retrieval is keyword-based with synonyms, so heavily paraphrased questions can still miss. The demo is a fictional home care agency sample.
How I built it
This was on the Claude Code side of the split file on September 23, the day Claude Code and Codex ran in tandem. It was one directed Claude Code session of about half an hour.
I set the constraints: never guess, cite everything, keep doc text fenced, block private addresses and re-check the resolved IP. Claude turned those into tests first, then code. The pure core (tokenizer, chunker, BM25, synonym expansion, answerer, prompt builder, SSRF guard, exporter) sits under 254 assertions, including a hand-computed BM25 value and prompt-injection fences. It's plain ES modules with no framework and no build step, plus a small Node server for static files and the guarded URL importer.
Docs come in by paste, file drop, or a URL and sitemap import. The default mode builds an extractive answer with citations. An optional Claude mode sends only the top 4 chunks, fenced as untrusted data. The Export tab produces one self-contained widget.js with the index inside, and an analytics view lists the top unanswered questions so the owner knows what to write next.
What I learned
- A score threshold alone can't say "I don't know." "Do you help with pets?" still scored well on "help." Adding query coverage (how much of the question's rarest words the top chunk actually contains, with synonyms earning partial credit) is what made declines trustworthy.
- SSRF checks belong in the socket lookup. Passing a custom
lookuptohttp.getmeans the IP we checked is the IP we connect to, which closes the DNS rebinding gap. - Fencing doc text isn't just wrapping it in tags. Text that contains
</source>or</context>gets neutralized, and a test counts the fences. - Building the exported widget from the core functions with
Function.toStringmeant avmtest could prove the offline widget answers exactly like the console. - Shell heredocs in this environment turned
\uescapes into literal characters and broke a regex, so anything with escapes got written directly.
What carried forward
Lantern is the first of three RAG builds done back to back that day. Folio (65) took the retrieval further with local embeddings, and Binder (66) was deliberately scoped as a team wiki, not a support widget like this one. All three share the same habits: a pure tested core, extractive cited answers, an honest decline, and fenced untrusted context for the optional Claude mode. Driving headless Chrome over the DevTools protocol from a roughly 40-line Node script turned out to be enough to click through a real UI without Playwright, and that trick kept showing up.
If I took it further
I'd add a small embedding reranker on top of BM25 for paraphrased questions, test Claude mode with a real key, and track which doc each thumbs-down points to so the report says which page to fix.
#064 Built with Claude Code September 23, 2026 254 automated checks