Knowledge
Stash
I bookmarked things on every platform and could never find them again — often not even which app they were in. So I pooled them into one searchable place.
- Year
- 2025~now
- Role
- Solo — extension, web app, retrieval pipeline
- Status
- Live — Chrome extension and web app
The problem
Useful things arrive from everywhere now — Instagram, LinkedIn, YouTube, X, a random blog — so I got into the habit of screenshotting anything worth keeping. The habit worked. The retrieval did not. A screenshot is a dead end: it is pixels in a folder, and once there are a few thousand of them, saving something is indistinguishable from losing it.
Stash makes a capture into a record instead of an image. It runs OCR over the picture, analyses the text, and pulls the page's own metadata, then uses that to enrich, tag and categorise the item automatically. Everything saved is ingested into a retrieval pipeline, so you can ask your own library a question in plain language instead of scrolling it.
The name comes from `git stash` — set something aside without losing your place, and come back for it when you are ready.
How it changed
Each version below exists because the one before it was wrong about something specific.
- v1
Put the whole library in the prompt
- Why
- The first version of asking your library a question was the simplest one that could work.
- Change
- Inject the full set of captures into the model's context and let a long-context model do the retrieval itself.
- Result
- Excellent answers, and completely unaffordable past a few hundred items. Accuracy was never the constraint — cost and context length were, and both scale with exactly the thing the product is designed to accumulate.
- v2
Hybrid retrieval that switches on library size
- Why
- Small libraries genuinely are best served by full-context injection. Large ones cannot be. The product needs both without asking the user which.
- Change
- Built a hybrid RAG layer that switches between long-context injection and semantic embedding search based on library size, plus four server-enforced defence layers — rate limiting, quota, per-request caps and scope enforcement — so the assistant stays bounded and stays inside the user's own collage.
- Result
- Retrieval quality holds as the library grows, and cost stops tracking library size. The defence layers exist because an open chat box over an LLM is otherwise a free ChatGPT with my API key attached.

The same library, dense view.
From captures to something you own
The goal was never a better bookmark manager. It is that the gap between data and knowledge is retrieval — a capture you cannot find is worth exactly as much as one you never made.

Built with
- React
- TypeScript
- Chrome Extension
- Supabase
- OpenAI API
- OCR
- RAG
Deep dives
Individual pieces of this project, written up on their own.
AI Chat — Hybrid RAG →
How users retrieve insights from their saved captures. A hybrid RAG system that automatically switches between Long Context (full library injection) and semantic embedding search based on library size — optimizing for both accuracy and cost.
Defense Layers →
How I prevent AI Chat from becoming a free ChatGPT. 4 server-enforced layers — rate limiting, quota, per-request caps, and scope enforcement — to keep LLM token costs bounded and conversations within the user's collage.
.overlay Is Not Yours →
Why common class names are a silent attack surface for injected UI. A content-script modal that rendered fine everywhere except Brave — and the CSS cascade-origin rule that explains why Shadow DOM isn't the fortress you think it is.